Merge "Add AudioRestriction get/set APIs on CameraDeviceWrapper" into androidx-main
diff --git a/activity/activity-compose/api/1.9.0-beta01.txt b/activity/activity-compose/api/1.9.0-beta01.txt
new file mode 100644
index 0000000..c6e178e
--- /dev/null
+++ b/activity/activity-compose/api/1.9.0-beta01.txt
@@ -0,0 +1,55 @@
+// 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>,?> 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>,?> 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/res-1.9.0-beta01.txt b/activity/activity-compose/api/res-1.9.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/activity/activity-compose/api/res-1.9.0-beta01.txt
diff --git a/activity/activity-compose/api/restricted_1.9.0-beta01.txt b/activity/activity-compose/api/restricted_1.9.0-beta01.txt
new file mode 100644
index 0000000..c6e178e
--- /dev/null
+++ b/activity/activity-compose/api/restricted_1.9.0-beta01.txt
@@ -0,0 +1,55 @@
+// 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>,?> 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>,?> block);
+ method @androidx.compose.runtime.Composable public static void ReportDrawnWhen(kotlin.jvm.functions.Function0<java.lang.Boolean> predicate);
+ }
+
+}
+
diff --git a/activity/activity-compose/build.gradle b/activity/activity-compose/build.gradle
index 1fad276..b7f49ecc 100644
--- a/activity/activity-compose/build.gradle
+++ b/activity/activity-compose/build.gradle
@@ -21,8 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import androidx.build.RunApiTasks
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -55,10 +54,9 @@
androidx {
name = "Activity Compose"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose integration with Activity"
- runApiTasks = new RunApiTasks.Yes()
metalavaK2UastEnabled = true
samples(projectOrArtifact(":activity:activity-compose:activity-compose-samples"))
}
diff --git a/activity/activity-ktx/api/1.9.0-beta01.txt b/activity/activity-ktx/api/1.9.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/activity/activity-ktx/api/1.9.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/activity/activity-ktx/api/res-1.9.0-beta01.txt b/activity/activity-ktx/api/res-1.9.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/activity/activity-ktx/api/res-1.9.0-beta01.txt
diff --git a/activity/activity-ktx/api/restricted_1.9.0-beta01.txt b/activity/activity-ktx/api/restricted_1.9.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/activity/activity-ktx/api/restricted_1.9.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/activity/activity/api/1.9.0-beta01.txt b/activity/activity/api/1.9.0-beta01.txt
new file mode 100644
index 0000000..a4c9c09
--- /dev/null
+++ b/activity/activity/api/1.9.0-beta01.txt
@@ -0,0 +1,510 @@
+// 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>,?> 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,?> 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,?> 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 androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType getMediaType();
+ 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 setMediaType(androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType);
+ }
+
+ public final class PickVisualMediaRequestKt {
+ method public static androidx.activity.result.PickVisualMediaRequest PickVisualMediaRequest(optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType);
+ }
+
+}
+
+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(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_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 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/current.ignore b/activity/activity/api/current.ignore
deleted file mode 100644
index c46e892..0000000
--- a/activity/activity/api/current.ignore
+++ /dev/null
@@ -1,23 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.activity.ComponentActivity#registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.ComponentActivity.registerForActivityResult has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-ChangedType: androidx.activity.ComponentActivity#registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.ComponentActivity.registerForActivityResult has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-ChangedType: androidx.activity.result.ActivityResult#CREATOR:
- Field androidx.activity.result.ActivityResult.CREATOR has changed type from android.os.Parcelable.Creator<androidx.activity.result.ActivityResult!> to android.os.Parcelable.Creator<androidx.activity.result.ActivityResult>
-ChangedType: androidx.activity.result.ActivityResultCaller#registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.result.ActivityResultCaller.registerForActivityResult has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-ChangedType: androidx.activity.result.ActivityResultCaller#registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.result.ActivityResultCaller.registerForActivityResult has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-ChangedType: androidx.activity.result.ActivityResultLauncher#getContract():
- Method androidx.activity.result.ActivityResultLauncher.getContract has changed return type from androidx.activity.result.contract.ActivityResultContract<I,?> to androidx.activity.result.contract.ActivityResultContract<I,?>
-ChangedType: androidx.activity.result.ActivityResultRegistry#register(String, androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.result.ActivityResultRegistry.register has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-ChangedType: androidx.activity.result.ActivityResultRegistry#register(String, androidx.lifecycle.LifecycleOwner, androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.result.ActivityResultRegistry.register has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-
-
-RemovedMethod: androidx.activity.ComponentActivity#onMultiWindowModeChanged(boolean):
- Removed method androidx.activity.ComponentActivity.onMultiWindowModeChanged(boolean)
-RemovedMethod: androidx.activity.ComponentActivity#onPictureInPictureModeChanged(boolean):
- Removed method androidx.activity.ComponentActivity.onPictureInPictureModeChanged(boolean)
diff --git a/activity/activity/api/res-1.9.0-beta01.txt b/activity/activity/api/res-1.9.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/activity/activity/api/res-1.9.0-beta01.txt
diff --git a/activity/activity/api/restricted_1.9.0-beta01.txt b/activity/activity/api/restricted_1.9.0-beta01.txt
new file mode 100644
index 0000000..3cb9aa0
--- /dev/null
+++ b/activity/activity/api/restricted_1.9.0-beta01.txt
@@ -0,0 +1,509 @@
+// 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>,?> 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,?> 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,?> 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 androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType getMediaType();
+ 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 setMediaType(androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType);
+ }
+
+ public final class PickVisualMediaRequestKt {
+ method public static androidx.activity.result.PickVisualMediaRequest PickVisualMediaRequest(optional androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType);
+ }
+
+}
+
+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(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_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 static final class ActivityResultContracts.PickVisualMedia.ImageAndVideo implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
+ field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.ImageAndVideo INSTANCE;
+ }
+
+ public static final class ActivityResultContracts.PickVisualMedia.ImageOnly implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
+ field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.ImageOnly INSTANCE;
+ }
+
+ public static final class ActivityResultContracts.PickVisualMedia.SingleMimeType implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
+ ctor public ActivityResultContracts.PickVisualMedia.SingleMimeType(String mimeType);
+ method public String getMimeType();
+ property public final String mimeType;
+ }
+
+ public static final class ActivityResultContracts.PickVisualMedia.VideoOnly implements androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VisualMediaType {
+ field public static final androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.VideoOnly INSTANCE;
+ }
+
+ public static sealed interface ActivityResultContracts.PickVisualMedia.VisualMediaType {
+ }
+
+ public static final class ActivityResultContracts.RequestMultiplePermissions extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.Map<java.lang.String,java.lang.Boolean>> {
+ ctor public ActivityResultContracts.RequestMultiplePermissions();
+ method public android.content.Intent createIntent(android.content.Context context, String[] input);
+ method public androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.Map<java.lang.String,java.lang.Boolean>>? getSynchronousResult(android.content.Context context, String[] input);
+ method public java.util.Map<java.lang.String,java.lang.Boolean> parseResult(int resultCode, android.content.Intent? intent);
+ field public static final String ACTION_REQUEST_PERMISSIONS = "androidx.activity.result.contract.action.REQUEST_PERMISSIONS";
+ field public static final androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions.Companion Companion;
+ field public static final String EXTRA_PERMISSIONS = "androidx.activity.result.contract.extra.PERMISSIONS";
+ field public static final String EXTRA_PERMISSION_GRANT_RESULTS = "androidx.activity.result.contract.extra.PERMISSION_GRANT_RESULTS";
+ }
+
+ public static final class ActivityResultContracts.RequestMultiplePermissions.Companion {
+ }
+
+ public static final class ActivityResultContracts.RequestPermission extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.lang.Boolean> {
+ ctor public ActivityResultContracts.RequestPermission();
+ method public android.content.Intent createIntent(android.content.Context context, String input);
+ method public androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.lang.Boolean>? getSynchronousResult(android.content.Context context, String input);
+ method public Boolean parseResult(int resultCode, android.content.Intent? intent);
+ }
+
+ public static final class ActivityResultContracts.StartActivityForResult extends androidx.activity.result.contract.ActivityResultContract<android.content.Intent,androidx.activity.result.ActivityResult> {
+ ctor public ActivityResultContracts.StartActivityForResult();
+ method public android.content.Intent createIntent(android.content.Context context, android.content.Intent input);
+ method public androidx.activity.result.ActivityResult parseResult(int resultCode, android.content.Intent? intent);
+ field public static final androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult.Companion Companion;
+ field public static final String EXTRA_ACTIVITY_OPTIONS_BUNDLE = "androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE";
+ }
+
+ public static final class ActivityResultContracts.StartActivityForResult.Companion {
+ }
+
+ public static final class ActivityResultContracts.StartIntentSenderForResult extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.IntentSenderRequest,androidx.activity.result.ActivityResult> {
+ ctor public ActivityResultContracts.StartIntentSenderForResult();
+ method public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.IntentSenderRequest input);
+ method public androidx.activity.result.ActivityResult parseResult(int resultCode, android.content.Intent? intent);
+ field public static final String ACTION_INTENT_SENDER_REQUEST = "androidx.activity.result.contract.action.INTENT_SENDER_REQUEST";
+ field public static final androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult.Companion Companion;
+ field public static final String EXTRA_INTENT_SENDER_REQUEST = "androidx.activity.result.contract.extra.INTENT_SENDER_REQUEST";
+ field public static final String EXTRA_SEND_INTENT_EXCEPTION = "androidx.activity.result.contract.extra.SEND_INTENT_EXCEPTION";
+ }
+
+ public static final class ActivityResultContracts.StartIntentSenderForResult.Companion {
+ }
+
+ public static class ActivityResultContracts.TakePicture extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,java.lang.Boolean> {
+ ctor public ActivityResultContracts.TakePicture();
+ method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
+ method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.lang.Boolean>? getSynchronousResult(android.content.Context context, android.net.Uri input);
+ method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
+ }
+
+ public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void,android.graphics.Bitmap> {
+ ctor public ActivityResultContracts.TakePicturePreview();
+ method @CallSuper public android.content.Intent createIntent(android.content.Context context, Void? input);
+ method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, Void? input);
+ method public final android.graphics.Bitmap? parseResult(int resultCode, android.content.Intent? intent);
+ }
+
+ @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap> {
+ ctor @Deprecated public ActivityResultContracts.TakeVideo();
+ method @Deprecated @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
+ method @Deprecated public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, android.net.Uri input);
+ method @Deprecated public final android.graphics.Bitmap? parseResult(int resultCode, android.content.Intent? intent);
+ }
+
+}
+
diff --git a/activity/activity/api/restricted_current.ignore b/activity/activity/api/restricted_current.ignore
deleted file mode 100644
index c46e892..0000000
--- a/activity/activity/api/restricted_current.ignore
+++ /dev/null
@@ -1,23 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.activity.ComponentActivity#registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.ComponentActivity.registerForActivityResult has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-ChangedType: androidx.activity.ComponentActivity#registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.ComponentActivity.registerForActivityResult has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-ChangedType: androidx.activity.result.ActivityResult#CREATOR:
- Field androidx.activity.result.ActivityResult.CREATOR has changed type from android.os.Parcelable.Creator<androidx.activity.result.ActivityResult!> to android.os.Parcelable.Creator<androidx.activity.result.ActivityResult>
-ChangedType: androidx.activity.result.ActivityResultCaller#registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.result.ActivityResultCaller.registerForActivityResult has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-ChangedType: androidx.activity.result.ActivityResultCaller#registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.result.ActivityResultCaller.registerForActivityResult has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-ChangedType: androidx.activity.result.ActivityResultLauncher#getContract():
- Method androidx.activity.result.ActivityResultLauncher.getContract has changed return type from androidx.activity.result.contract.ActivityResultContract<I,?> to androidx.activity.result.contract.ActivityResultContract<I,?>
-ChangedType: androidx.activity.result.ActivityResultRegistry#register(String, androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.result.ActivityResultRegistry.register has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-ChangedType: androidx.activity.result.ActivityResultRegistry#register(String, androidx.lifecycle.LifecycleOwner, androidx.activity.result.contract.ActivityResultContract<I,O>, androidx.activity.result.ActivityResultCallback<O>):
- Method androidx.activity.result.ActivityResultRegistry.register has changed return type from androidx.activity.result.ActivityResultLauncher<I> to androidx.activity.result.ActivityResultLauncher<I>
-
-
-RemovedMethod: androidx.activity.ComponentActivity#onMultiWindowModeChanged(boolean):
- Removed method androidx.activity.ComponentActivity.onMultiWindowModeChanged(boolean)
-RemovedMethod: androidx.activity.ComponentActivity#onPictureInPictureModeChanged(boolean):
- Removed method androidx.activity.ComponentActivity.onPictureInPictureModeChanged(boolean)
diff --git a/annotation/annotation-experimental/api/current.ignore b/annotation/annotation-experimental/api/current.ignore
deleted file mode 100644
index 3eb820d..0000000
--- a/annotation/annotation-experimental/api/current.ignore
+++ /dev/null
@@ -1,11 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.annotation.OptIn#markerClass():
- Method androidx.annotation.OptIn.markerClass has changed return type from kotlin.reflect.KClass<? extends java.lang.annotation.Annotation>[] to Class<? extends java.lang.annotation.Annotation>[]
-ChangedType: androidx.annotation.experimental.UseExperimental#markerClass():
- Method androidx.annotation.experimental.UseExperimental.markerClass has changed return type from kotlin.reflect.KClass<? extends java.lang.annotation.Annotation>[] to Class<? extends java.lang.annotation.Annotation>[]
-
-
-ParameterNameChange: androidx.annotation.RequiresOptIn.Level#valueOf(String) parameter #0:
- Attempted to change parameter name from name to value in method androidx.annotation.RequiresOptIn.Level.valueOf
-ParameterNameChange: androidx.annotation.experimental.Experimental.Level#valueOf(String) parameter #0:
- Attempted to change parameter name from name to value in method androidx.annotation.experimental.Experimental.Level.valueOf
diff --git a/annotation/annotation-experimental/api/current.txt b/annotation/annotation-experimental/api/current.txt
index 03452a5..ad5d811 100644
--- a/annotation/annotation-experimental/api/current.txt
+++ b/annotation/annotation-experimental/api/current.txt
@@ -12,8 +12,6 @@
}
public enum RequiresOptIn.Level {
- method public static androidx.annotation.RequiresOptIn.Level valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.annotation.RequiresOptIn.Level[] values();
enum_constant public static final androidx.annotation.RequiresOptIn.Level ERROR;
enum_constant public static final androidx.annotation.RequiresOptIn.Level WARNING;
}
@@ -28,8 +26,6 @@
}
@Deprecated public enum Experimental.Level {
- method @Deprecated public static androidx.annotation.experimental.Experimental.Level valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.annotation.experimental.Experimental.Level[] values();
enum_constant @Deprecated public static final androidx.annotation.experimental.Experimental.Level ERROR;
enum_constant @Deprecated public static final androidx.annotation.experimental.Experimental.Level WARNING;
}
diff --git a/annotation/annotation-experimental/api/restricted_current.ignore b/annotation/annotation-experimental/api/restricted_current.ignore
deleted file mode 100644
index 3eb820d..0000000
--- a/annotation/annotation-experimental/api/restricted_current.ignore
+++ /dev/null
@@ -1,11 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.annotation.OptIn#markerClass():
- Method androidx.annotation.OptIn.markerClass has changed return type from kotlin.reflect.KClass<? extends java.lang.annotation.Annotation>[] to Class<? extends java.lang.annotation.Annotation>[]
-ChangedType: androidx.annotation.experimental.UseExperimental#markerClass():
- Method androidx.annotation.experimental.UseExperimental.markerClass has changed return type from kotlin.reflect.KClass<? extends java.lang.annotation.Annotation>[] to Class<? extends java.lang.annotation.Annotation>[]
-
-
-ParameterNameChange: androidx.annotation.RequiresOptIn.Level#valueOf(String) parameter #0:
- Attempted to change parameter name from name to value in method androidx.annotation.RequiresOptIn.Level.valueOf
-ParameterNameChange: androidx.annotation.experimental.Experimental.Level#valueOf(String) parameter #0:
- Attempted to change parameter name from name to value in method androidx.annotation.experimental.Experimental.Level.valueOf
diff --git a/annotation/annotation-experimental/api/restricted_current.txt b/annotation/annotation-experimental/api/restricted_current.txt
index 03452a5..ad5d811 100644
--- a/annotation/annotation-experimental/api/restricted_current.txt
+++ b/annotation/annotation-experimental/api/restricted_current.txt
@@ -12,8 +12,6 @@
}
public enum RequiresOptIn.Level {
- method public static androidx.annotation.RequiresOptIn.Level valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.annotation.RequiresOptIn.Level[] values();
enum_constant public static final androidx.annotation.RequiresOptIn.Level ERROR;
enum_constant public static final androidx.annotation.RequiresOptIn.Level WARNING;
}
@@ -28,8 +26,6 @@
}
@Deprecated public enum Experimental.Level {
- method @Deprecated public static androidx.annotation.experimental.Experimental.Level valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.annotation.experimental.Experimental.Level[] values();
enum_constant @Deprecated public static final androidx.annotation.experimental.Experimental.Level ERROR;
enum_constant @Deprecated public static final androidx.annotation.experimental.Experimental.Level WARNING;
}
diff --git a/annotation/annotation/api/current.txt b/annotation/annotation/api/current.txt
index b5d93db..dababc5 100644
--- a/annotation/annotation/api/current.txt
+++ b/annotation/annotation/api/current.txt
@@ -163,8 +163,6 @@
}
@Deprecated public enum InspectableProperty.ValueType {
- method @Deprecated public static androidx.annotation.InspectableProperty.ValueType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.annotation.InspectableProperty.ValueType[] values();
enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType COLOR;
enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType GRAVITY;
enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType INFERRED;
@@ -300,8 +298,6 @@
}
public enum RestrictTo.Scope {
- method public static androidx.annotation.RestrictTo.Scope valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.annotation.RestrictTo.Scope[] values();
enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
diff --git a/annotation/annotation/api/restricted_current.txt b/annotation/annotation/api/restricted_current.txt
index b5d93db..dababc5 100644
--- a/annotation/annotation/api/restricted_current.txt
+++ b/annotation/annotation/api/restricted_current.txt
@@ -163,8 +163,6 @@
}
@Deprecated public enum InspectableProperty.ValueType {
- method @Deprecated public static androidx.annotation.InspectableProperty.ValueType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.annotation.InspectableProperty.ValueType[] values();
enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType COLOR;
enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType GRAVITY;
enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType INFERRED;
@@ -300,8 +298,6 @@
}
public enum RestrictTo.Scope {
- method public static androidx.annotation.RestrictTo.Scope valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.annotation.RestrictTo.Scope[] values();
enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
diff --git a/annotation/annotation/build.gradle b/annotation/annotation/build.gradle
index 4ac4d65..819a5a8 100644
--- a/annotation/annotation/build.gradle
+++ b/annotation/annotation/build.gradle
@@ -5,10 +5,9 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
-import androidx.build.KotlinTarget
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
+import androidx.build.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
@@ -32,20 +31,19 @@
}
}
- if (KmpPlatformsKt.enableNative(project)) {
- nonJvmMain {
- dependsOn commonMain
- }
+ nonJvmMain {
+ dependsOn commonMain
+ }
- targets.all { target ->
- if (target.platformType !in [KotlinPlatformType.jvm, KotlinPlatformType.common]) {
- target.compilations["main"].defaultSourceSet {
- dependsOn(nonJvmMain)
- }
+ targets.all { target ->
+ if (target.platformType !in [KotlinPlatformType.jvm, KotlinPlatformType.common]) {
+ target.compilations["main"].defaultSourceSet {
+ dependsOn(nonJvmMain)
}
}
}
+
// Workaround for https://youtrack.jetbrains.com/issue/KT-51763
// Make sure commonization runs before any compilation task.
tasks.withType(KotlinNativeCompile).configureEach {
diff --git a/arch/core/core-common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java b/arch/core/core-common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java
index 1f76151..030e7dc 100644
--- a/arch/core/core-common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java
+++ b/arch/core/core-common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java
@@ -363,6 +363,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public abstract static class SupportRemove<K, V> {
+ @SuppressWarnings("HiddenAbstractMethod")
abstract void supportRemove(@NonNull Entry<K, V> entry);
}
diff --git a/autofill/autofill/src/main/java/androidx/autofill/inline/common/ImageViewStyle.java b/autofill/autofill/src/main/java/androidx/autofill/inline/common/ImageViewStyle.java
index ea691a3..c519b4b 100644
--- a/autofill/autofill/src/main/java/androidx/autofill/inline/common/ImageViewStyle.java
+++ b/autofill/autofill/src/main/java/androidx/autofill/inline/common/ImageViewStyle.java
@@ -100,6 +100,7 @@
/**
* Builder for the {@link ImageViewStyle}.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder extends BaseBuilder<ImageViewStyle, Builder> {
public Builder() {
diff --git a/autofill/autofill/src/main/java/androidx/autofill/inline/common/TextViewStyle.java b/autofill/autofill/src/main/java/androidx/autofill/inline/common/TextViewStyle.java
index 3ccd7f3..c289e2e 100644
--- a/autofill/autofill/src/main/java/androidx/autofill/inline/common/TextViewStyle.java
+++ b/autofill/autofill/src/main/java/androidx/autofill/inline/common/TextViewStyle.java
@@ -94,6 +94,7 @@
/**
* Builder for the {@link TextViewStyle}.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder extends BaseBuilder<TextViewStyle, Builder> {
public Builder() {
super(KEY_TEXT_VIEW_STYLE);
diff --git a/autofill/autofill/src/main/java/androidx/autofill/inline/common/ViewStyle.java b/autofill/autofill/src/main/java/androidx/autofill/inline/common/ViewStyle.java
index 01d9207..b322abc 100644
--- a/autofill/autofill/src/main/java/androidx/autofill/inline/common/ViewStyle.java
+++ b/autofill/autofill/src/main/java/androidx/autofill/inline/common/ViewStyle.java
@@ -33,6 +33,7 @@
* Specifies the style for a {@link View} or a {@link android.view.ViewGroup}.
*/
@RequiresApi(api = Build.VERSION_CODES.R)
+@SuppressWarnings("HiddenSuperclass")
public class ViewStyle extends BundledStyle {
private static final String KEY_VIEW_STYLE = "view_style";
@@ -210,6 +211,7 @@
/**
* Builder for the {@link ViewStyle}.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder extends BaseBuilder<ViewStyle, Builder> {
public Builder() {
diff --git a/autofill/autofill/src/main/java/androidx/autofill/inline/v1/InlineSuggestionUi.java b/autofill/autofill/src/main/java/androidx/autofill/inline/v1/InlineSuggestionUi.java
index fb06e18..06b48cb 100644
--- a/autofill/autofill/src/main/java/androidx/autofill/inline/v1/InlineSuggestionUi.java
+++ b/autofill/autofill/src/main/java/androidx/autofill/inline/v1/InlineSuggestionUi.java
@@ -327,6 +327,7 @@
/**
* Style for the V1 inline suggestion UI.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class Style extends BundledStyle implements UiVersions.Style {
private static final String KEY_STYLE_V1 = "style_v1";
private static final String KEY_CHIP_STYLE = "chip_style";
@@ -522,6 +523,7 @@
/**
* Builder for the {@link Style}.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder extends BundledStyle.Builder<Style> {
/**
@@ -641,6 +643,7 @@
/**
* Content for the V1 inline suggestion UI.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class Content extends SlicedContent {
static final String HINT_INLINE_TITLE = "inline_title";
static final String HINT_INLINE_SUBTITLE = "inline_subtitle";
@@ -799,6 +802,7 @@
/**
* Builder for the {@link Content}.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder extends SlicedContent.Builder<Content> {
@NonNull
private final PendingIntent mAttributionIntent;
diff --git a/benchmark/benchmark-common/build.gradle b/benchmark/benchmark-common/build.gradle
index f3f2d14..d2bac00 100644
--- a/benchmark/benchmark-common/build.gradle
+++ b/benchmark/benchmark-common/build.gradle
@@ -102,10 +102,3 @@
]
}
}
-
-// https://github.com/square/wire/issues/1947
-// Remove when we upgrade to fixed wire library
-afterEvaluate {
- tasks.named("compileReleaseKotlin").configure {it.dependsOn("generateDebugProtos")}
- tasks.named("compileDebugKotlin").configure {it.dependsOn("generateReleaseProtos")}
-}
diff --git a/benchmark/benchmark-darwin-core/build.gradle b/benchmark/benchmark-darwin-core/build.gradle
index 2ac5989f..62c42dd 100644
--- a/benchmark/benchmark-darwin-core/build.gradle
+++ b/benchmark/benchmark-darwin-core/build.gradle
@@ -5,7 +5,6 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import androidx.build.PlatformIdentifier
import androidx.build.Publish
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -14,7 +13,7 @@
plugins {
id("AndroidXPlugin")
}
-def enableNative = KmpPlatformsKt.enableNative(project)
+
androidXMultiplatform {
// needed for snapshot publishing to trigger component creation
jvm()
@@ -37,18 +36,14 @@
embedBitcode = BitcodeEmbeddingMode.DISABLE
}
}
- // put mac configuration inside an if to avoid warnings due to unused source sets
- // when building on linux
- if (enableNative) {
- sourceSets {
- // Need a source file to force a klib creation.
- // see: https://youtrack.jetbrains.com/issue/KT-52344
- darwinMain {}
- targets.all { target ->
- if (target.platformType == KotlinPlatformType.native) {
- target.compilations["main"].defaultSourceSet {
- dependsOn(darwinMain)
- }
+ sourceSets {
+ // Need a source file to force a klib creation.
+ // see: https://youtrack.jetbrains.com/issue/KT-52344
+ darwinMain {}
+ targets.all { target ->
+ if (target.platformType == KotlinPlatformType.native) {
+ target.compilations["main"].defaultSourceSet {
+ dependsOn(darwinMain)
}
}
}
diff --git a/benchmark/benchmark-darwin-samples/build.gradle b/benchmark/benchmark-darwin-samples/build.gradle
index 2ce2d6d..f1ad294 100644
--- a/benchmark/benchmark-darwin-samples/build.gradle
+++ b/benchmark/benchmark-darwin-samples/build.gradle
@@ -5,8 +5,6 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.BuildOnServerKt
-import androidx.build.KmpPlatformsKt
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkConfig
@@ -16,7 +14,6 @@
id("androidx.benchmark.darwin")
}
-def macEnabled = KmpPlatformsKt.enableMac(project)
androidXMultiplatform {
// XCFrameworkConfig must always be called AndroidXDarwinBenchmarks
def xcf = new XCFrameworkConfig(project, "AndroidXDarwinBenchmarks")
@@ -33,22 +30,20 @@
}
sourceSets {
- if (macEnabled) {
- iosMain {
- dependencies {
- api(libs.kotlinStdlib)
- api(project(":benchmark:benchmark-darwin"))
- }
+ iosMain {
+ dependencies {
+ api(libs.kotlinStdlib)
+ api(project(":benchmark:benchmark-darwin"))
}
- iosArm64Main {
- dependsOn(iosMain)
- }
- iosSimulatorArm64Main {
- dependsOn(iosMain)
- }
- iosX64Main {
- dependsOn(iosMain)
- }
+ }
+ iosArm64Main {
+ dependsOn(iosMain)
+ }
+ iosSimulatorArm64Main {
+ dependsOn(iosMain)
+ }
+ iosX64Main {
+ dependsOn(iosMain)
}
}
diff --git a/benchmark/benchmark-darwin/build.gradle b/benchmark/benchmark-darwin/build.gradle
index 483c0bb..070395d 100644
--- a/benchmark/benchmark-darwin/build.gradle
+++ b/benchmark/benchmark-darwin/build.gradle
@@ -5,7 +5,6 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import androidx.build.PlatformIdentifier
import androidx.build.Publish
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -15,7 +14,7 @@
plugins {
id("AndroidXPlugin")
}
-def enableNative = KmpPlatformsKt.enableNative(project)
+
androidXMultiplatform {
def xcf = new XCFrameworkConfig(project, "AndroidXDarwinBenchmarks")
ios {
@@ -32,46 +31,42 @@
// the only platform built on all machines
defaultPlatform(PlatformIdentifier.JVM)
sourceSets {
- // checking this avoids the warning about sourcesets that
- // are created but not added to compilation
- if (enableNative) {
- darwinMain {
- dependencies {
- api(libs.kotlinStdlib)
+ darwinMain {
+ dependencies {
+ api(libs.kotlinStdlib)
+ }
+ }
+ darwinTest {
+ dependencies {
+ implementation(libs.kotlinTest)
+ implementation(libs.kotlinTestAnnotationsCommon)
+ }
+ }
+ iosMain {
+ dependsOn(darwinMain)
+ dependencies {
+ api(project(":benchmark:benchmark-darwin-core"))
+ }
+ }
+ iosArm64Main {
+ dependsOn(iosMain)
+ }
+ iosSimulatorArm64Main {
+ dependsOn(iosMain)
+ }
+ iosX64Main {
+ dependsOn(iosMain)
+ }
+ targets.configureEach { target ->
+ if (target.platformType == KotlinPlatformType.native) {
+ target.compilations["main"].defaultSourceSet {
+ dependsOn(darwinMain)
}
- }
- darwinTest {
- dependencies {
- implementation(libs.kotlinTest)
- implementation(libs.kotlinTestAnnotationsCommon)
+ target.compilations["test"].defaultSourceSet {
+ dependsOn(darwinTest)
}
- }
- iosMain {
- dependsOn(darwinMain)
- dependencies {
- api(project(":benchmark:benchmark-darwin-core"))
- }
- }
- iosArm64Main {
- dependsOn(iosMain)
- }
- iosSimulatorArm64Main {
- dependsOn(iosMain)
- }
- iosX64Main {
- dependsOn(iosMain)
- }
- targets.configureEach { target ->
- if (target.platformType == KotlinPlatformType.native) {
- target.compilations["main"].defaultSourceSet {
- dependsOn(darwinMain)
- }
- target.compilations["test"].defaultSourceSet {
- dependsOn(darwinTest)
- }
- target.compilations.configureEach {
- compilerOptions.options.optIn.add("kotlinx.cinterop.ExperimentalForeignApi")
- }
+ target.compilations.configureEach {
+ compilerOptions.options.optIn.add("kotlinx.cinterop.ExperimentalForeignApi")
}
}
}
diff --git a/benchmark/benchmark-macro/api/current.txt b/benchmark/benchmark-macro/api/current.txt
index 0bb3341..88e7474 100644
--- a/benchmark/benchmark-macro/api/current.txt
+++ b/benchmark/benchmark-macro/api/current.txt
@@ -2,8 +2,6 @@
package androidx.benchmark.macro {
public enum BaselineProfileMode {
- method public static androidx.benchmark.macro.BaselineProfileMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.BaselineProfileMode[] values();
enum_constant public static final androidx.benchmark.macro.BaselineProfileMode Disable;
enum_constant public static final androidx.benchmark.macro.BaselineProfileMode Require;
enum_constant public static final androidx.benchmark.macro.BaselineProfileMode UseIfAvailable;
@@ -83,15 +81,11 @@
}
public enum MemoryUsageMetric.Mode {
- method public static androidx.benchmark.macro.MemoryUsageMetric.Mode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.MemoryUsageMetric.Mode[] values();
enum_constant public static final androidx.benchmark.macro.MemoryUsageMetric.Mode Last;
enum_constant public static final androidx.benchmark.macro.MemoryUsageMetric.Mode Max;
}
public enum MemoryUsageMetric.SubMetric {
- method public static androidx.benchmark.macro.MemoryUsageMetric.SubMetric valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.MemoryUsageMetric.SubMetric[] values();
enum_constant public static final androidx.benchmark.macro.MemoryUsageMetric.SubMetric Gpu;
enum_constant public static final androidx.benchmark.macro.MemoryUsageMetric.SubMetric HeapSize;
enum_constant public static final androidx.benchmark.macro.MemoryUsageMetric.SubMetric RssAnon;
@@ -139,8 +133,6 @@
}
@SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public enum PowerCategory {
- method public static androidx.benchmark.macro.PowerCategory valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.PowerCategory[] values();
enum_constant public static final androidx.benchmark.macro.PowerCategory CPU;
enum_constant public static final androidx.benchmark.macro.PowerCategory DISPLAY;
enum_constant public static final androidx.benchmark.macro.PowerCategory GPS;
@@ -152,8 +144,6 @@
}
@SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public enum PowerCategoryDisplayLevel {
- method public static androidx.benchmark.macro.PowerCategoryDisplayLevel valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.PowerCategoryDisplayLevel[] values();
enum_constant public static final androidx.benchmark.macro.PowerCategoryDisplayLevel BREAKDOWN;
enum_constant public static final androidx.benchmark.macro.PowerCategoryDisplayLevel TOTAL;
}
@@ -191,8 +181,6 @@
}
public enum StartupMode {
- method public static androidx.benchmark.macro.StartupMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.StartupMode[] values();
enum_constant public static final androidx.benchmark.macro.StartupMode COLD;
enum_constant public static final androidx.benchmark.macro.StartupMode HOT;
enum_constant public static final androidx.benchmark.macro.StartupMode WARM;
@@ -212,8 +200,6 @@
}
public enum TraceSectionMetric.Mode {
- method public static androidx.benchmark.macro.TraceSectionMetric.Mode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.TraceSectionMetric.Mode[] values();
enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode First;
enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Max;
enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Min;
diff --git a/benchmark/benchmark-macro/api/restricted_current.txt b/benchmark/benchmark-macro/api/restricted_current.txt
index b7caaa5..3ff3944 100644
--- a/benchmark/benchmark-macro/api/restricted_current.txt
+++ b/benchmark/benchmark-macro/api/restricted_current.txt
@@ -2,8 +2,6 @@
package androidx.benchmark.macro {
public enum BaselineProfileMode {
- method public static androidx.benchmark.macro.BaselineProfileMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.BaselineProfileMode[] values();
enum_constant public static final androidx.benchmark.macro.BaselineProfileMode Disable;
enum_constant public static final androidx.benchmark.macro.BaselineProfileMode Require;
enum_constant public static final androidx.benchmark.macro.BaselineProfileMode UseIfAvailable;
@@ -96,15 +94,11 @@
}
public enum MemoryUsageMetric.Mode {
- method public static androidx.benchmark.macro.MemoryUsageMetric.Mode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.MemoryUsageMetric.Mode[] values();
enum_constant public static final androidx.benchmark.macro.MemoryUsageMetric.Mode Last;
enum_constant public static final androidx.benchmark.macro.MemoryUsageMetric.Mode Max;
}
public enum MemoryUsageMetric.SubMetric {
- method public static androidx.benchmark.macro.MemoryUsageMetric.SubMetric valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.MemoryUsageMetric.SubMetric[] values();
enum_constant public static final androidx.benchmark.macro.MemoryUsageMetric.SubMetric Gpu;
enum_constant public static final androidx.benchmark.macro.MemoryUsageMetric.SubMetric HeapSize;
enum_constant public static final androidx.benchmark.macro.MemoryUsageMetric.SubMetric RssAnon;
@@ -152,8 +146,6 @@
}
@SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public enum PowerCategory {
- method public static androidx.benchmark.macro.PowerCategory valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.PowerCategory[] values();
enum_constant public static final androidx.benchmark.macro.PowerCategory CPU;
enum_constant public static final androidx.benchmark.macro.PowerCategory DISPLAY;
enum_constant public static final androidx.benchmark.macro.PowerCategory GPS;
@@ -165,8 +157,6 @@
}
@SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public enum PowerCategoryDisplayLevel {
- method public static androidx.benchmark.macro.PowerCategoryDisplayLevel valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.PowerCategoryDisplayLevel[] values();
enum_constant public static final androidx.benchmark.macro.PowerCategoryDisplayLevel BREAKDOWN;
enum_constant public static final androidx.benchmark.macro.PowerCategoryDisplayLevel TOTAL;
}
@@ -209,8 +199,6 @@
}
public enum StartupMode {
- method public static androidx.benchmark.macro.StartupMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.StartupMode[] values();
enum_constant public static final androidx.benchmark.macro.StartupMode COLD;
enum_constant public static final androidx.benchmark.macro.StartupMode HOT;
enum_constant public static final androidx.benchmark.macro.StartupMode WARM;
@@ -234,8 +222,6 @@
}
public enum TraceSectionMetric.Mode {
- method public static androidx.benchmark.macro.TraceSectionMetric.Mode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.benchmark.macro.TraceSectionMetric.Mode[] values();
enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode First;
enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Max;
enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Min;
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/VerifyBenchmarkCompiledTest.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/VerifyBenchmarkCompiledTest.kt
new file mode 100644
index 0000000..35a32f5
--- /dev/null
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/VerifyBenchmarkCompiledTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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 androidx.benchmark.Arguments
+import androidx.benchmark.Shell
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeFalse
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test to help validate compilation occurs.
+ * In the future, consider moving this to be a module-wide configurable assert
+ *
+ * Note that while most non-benchmark tests shouldn't live in benchmark modules, this is an
+ * exception, as it's validating runtime conditions (esp in CI)
+ */
+@MediumTest
+@SdkSuppress(minSdkVersion = 21)
+@RunWith(AndroidJUnit4::class)
+class VerifyBenchmarkCompiledTest {
+ @Test
+ fun verifyCompilation() {
+ assumeFalse("ignoring compilation state in dry run mode", Arguments.dryRunMode)
+ val stdout = Shell.executeScriptCaptureStdout(
+ "dumpsys package dexopt | grep -A 1 \"androidx.benchmark.benchmark.test\""
+ )
+ assertTrue(
+ "expected exactly one instance of compilation status, output = $stdout",
+ stdout.indexOf("[status=") == stdout.lastIndexOf("[status=")
+ )
+ assertTrue(
+ "expected dexopt to show speed compilation, output = $stdout",
+ stdout.contains("[status=speed]")
+ )
+ }
+}
diff --git a/bluetooth/bluetooth-testing/build.gradle b/bluetooth/bluetooth-testing/build.gradle
index 1e803b5..adf2e11 100644
--- a/bluetooth/bluetooth-testing/build.gradle
+++ b/bluetooth/bluetooth-testing/build.gradle
@@ -53,6 +53,5 @@
namespace "androidx.bluetooth.testing"
defaultConfig {
minSdkVersion 33
- targetSdkVersion 33
}
}
diff --git a/bluetooth/integration-tests/testapp/build.gradle b/bluetooth/integration-tests/testapp/build.gradle
index 54965f0..62ef054 100644
--- a/bluetooth/integration-tests/testapp/build.gradle
+++ b/bluetooth/integration-tests/testapp/build.gradle
@@ -34,16 +34,6 @@
android {
defaultConfig {
minSdkVersion 21
- targetSdkVersion 33
- versionCode 1
- versionName "1.0"
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = "1.8"
}
buildFeatures {
viewBinding true
diff --git a/browser/browser/api/api_lint.ignore b/browser/browser/api/api_lint.ignore
index 4135ef4..4ce666c 100644
--- a/browser/browser/api/api_lint.ignore
+++ b/browser/browser/api/api_lint.ignore
@@ -5,10 +5,6 @@
Acronyms should not be capitalized in method names: was `shouldAlwaysUseBrowserUI`, should this be `shouldAlwaysUseBrowserUi`?
-ActionValue: androidx.browser.browseractions.BrowserActionsIntent#ACTION_BROWSER_ACTIONS_OPEN:
- Inconsistent action value; expected `androidx.browser.browseractions.action.BROWSER_ACTIONS_OPEN`, was `androidx.browser.browseractions.browser_action_open`
-ActionValue: androidx.browser.browseractions.BrowserActionsIntent#EXTRA_APP_ID:
- Inconsistent extra value; expected `androidx.browser.browseractions.extra.APP_ID`, was `androidx.browser.browseractions.APP_ID`
ActionValue: androidx.browser.customtabs.CustomTabsIntent#EXTRA_ACTION_BUTTON_BUNDLE:
Inconsistent extra value; expected `androidx.browser.customtabs.extra.ACTION_BUTTON_BUNDLE`, was `android.support.customtabs.extra.ACTION_BUTTON_BUNDLE`
ActionValue: androidx.browser.customtabs.CustomTabsIntent#EXTRA_CLOSE_BUTTON_ICON:
@@ -77,14 +73,8 @@
Callback method names must follow the on<Something> style: extraCallbackWithResult
-ConcreteCollection: androidx.browser.browseractions.BrowserActionsIntent#openBrowserAction(android.content.Context, android.net.Uri, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>, android.app.PendingIntent) parameter #3:
- Parameter type is concrete collection (`java.util.ArrayList`); must be higher-level interface
-ConcreteCollection: androidx.browser.browseractions.BrowserActionsIntent#parseBrowserActionItems(java.util.ArrayList<android.os.Bundle>) parameter #0:
- Parameter type is concrete collection (`java.util.ArrayList`); must be higher-level interface
-ConcreteCollection: androidx.browser.browseractions.BrowserActionsIntent.Builder#setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>) parameter #0:
- Parameter type is concrete collection (`java.util.ArrayList`); must be higher-level interface
-
-
+ExecutorRegistration: androidx.browser.customtabs.CustomTabsClient#newPendingSession(android.content.Context, androidx.browser.customtabs.CustomTabsCallback, int):
+ Registration methods should have overload that accepts delivery Executor: `newPendingSession`
ExecutorRegistration: androidx.browser.customtabs.CustomTabsClient#newSession(androidx.browser.customtabs.CustomTabsCallback, int):
Registration methods should have overload that accepts delivery Executor: `newSession`
ExecutorRegistration: androidx.browser.trusted.TrustedWebActivityServiceConnection#sendExtraCommand(String, android.os.Bundle, androidx.browser.trusted.TrustedWebActivityCallback):
@@ -97,10 +87,6 @@
Getter should be on the built object, not the builder: method androidx.browser.trusted.TrustedWebActivityIntentBuilder.getUri()
-IntentName: androidx.browser.browseractions.BrowserActionsIntent#KEY_ACTION:
- Intent action constant name must be ACTION_FOO: KEY_ACTION
-
-
InvalidNullabilityOverride: androidx.browser.customtabs.CustomTabsServiceConnection#onServiceConnected(android.content.ComponentName, android.os.IBinder) parameter #0:
Invalid nullability on parameter `name` in method `onServiceConnected`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: androidx.browser.customtabs.CustomTabsServiceConnection#onServiceConnected(android.content.ComponentName, android.os.IBinder) parameter #1:
@@ -111,22 +97,14 @@
Invalid nullability on parameter `service` in method `onServiceConnected`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: androidx.browser.customtabs.PostMessageServiceConnection#onServiceDisconnected(android.content.ComponentName) parameter #0:
Invalid nullability on parameter `name` in method `onServiceDisconnected`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: androidx.browser.trusted.TrustedWebActivityService#onBind(android.content.Intent):
- Invalid nullability on method `onBind` return. Overrides of unannotated super method cannot be Nullable.
+ListenerLast: androidx.browser.customtabs.CustomTabsClient#newPendingSession(android.content.Context, androidx.browser.customtabs.CustomTabsCallback, int) parameter #2:
+ Listeners should always be at end of argument list (method `newPendingSession`)
ListenerLast: androidx.browser.customtabs.CustomTabsClient#newSession(androidx.browser.customtabs.CustomTabsCallback, int) parameter #1:
Listeners should always be at end of argument list (method `newSession`)
-MissingGetterMatchingBuilder: androidx.browser.browseractions.BrowserActionsIntent.Builder#setCustomItems(androidx.browser.browseractions.BrowserActionItem...):
- androidx.browser.browseractions.BrowserActionsIntent does not declare a `getCustomItems()` method matching method androidx.browser.browseractions.BrowserActionsIntent.Builder.setCustomItems(androidx.browser.browseractions.BrowserActionItem...)
-MissingGetterMatchingBuilder: androidx.browser.browseractions.BrowserActionsIntent.Builder#setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>):
- androidx.browser.browseractions.BrowserActionsIntent does not declare a `getCustomItems()` method matching method androidx.browser.browseractions.BrowserActionsIntent.Builder.setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>)
-MissingGetterMatchingBuilder: androidx.browser.browseractions.BrowserActionsIntent.Builder#setOnItemSelectedAction(android.app.PendingIntent):
- androidx.browser.browseractions.BrowserActionsIntent does not declare a `getOnItemSelectedAction()` method matching method androidx.browser.browseractions.BrowserActionsIntent.Builder.setOnItemSelectedAction(android.app.PendingIntent)
-MissingGetterMatchingBuilder: androidx.browser.browseractions.BrowserActionsIntent.Builder#setUrlType(int):
- androidx.browser.browseractions.BrowserActionsIntent does not declare a `getUrlType()` method matching method androidx.browser.browseractions.BrowserActionsIntent.Builder.setUrlType(int)
MissingGetterMatchingBuilder: androidx.browser.customtabs.CustomTabColorSchemeParams.Builder#setNavigationBarColor(int):
androidx.browser.customtabs.CustomTabColorSchemeParams does not declare a `getNavigationBarColor()` method matching method androidx.browser.customtabs.CustomTabColorSchemeParams.Builder.setNavigationBarColor(int)
MissingGetterMatchingBuilder: androidx.browser.customtabs.CustomTabColorSchemeParams.Builder#setNavigationBarDividerColor(int):
@@ -151,6 +129,8 @@
androidx.browser.customtabs.CustomTabsIntent does not declare a `getExitAnimations()` method matching method androidx.browser.customtabs.CustomTabsIntent.Builder.setExitAnimations(android.content.Context,int,int)
MissingGetterMatchingBuilder: androidx.browser.customtabs.CustomTabsIntent.Builder#setInstantAppsEnabled(boolean):
androidx.browser.customtabs.CustomTabsIntent does not declare a `isInstantAppsEnabled()` method matching method androidx.browser.customtabs.CustomTabsIntent.Builder.setInstantAppsEnabled(boolean)
+MissingGetterMatchingBuilder: androidx.browser.customtabs.CustomTabsIntent.Builder#setPendingSession(androidx.browser.customtabs.CustomTabsSession.PendingSession):
+ androidx.browser.customtabs.CustomTabsIntent does not declare a `getPendingSession()` method matching method androidx.browser.customtabs.CustomTabsIntent.Builder.setPendingSession(androidx.browser.customtabs.CustomTabsSession.PendingSession)
MissingGetterMatchingBuilder: androidx.browser.customtabs.CustomTabsIntent.Builder#setSecondaryToolbarViews(android.widget.RemoteViews, int[], android.app.PendingIntent):
androidx.browser.customtabs.CustomTabsIntent does not declare a `getSecondaryToolbarViews()` method matching method androidx.browser.customtabs.CustomTabsIntent.Builder.setSecondaryToolbarViews(android.widget.RemoteViews,int[],android.app.PendingIntent)
MissingGetterMatchingBuilder: androidx.browser.customtabs.CustomTabsIntent.Builder#setSession(androidx.browser.customtabs.CustomTabsSession):
diff --git a/browser/browser/api/current.ignore b/browser/browser/api/current.ignore
deleted file mode 100644
index a82ad47..0000000
--- a/browser/browser/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.browser.customtabs.CustomTabsIntent.Builder#setActivitySideSheetMaximizationEnabled(boolean):
- Added method androidx.browser.customtabs.CustomTabsIntent.Builder.setActivitySideSheetMaximizationEnabled(boolean)
diff --git a/browser/browser/api/current.txt b/browser/browser/api/current.txt
index f0074ac..df5e688 100644
--- a/browser/browser/api/current.txt
+++ b/browser/browser/api/current.txt
@@ -97,12 +97,14 @@
}
public class CustomTabsClient {
+ method @SuppressCompatibility @androidx.browser.customtabs.ExperimentalPendingSession public androidx.browser.customtabs.CustomTabsSession? attachSession(androidx.browser.customtabs.CustomTabsSession.PendingSession);
method public static boolean bindCustomTabsService(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
method public static boolean bindCustomTabsServicePreservePriority(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
method public static boolean connectAndInitialize(android.content.Context, String);
method public android.os.Bundle? extraCommand(String, android.os.Bundle?);
method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?);
method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?, boolean);
+ method @SuppressCompatibility @androidx.browser.customtabs.ExperimentalPendingSession public static androidx.browser.customtabs.CustomTabsSession.PendingSession newPendingSession(android.content.Context, androidx.browser.customtabs.CustomTabsCallback?, int);
method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?);
method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?, int);
method public boolean warmup(long);
@@ -233,6 +235,7 @@
method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarColor(@ColorInt int);
method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarDividerColor(@ColorInt int);
+ method @SuppressCompatibility @androidx.browser.customtabs.ExperimentalPendingSession public androidx.browser.customtabs.CustomTabsIntent.Builder setPendingSession(androidx.browser.customtabs.CustomTabsSession.PendingSession);
method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarColor(@ColorInt int);
method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarSwipeUpGesture(android.app.PendingIntent?);
method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[]?, android.app.PendingIntent?);
@@ -310,6 +313,9 @@
method public boolean validateRelationship(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
}
+ @SuppressCompatibility @androidx.browser.customtabs.ExperimentalPendingSession public static class CustomTabsSession.PendingSession {
+ }
+
public class CustomTabsSessionToken {
method public static androidx.browser.customtabs.CustomTabsSessionToken createMockSessionTokenForTesting();
method public androidx.browser.customtabs.CustomTabsCallback? getCallback();
@@ -326,6 +332,9 @@
@SuppressCompatibility @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.WARNING) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalMinimizationCallback {
}
+ @SuppressCompatibility @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.WARNING) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPendingSession {
+ }
+
public class PostMessageService extends android.app.Service {
ctor public PostMessageService();
method public android.os.IBinder onBind(android.content.Intent?);
diff --git a/browser/browser/api/restricted_current.ignore b/browser/browser/api/restricted_current.ignore
deleted file mode 100644
index a82ad47..0000000
--- a/browser/browser/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.browser.customtabs.CustomTabsIntent.Builder#setActivitySideSheetMaximizationEnabled(boolean):
- Added method androidx.browser.customtabs.CustomTabsIntent.Builder.setActivitySideSheetMaximizationEnabled(boolean)
diff --git a/browser/browser/api/restricted_current.txt b/browser/browser/api/restricted_current.txt
index 62f48bc..329d2a6 100644
--- a/browser/browser/api/restricted_current.txt
+++ b/browser/browser/api/restricted_current.txt
@@ -108,12 +108,14 @@
}
public class CustomTabsClient {
+ method @SuppressCompatibility @androidx.browser.customtabs.ExperimentalPendingSession public androidx.browser.customtabs.CustomTabsSession? attachSession(androidx.browser.customtabs.CustomTabsSession.PendingSession);
method public static boolean bindCustomTabsService(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
method public static boolean bindCustomTabsServicePreservePriority(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
method public static boolean connectAndInitialize(android.content.Context, String);
method public android.os.Bundle? extraCommand(String, android.os.Bundle?);
method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?);
method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?, boolean);
+ method @SuppressCompatibility @androidx.browser.customtabs.ExperimentalPendingSession public static androidx.browser.customtabs.CustomTabsSession.PendingSession newPendingSession(android.content.Context, androidx.browser.customtabs.CustomTabsCallback?, int);
method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?);
method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?, int);
method public boolean warmup(long);
@@ -244,6 +246,7 @@
method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarColor(@ColorInt int);
method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarDividerColor(@ColorInt int);
+ method @SuppressCompatibility @androidx.browser.customtabs.ExperimentalPendingSession public androidx.browser.customtabs.CustomTabsIntent.Builder setPendingSession(androidx.browser.customtabs.CustomTabsSession.PendingSession);
method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarColor(@ColorInt int);
method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarSwipeUpGesture(android.app.PendingIntent?);
method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[]?, android.app.PendingIntent?);
@@ -321,6 +324,9 @@
method public boolean validateRelationship(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
}
+ @SuppressCompatibility @androidx.browser.customtabs.ExperimentalPendingSession public static class CustomTabsSession.PendingSession {
+ }
+
public class CustomTabsSessionToken {
method public static androidx.browser.customtabs.CustomTabsSessionToken createMockSessionTokenForTesting();
method public androidx.browser.customtabs.CustomTabsCallback? getCallback();
@@ -337,6 +343,9 @@
@SuppressCompatibility @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.WARNING) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalMinimizationCallback {
}
+ @SuppressCompatibility @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.WARNING) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPendingSession {
+ }
+
public class PostMessageService extends android.app.Service {
ctor public PostMessageService();
method public android.os.IBinder onBind(android.content.Intent?);
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsClient.java b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsClient.java
index e445ca5..b71d475 100644
--- a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsClient.java
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsClient.java
@@ -36,7 +36,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
import java.util.ArrayList;
import java.util.List;
@@ -276,7 +275,7 @@
*
* {@see PendingSession}
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @ExperimentalPendingSession
@NonNull
public static CustomTabsSession.PendingSession newPendingSession(
@NonNull Context context, @Nullable final CustomTabsCallback callback, int id) {
@@ -464,7 +463,7 @@
* and turn it into a {@link CustomTabsSession}.
*
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @ExperimentalPendingSession
@SuppressWarnings("NullAway") // TODO: b/141869399
@Nullable
public CustomTabsSession attachSession(@NonNull CustomTabsSession.PendingSession session) {
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
index 2a095c5..dc9e030 100644
--- a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
@@ -723,7 +723,7 @@
* Overrides the effect of {@link #setSession}.
*
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @ExperimentalPendingSession
@NonNull
public Builder setPendingSession(@NonNull CustomTabsSession.PendingSession session) {
setSessionParameters(null, session.getId());
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsSession.java b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsSession.java
index 09dabc4..d90e204 100644
--- a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsSession.java
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsSession.java
@@ -35,7 +35,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresFeature;
-import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.browser.customtabs.CustomTabsService.Relation;
import androidx.browser.customtabs.CustomTabsService.Result;
@@ -525,12 +524,12 @@
}
/**
- * A class to be used instead of {@link CustomTabsSession} before we are connected
- * {@link CustomTabsService}.
+ * A class to be used instead of {@link CustomTabsSession} when a Custom Tab is launched before
+ * a Service connection is established.
*
* Use {@link CustomTabsClient#attachSession(PendingSession)} to get {@link CustomTabsSession}.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @ExperimentalPendingSession
public static class PendingSession {
@Nullable
private final CustomTabsCallback mCallback;
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/ExperimentalPendingSession.java b/browser/browser/src/main/java/androidx/browser/customtabs/ExperimentalPendingSession.java
new file mode 100644
index 0000000..3074119
--- /dev/null
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/ExperimentalPendingSession.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.browser.customtabs;
+
+import androidx.annotation.RequiresOptIn;
+
+@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
+public @interface ExperimentalPendingSession {}
diff --git a/buildSrc-tests/lint-baseline.xml b/buildSrc-tests/lint-baseline.xml
index 9ee6f5b..82349a5 100644
--- a/buildSrc-tests/lint-baseline.xml
+++ b/buildSrc-tests/lint-baseline.xml
@@ -568,4 +568,13 @@
file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/dependencyTracker/ToStringLogger.kt"/>
</issue>
+ <issue
+ id="WithPluginClasspathUsage"
+ message="Avoid usage of GradleRunner#withPluginClasspath, which is broken. Instead use something like https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/testkit#gradle-testkit-support-plugin"
+ errorLine1=" .withPluginClasspath()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/test/java/androidx/build/buildInfo/CreateLibraryBuildInfoFileTaskTest.kt"/>
+ </issue>
+
</issues>
diff --git a/buildSrc-tests/src/test/java/androidx/build/KmpPlatformsTest.kt b/buildSrc-tests/src/test/java/androidx/build/KmpPlatformsTest.kt
index 1eeeaaa..a3a86aa 100644
--- a/buildSrc-tests/src/test/java/androidx/build/KmpPlatformsTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/KmpPlatformsTest.kt
@@ -24,47 +24,86 @@
@Test
fun withAnEmptyFlag_itReturnsTheDefaultValue() {
assertThat(parseTargetPlatformsFlag("")).isEqualTo(
- setOf(PlatformGroup.JVM, PlatformGroup.DESKTOP)
+ setOf(
+ PlatformGroup.JVM,
+ PlatformGroup.MAC,
+ PlatformGroup.LINUX,
+ PlatformGroup.DESKTOP,
+ PlatformGroup.ANDROID_NATIVE
+ )
)
}
@Test
fun withANullFlag_itReturnsTheDefaultValue() {
assertThat(parseTargetPlatformsFlag(null)).isEqualTo(
- setOf(PlatformGroup.JVM, PlatformGroup.DESKTOP)
+ setOf(
+ PlatformGroup.JVM,
+ PlatformGroup.MAC,
+ PlatformGroup.LINUX,
+ PlatformGroup.DESKTOP,
+ PlatformGroup.ANDROID_NATIVE,
+ )
)
}
@Test
fun withASingleDefaultPlatform_itParsesTheFlagCorrectly() {
assertThat(parseTargetPlatformsFlag("+jvm")).isEqualTo(
- setOf(PlatformGroup.JVM, PlatformGroup.DESKTOP)
+ setOf(
+ PlatformGroup.JVM,
+ PlatformGroup.MAC,
+ PlatformGroup.LINUX,
+ PlatformGroup.DESKTOP,
+ PlatformGroup.ANDROID_NATIVE,
+ )
)
}
@Test
fun withNoPlatforms_itParsesTheFlagCorrectly() {
- assertThat(parseTargetPlatformsFlag("-jvm,-desktop")).isEqualTo(emptySet<PlatformGroup>())
+ assertThat(parseTargetPlatformsFlag("-jvm,-desktop,-native")).isEqualTo(
+ emptySet<PlatformGroup>()
+ )
}
@Test
fun withASingleNonDefaultPlatform_itParsesTheFlagCorrectly() {
assertThat(parseTargetPlatformsFlag("+js")).isEqualTo(
- setOf(PlatformGroup.JVM, PlatformGroup.JS, PlatformGroup.DESKTOP)
+ setOf(
+ PlatformGroup.JVM,
+ PlatformGroup.JS,
+ PlatformGroup.MAC,
+ PlatformGroup.LINUX,
+ PlatformGroup.DESKTOP,
+ PlatformGroup.ANDROID_NATIVE,
+ )
)
}
@Test
fun withAMultiplePlatforms_itParsesTheFlagCorrectly() {
assertThat(parseTargetPlatformsFlag("+js,+mac")).isEqualTo(
- setOf(PlatformGroup.JVM, PlatformGroup.JS, PlatformGroup.MAC, PlatformGroup.DESKTOP)
+ setOf(
+ PlatformGroup.JVM,
+ PlatformGroup.JS,
+ PlatformGroup.MAC,
+ PlatformGroup.LINUX,
+ PlatformGroup.DESKTOP,
+ PlatformGroup.ANDROID_NATIVE,
+ )
)
}
@Test
fun withNegativeFlags_itParsesTheFlagCorrectly() {
assertThat(parseTargetPlatformsFlag("-jvm,+mac")).isEqualTo(
- setOf(PlatformGroup.MAC, PlatformGroup.DESKTOP)
+ setOf(
+ PlatformGroup.MAC,
+ PlatformGroup.LINUX,
+ PlatformGroup.DESKTOP,
+ PlatformGroup.ANDROID_NATIVE,
+ )
)
}
diff --git a/buildSrc-tests/src/test/java/androidx/build/clang/AndroidXClangTest.kt b/buildSrc-tests/src/test/java/androidx/build/clang/AndroidXClangTest.kt
index 146f1fe..2a4fad9 100644
--- a/buildSrc-tests/src/test/java/androidx/build/clang/AndroidXClangTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/clang/AndroidXClangTest.kt
@@ -102,10 +102,10 @@
it.freeArgs.addAll("androidArg2")
}
- // configurations are done lazily when needed
- assertThat(project.tasks.withType(
- ClangCompileTask::class.java
- ).toList()).isEmpty()
+ // Add this check if we can re-enable lazy evaluation b/325518502
+// assertThat(project.tasks.withType(
+// ClangCompileTask::class.java
+// ).toList()).isEmpty()
// trigger configuration of targets
multiTargetNativeCompilation.targetProvider(KonanTarget.LINUX_X64).get()
diff --git a/buildSrc/lint.xml b/buildSrc/lint.xml
index 1eaf525..1993387 100644
--- a/buildSrc/lint.xml
+++ b/buildSrc/lint.xml
@@ -41,6 +41,7 @@
<issue id="WrongThread" severity="fatal" />
<issue id="MissingTestSizeAnnotation" severity="fatal" />
<issue id="IgnoreClassLevelDetector" severity="fatal" />
+ <issue id="WithPluginClasspathUsage" severity="fatal" />
<!-- Disable all lint checks on transformed classes by default. b/283812176 -->
<issue id="all">
<ignore path="**/.transforms/**" />
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index 06824dc..3c1a750 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -249,6 +249,7 @@
compile.pluginClasspath.from(kotlinPluginProvider.get())
compile.addPluginOption(ComposeCompileOptions.StrongSkippingOption, "true")
+ compile.addPluginOption(ComposeCompileOptions.NonSkippingGroupOption, "true")
if (shouldPublish) {
compile.addPluginOption(ComposeCompileOptions.SourceOption, "true")
@@ -333,9 +334,12 @@
return File(getDistributionDirectory(), "compose-compiler-data")
}
+private const val ComposePluginId = "androidx.compose.compiler.plugins.kotlin"
+
private enum class ComposeCompileOptions(val pluginId: String, val key: String) {
- SourceOption("androidx.compose.compiler.plugins.kotlin", "sourceInformation"),
- MetricsOption("androidx.compose.compiler.plugins.kotlin", "metricsDestination"),
- ReportsOption("androidx.compose.compiler.plugins.kotlin", "reportsDestination"),
- StrongSkippingOption("androidx.compose.compiler.plugins.kotlin", "experimentalStrongSkipping");
+ SourceOption(ComposePluginId, "sourceInformation"),
+ MetricsOption(ComposePluginId, "metricsDestination"),
+ ReportsOption(ComposePluginId, "reportsDestination"),
+ StrongSkippingOption(ComposePluginId, "experimentalStrongSkipping"),
+ NonSkippingGroupOption(ComposePluginId, "nonSkippingGroupOptimization")
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 5a85133..3589538 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -302,28 +302,6 @@
var description: String? = null
var inceptionYear: String? = null
- /**
- * targetsJavaConsumers = true, if project is intended to be accessed from Java-language source
- * code.
- */
- var targetsJavaConsumers = true
- get() {
- when (project.path) {
- // add per-project overrides here
- // for example
- // the following project is intended to be accessed from Java
- // ":compose:lint:internal-lint-checks" -> return true
- // the following project is not intended to be accessed from Java
- // ":annotation:annotation" -> return false
- }
- // TODO: rework this to use LibraryType. Fork Library and KolinOnlyLibrary?
- if (project.path.contains("-ktx")) return false
- if (project.path.contains("compose")) return false
- if (project.path.startsWith(":ui")) return false
- if (project.path.startsWith(":text:text")) return false
- return field
- }
-
private var licenses: MutableCollection<License> = ArrayList()
// Should only be used to override LibraryType.publish, if a library isn't ready to publish yet
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 5a152d2..a83a85c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -37,6 +37,8 @@
import androidx.build.testConfiguration.addAppApkToTestConfigGeneration
import androidx.build.testConfiguration.configureTestConfigGeneration
import androidx.build.uptodatedness.TaskUpToDateValidator
+import androidx.build.uptodatedness.cacheEvenIfNoOutputs
+import com.android.build.api.artifact.Artifacts
import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.dsl.KotlinMultiplatformAndroidTarget
import com.android.build.api.dsl.KotlinMultiplatformAndroidTestOnDeviceCompilation
@@ -93,6 +95,7 @@
import org.gradle.build.event.BuildEventsListenerRegistry
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.KotlinClosure1
+import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.extra
@@ -455,7 +458,8 @@
if (!project.name.contains("camera-camera2-pipe")) {
kotlinCompilerArgs += "-Xjvm-default=all"
}
- if (!androidXExtension.targetsJavaConsumers) {
+ if (androidXExtension.type == LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY ||
+ androidXExtension.type == LibraryType.PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY) {
// The Kotlin Compiler adds intrinsic assertions which are only relevant
// when the code is consumed by Java users. Therefore we can turn this off
// when code is being consumed by Kotlin users.
@@ -579,6 +583,43 @@
AndroidMultiplatformApiTaskConfig,
androidXExtension
)
+
+ kotlinMultiplatformAndroidComponentsExtension.onVariant { variant ->
+ project.createVariantAarManifestTransformerTask(variant.name, variant.artifacts)
+ }
+
+ kotlinMultiplatformAndroidComponentsExtension.onVariant {
+ it.configureTests()
+ }
+
+ project.configurePublicResourcesStub(project.multiplatformExtension!!)
+ project.configureMultiplatformSourcesForAndroid { action ->
+ kotlinMultiplatformAndroidComponentsExtension.onVariant {
+ action(it.name)
+ }
+ }
+ project.configureVersionFileWriter(
+ project.multiplatformExtension!!,
+ androidXExtension
+ )
+ project.configureJavaCompilationWarnings(androidXExtension)
+
+ project.configureDependencyVerification(androidXExtension) { taskProvider ->
+ kotlinMultiplatformAndroidTarget.compilations.configureEach {
+ taskProvider.configure { task ->
+ task.dependsOn(it.compileTaskProvider)
+ }
+ }
+ }
+
+ val reportLibraryMetrics = project.configureReportLibraryMetricsTask()
+ project.addToBuildOnServer(reportLibraryMetrics)
+
+ project.addToProjectMap(androidXExtension)
+ project.afterEvaluate {
+ project.addToBuildOnServer("assembleAndroidMain")
+ project.addToBuildOnServer("lint")
+ }
project.setUpCheckDocsTask(androidXExtension)
}
@@ -656,6 +697,13 @@
experimentalProperties.put("android.experimental.art-profile-r8-rewriting", false)
}
+ @Suppress("UnstableApiUsage") // usage of experimentalProperties
+ private fun Variant.aotCompileMicrobenchmarks(project: Project) {
+ if (project.hasBenchmarkPlugin()) {
+ experimentalProperties.put("android.experimental.force-aot-compilation", true)
+ }
+ }
+
private fun HasAndroidTest.configureLicensePackaging() {
androidTest?.packaging?.resources?.apply {
// Workaround a limitation in AGP that fails to merge these META-INF license files.
@@ -699,20 +747,7 @@
// Remove the android:targetSdkVersion element from the manifest used for AARs.
libraryAndroidComponentsExtension.onVariants { variant ->
- project.tasks
- .register(
- variant.name + "AarManifestTransformer",
- AarManifestTransformerTask::class.java
- )
- .let { taskProvider ->
- variant.artifacts
- .use(taskProvider)
- .wiredWithFiles(
- AarManifestTransformerTask::aarFile,
- AarManifestTransformerTask::updatedAarFile
- )
- .toTransform(SingleArtifact.AAR)
- }
+ project.createVariantAarManifestTransformerTask(variant.name, variant.artifacts)
}
project.extensions.getByType<com.android.build.api.dsl.LibraryExtension>().apply {
@@ -726,6 +761,7 @@
onVariants {
it.configureTests()
it.artRewritingWorkaround()
+ it.aotCompileMicrobenchmarks(project)
}
}
@@ -747,11 +783,33 @@
libraryExtension.defaultPublishVariant { libraryVariant ->
reportLibraryMetrics.configure {
it.jarFiles.from(
- libraryVariant.packageLibraryProvider.map { zip -> zip.inputs.files }
+ libraryVariant.packageLibraryProvider.map { zip ->
+ zip.inputs.files
+ }
)
}
}
+ val prebuiltLibraries = listOf("libtracing_perfetto.so", "libc++_shared.so")
+ libraryAndroidComponentsExtension.onVariants { variant ->
+ val verifyELFRegionAlignmentTaskProvider = project.tasks.register(
+ variant.name + "VerifyELFRegionAlignment",
+ VerifyELFRegionAlignmentTask::class.java
+ ) { task ->
+ task.files.from(
+ variant.artifacts.get(SingleArtifact.MERGED_NATIVE_LIBS)
+ .map { dir ->
+ dir.asFileTree.files
+ .filter { it.extension == "so" }
+ .filter { it.path.contains("arm64-v8a") }
+ .filterNot { prebuiltLibraries.contains(it.name) }
+ }
+ )
+ task.cacheEvenIfNoOutputs()
+ }
+ project.addToBuildOnServer(verifyELFRegionAlignmentTaskProvider)
+ }
+
// Standard docs, resource API, and Metalava configuration for AndroidX projects.
project.configureProjectForApiTasks(
LibraryApiTaskConfig(libraryExtension),
@@ -856,6 +914,7 @@
val mavenGroup = androidXExtension.mavenGroup
val isProbablyPublished =
androidXExtension.type == LibraryType.PUBLISHED_LIBRARY ||
+ androidXExtension.type == LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY ||
androidXExtension.type == LibraryType.UNSET
if (mavenGroup != null && isProbablyPublished && androidXExtension.shouldPublish()) {
validateProjectMavenGroup(mavenGroup.group)
@@ -1029,9 +1088,6 @@
}
project.disableStrictVersionConstraints()
project.setPublishProperty(androidXExtension)
- project.afterEvaluate {
- setBenchmarkAdbOptions(project)
- }
}
private fun KotlinMultiplatformAndroidTarget.configureAndroidLibraryOptions(
@@ -1044,21 +1100,6 @@
project.setPublishProperty(androidXExtension)
}
- private fun LibraryExtension.setBenchmarkAdbOptions(project: Project) {
- if (project.hasBenchmarkPlugin()) {
- // Inject AOT compilation - see b/287358254 for context, b/288167775 for AGP support
-
- // NOTE: we assume here that all benchmarks have package name $namespace.test
- val aotCompile = "cmd package compile -m speed -f $namespace.test"
-
- // only run aotCompile on N+, where it's supported
- val inject = "if [ `getprop ro.build.version.sdk` -ge 24 ]; then $aotCompile; fi"
- val options =
- "/data/local/tmp/${project.name}-$testBuildType-androidTest.apk && $inject #"
- adbOptions.setInstallOptions(*options.split(" ").toTypedArray())
- }
- }
-
/**
* Adds a module handler replacement rule that treats full Guava (of any version) as an upgrade
* to ListenableFuture-only Guava. This prevents irreconcilable versioning conflicts and/or
@@ -1292,6 +1333,27 @@
return true
}
+ private fun Project.createVariantAarManifestTransformerTask(
+ variantName: String,
+ artifacts: Artifacts
+ ) {
+ // Remove the android:targetSdkVersion element from the manifest used for AARs.
+ tasks
+ .register(
+ variantName + "AarManifestTransformer",
+ AarManifestTransformerTask::class.java
+ )
+ .let { taskProvider ->
+ artifacts
+ .use(taskProvider)
+ .wiredWithFiles(
+ AarManifestTransformerTask::aarFile,
+ AarManifestTransformerTask::updatedAarFile
+ )
+ .toTransform(SingleArtifact.AAR)
+ }
+ }
+
companion object {
const val CREATE_LIBRARY_BUILD_INFO_FILES_TASK = "createLibraryBuildInfoFiles"
const val GENERATE_TEST_CONFIGURATION_TASK = "GenerateTestConfiguration"
@@ -1553,6 +1615,10 @@
}
}
+internal fun Project.hasAndroidMultiplatformPlugin(): Boolean =
+ extensions.findByType(AndroidXMultiplatformExtension::class.java)?.hasAndroidMultiplatform()
+ ?: false
+
internal fun String.camelCase() = replaceFirstChar {
if (it.isLowerCase()) it.titlecase() else it.toString()
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index 310e5ff..70fbc32 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -36,7 +36,6 @@
import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.Property
-import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.PathSensitive
@@ -79,6 +78,7 @@
.extensions
.getByType(KotlinMultiplatformAndroidTarget::class.java)
}
+
val agpKmpExtension: KotlinMultiplatformAndroidTarget by agpKmpExtensionDelegate
/**
@@ -167,7 +167,15 @@
fun sourceSets(closure: Closure<*>) {
if (kotlinExtensionDelegate.isInitialized()) {
- kotlinExtension.sourceSets.configure(closure)
+ kotlinExtension.sourceSets.configure(closure).also {
+ if (!project.enableMac()) {
+ for (sourceSetName in macOnlySourceSetNames) {
+ kotlinExtension.sourceSets.findByName(sourceSetName)?.let {
+ kotlinExtension.sourceSets.remove(it)
+ }
+ }
+ }
+ }
}
}
@@ -383,7 +391,7 @@
@JvmOverloads
fun androidNativeX86(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
supportedPlatforms.add(PlatformIdentifier.ANDROID_NATIVE_X86)
- return if (project.enableNative()) {
+ return if (project.enableAndroidNative()) {
kotlinExtension.androidNativeX86().also { block?.execute(it) }
} else {
null
@@ -393,7 +401,7 @@
@JvmOverloads
fun androidNativeX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
supportedPlatforms.add(PlatformIdentifier.ANDROID_NATIVE_X64)
- return if (project.enableNative()) {
+ return if (project.enableAndroidNative()) {
kotlinExtension.androidNativeX64().also { block?.execute(it) }
} else {
null
@@ -403,7 +411,7 @@
@JvmOverloads
fun androidNativeArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
supportedPlatforms.add(PlatformIdentifier.ANDROID_NATIVE_ARM64)
- return if (project.enableNative()) {
+ return if (project.enableAndroidNative()) {
kotlinExtension.androidNativeArm64().also { block?.execute(it) }
} else {
null
@@ -413,7 +421,7 @@
@JvmOverloads
fun androidNativeArm32(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
supportedPlatforms.add(PlatformIdentifier.ANDROID_NATIVE_ARM32)
- return if (project.enableNative()) {
+ return if (project.enableAndroidNative()) {
kotlinExtension.androidNativeArm32().also { block?.execute(it) }
} else {
null
@@ -533,18 +541,17 @@
companion object {
const val EXTENSION_NAME = "androidXMultiplatform"
+ private val macOnlySourceSetNames = setOf(
+ "darwinMain",
+ "darwinTest",
+ "iosMain",
+ "iosSimulatorArm64Main",
+ "iosX64Main",
+ "iosArm64Main"
+ )
}
}
-/**
- * Returns a provider that is set to true if and only if this project has at least 1 kotlin native
- * target (mac, linux, ios).
- */
-internal fun Project.hasKotlinNativeTarget(): Provider<Boolean> =
- project.provider {
- project.extensions.getByType(AndroidXMultiplatformExtension::class.java).hasNativeTarget()
- }
-
fun Project.validatePublishedMultiplatformHasDefault() {
val extension = project.extensions.getByType(AndroidXMultiplatformExtension::class.java)
if (extension.defaultPlatform == null && extension.supportedPlatforms.isNotEmpty()) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index d9dd5cd..0e3436e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -128,6 +128,7 @@
registerOwnersServiceTasks()
+ project.configureRootProjectForKmpLink()
// If useMaxDepVersions is set, iterate through all the project and substitute any androidx
// artifact dependency with the local tip of tree version of the library.
if (project.usingMaxDepVersions()) {
@@ -173,7 +174,6 @@
project.zipComposeCompilerMetrics()
project.zipComposeCompilerReports()
- project.configureRootProjectForKmpLink()
}
private fun Project.setDependencyVersions() {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt b/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
index 6774cd8..ca5e6cb 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
@@ -83,6 +83,11 @@
@get:Optional
@get:Input
+ @get:Option(option = "testTimeout", description = "timeout to pass to FTL test runner")
+ abstract val testTimeout: Property<String>
+
+ @get:Optional
+ @get:Input
@get:Option(
option = "instrumentationArgs",
description = "instrumentation arguments to pass to FTL test runner"
@@ -154,6 +159,8 @@
if (shouldPull) {
"/sdcard/Android/data/${apkPackageName.get()}/cache/androidx_screenshots"
} else null,
+ if (testTimeout.isPresent) "--timeout" else null,
+ if (testTimeout.isPresent) testTimeout.get() else null,
if (instrumentationArgs.isPresent) "--environment-variables" else null,
if (instrumentationArgs.isPresent) instrumentationArgs.get() else null,
) +
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt b/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt
index 19a217e..5759c76d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt
@@ -33,6 +33,13 @@
private const val DID_SETUP_KONAN_PROPERTIES_FLAG = "androidx.didSetupKonanProperties"
/**
+ * Flag that causes konan to run in a separate process whose working directory
+ * is the compiling project (i.e. frameworks/support/room/room-runtime) and not the root project
+ * (frameworks/support).
+ */
+ private const val DISABLE_COMPILER_DAEMON_FLAG = "kotlin.native.disableCompilerDaemon"
+
+ /**
* Creates a Konan distribution with the given [prebuiltsDirectory] and [konanHome].
*
* @param prebuiltsDirectory The directory where AndroidX prebuilts are present. Can be `null`
@@ -75,9 +82,12 @@
}
private fun Project.overrideKotlinNativeDependenciesUrlToLocalDirectory() {
+ val compilerDaemonDisabled =
+ findProperty(DISABLE_COMPILER_DAEMON_FLAG)?.toString()?.toBoolean() == true
val konanPrebuiltsFolder = getKonanPrebuiltsFolder()
+ val rootBaseDir = if (compilerDaemonDisabled) projectDir else rootProject.projectDir
// use relative path so it doesn't affect gradle remote cache.
- val relativeRootPath = konanPrebuiltsFolder.relativeTo(rootProject.projectDir).path
+ val relativeRootPath = konanPrebuiltsFolder.relativeTo(rootBaseDir).path
val relativeProjectPath = konanPrebuiltsFolder.relativeTo(projectDir).path
tasks.withType(KotlinNativeCompile::class.java).configureEach {
it.kotlinOptions.freeCompilerArgs +=
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index ad017c9..20e74cf 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -16,9 +16,11 @@
package androidx.build
+import com.android.build.api.dsl.KotlinMultiplatformAndroidTarget
import com.android.build.api.dsl.Lint
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.LibraryPlugin
+import com.android.build.gradle.api.KotlinMultiplatformAndroidPlugin
import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask
import com.android.build.gradle.internal.lint.LintModelWriterTask
import com.android.build.gradle.internal.lint.VariantInputs
@@ -42,6 +44,9 @@
when (plugin) {
is AppPlugin -> configureAndroidProjectForLint(isLibrary = false)
is LibraryPlugin -> configureAndroidProjectForLint(isLibrary = true)
+ is KotlinMultiplatformAndroidPlugin -> configureAndroidMultiplatformProjectForLint(
+ extensions.getByType<AndroidXMultiplatformExtension>().agpKmpExtension
+ )
// Only configure non-multiplatform Java projects via JavaPlugin. Multiplatform
// projects targeting Java (e.g. `jvm { withJava() }`) are configured via
// KotlinBasePlugin.
@@ -56,7 +61,8 @@
if (
project.multiplatformExtension != null &&
!project.plugins.hasPlugin(AppPlugin::class.java) &&
- !project.plugins.hasPlugin(LibraryPlugin::class.java)
+ !project.plugins.hasPlugin(LibraryPlugin::class.java) &&
+ !project.plugins.hasPlugin(KotlinMultiplatformAndroidPlugin::class.java)
) {
configureNonAndroidProjectForLint()
}
@@ -72,6 +78,13 @@
configureLint(extension.lint, isLibrary)
}
+private fun Project.configureAndroidMultiplatformProjectForLint(
+ extension: KotlinMultiplatformAndroidTarget
+) {
+ // The lintAnalyze task is used by `androidx-studio-integration-lint.sh`.
+ tasks.register("lintAnalyze") { task -> task.enabled = false }
+ configureLint(extension.lint, true)
+}
/** Android Lint configuration entry point for non-Android projects. */
private fun Project.configureNonAndroidProjectForLint() = afterEvaluate {
@@ -207,8 +220,9 @@
project.dependencies.add("lintChecks", it)
}
}
-
- afterEvaluate { addSourceSetsForAndroidMultiplatformAfterEvaluate() }
+ if (!project.hasAndroidMultiplatformPlugin()) {
+ afterEvaluate { addSourceSetsForAndroidMultiplatformAfterEvaluate() }
+ }
// The purpose of this specific project is to test that lint is running, so
// it contains expected violations that we do not want to trigger a build failure
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
index 60e6ec0..2d09d33 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
@@ -472,9 +472,11 @@
private val AndroidXExtension.publishPlatforms: List<String>
get() {
val potentialTargets =
- project.multiplatformExtension?.targets?.asMap?.keys?.map { it.lowercase() }
- ?: emptySet()
- val declaredTargets = potentialTargets.filter { it != "metadata" }
+ project.multiplatformExtension?.targets?.asMap?.filterValues {
+ it.publishable
+ }?.keys?.map { it.lowercase() } ?: emptySet()
+ val declaredTargets = potentialTargets
+ .filter { it != "metadata" }
return declaredTargets.toList()
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Samples.kt b/buildSrc/private/src/main/kotlin/androidx/build/Samples.kt
index 71f68a3..b92431c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Samples.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Samples.kt
@@ -80,7 +80,12 @@
it.destinationJar.set(project.layout.buildDirectory.file(srcJarFilename))
}
// this publishing variant is used in non-KMP projects and non-KMP source jars of KMP projects
- val publishingVariants = mutableListOf(sourcesConfigurationName)
+ val publishingVariants = mutableListOf<String>()
+ if (project.hasAndroidMultiplatformPlugin()) {
+ publishingVariants.add(androidMultiplatformSourcesConfigurationName)
+ } else {
+ publishingVariants.add(sourcesConfigurationName)
+ }
project.multiplatformExtension?.let {
publishingVariants += kmpSourcesConfigurationName // used for KMP source jars
if (it.targets.any { it.platformType == KotlinPlatformType.androidJvm })
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
index 4f6ab3b..2841ad1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
@@ -41,7 +41,6 @@
import org.gradle.kotlin.dsl.extra
import org.gradle.kotlin.dsl.named
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
-import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
@@ -74,29 +73,10 @@
}
}
project.afterEvaluate {
- // we can only tell if a project is multiplatform after it is configured
- if (it.multiplatformExtension != null && it.extra.has("publish")) {
- libraryExtension.defaultPublishVariant { variant ->
- val kotlinExt = project.extensions.getByName("kotlin") as KotlinProjectExtension
- val sourceJar =
- project.tasks.named(
- "sourceJar${variant.name.replaceFirstChar {
- if (it.isLowerCase()) {
- it.titlecase(Locale.getDefault())
- } else it.toString()
- }}",
- Jar::class.java
- )
- // multiplatform projects use different source sets, so we need to modify the task
- sourceJar.configure { sourceJarTask ->
- // use an inclusion list of source sets, because that is the preferred policy
- sourceJarTask.from(kotlinExt.sourceSets.getByName("commonMain").kotlin.srcDirs)
- sourceJarTask.from(kotlinExt.sourceSets.getByName("androidMain").kotlin.srcDirs)
- }
- }
+ project.configureMultiplatformSourcesForAndroid { action ->
+ libraryExtension.defaultPublishVariant { action(it.name) }
}
}
-
val disableNames =
setOf(
"releaseSourcesJar",
@@ -104,6 +84,27 @@
disableUnusedSourceJarTasks(disableNames)
}
+fun Project.configureMultiplatformSourcesForAndroid(
+ withVariant: (action: (variantName: String) -> Unit) -> Unit
+) {
+ val mpExtension = multiplatformExtension
+ if (mpExtension != null && extra.has("publish")) {
+ withVariant { variantName ->
+ val sourceJar =
+ project.tasks.named(
+ "sourceJar${variantName.capitalize()}",
+ Jar::class.java
+ )
+ // multiplatform projects use different source sets, so we need to modify the task
+ sourceJar.configure { sourceJarTask ->
+ // use an inclusion list of source sets, because that is the preferred policy
+ sourceJarTask.from(mpExtension.sourceSets.getByName("commonMain").kotlin.srcDirs)
+ sourceJarTask.from(mpExtension.sourceSets.getByName("androidMain").kotlin.srcDirs)
+ }
+ }
+ }
+}
+
/** Sets up a source jar task for a Java library project. */
fun Project.configureSourceJarForJava() {
val sourceJar =
@@ -282,6 +283,7 @@
"project_structure_metadata/$PROJECT_STRUCTURE_METADATA_FILENAME"
internal const val sourcesConfigurationName = "sourcesElements"
+internal const val androidMultiplatformSourcesConfigurationName = "androidSourcesElements"
internal const val kmpSourcesConfigurationName = "androidxSourcesElements"
internal fun String.capitalize() = replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
index eb8d7dd..5755c86 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
@@ -22,12 +22,14 @@
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
+import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.setProperty
/**
@@ -236,5 +238,15 @@
// version.
return false
}
+
+ // Should be guaranteed to be an androidx project at this point, but doesn't necessarily mean
+ // we have AndroidXExtension applied.
+ if (dependency is ProjectDependency &&
+ dependency.dependencyProject.extensions.findByType<AndroidXExtension>()
+ ?.shouldPublish() != true
+ ) {
+ return false
+ }
+
return true
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/VerifyELFRegionAlignmentTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/VerifyELFRegionAlignmentTask.kt
new file mode 100644
index 0000000..a8e3333
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/VerifyELFRegionAlignmentTask.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
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Classpath
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Task for verifying the ELF regions in all shared libs in androidx are aligned to 16Kb boundary
+ */
+
+@CacheableTask
+abstract class VerifyELFRegionAlignmentTask : DefaultTask() {
+ init {
+ group = "Verification"
+ description = "Task for verifying alignment in shared libs"
+ }
+
+ @get:[InputFiles Classpath]
+ abstract val files: ConfigurableFileCollection
+
+ @TaskAction
+ fun verifyELFRegionAlignment() {
+ files.forEach {
+ val alignment = getELFAlignment(it.path)
+ check(alignment == "2**14") {
+ "Expected ELF alignment of 2**14 for file ${it.name}, got $alignment"
+ }
+ }
+ }
+}
+
+private fun getELFAlignment(filePath: String): String? {
+ val alignment = ProcessBuilder("objdump", "-p", filePath)
+ .start()
+ .inputStream
+ .bufferedReader()
+ .useLines { lines ->
+ lines.filter {
+ it.contains("LOAD")
+ }.map {
+ it.split(" ").last()
+ }.firstOrNull()
+ }
+ return alignment
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/VersionFileWriterTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/VersionFileWriterTask.kt
index 867c3ea..115050d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/VersionFileWriterTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/VersionFileWriterTask.kt
@@ -26,7 +26,9 @@
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.TaskProvider
import org.gradle.work.DisableCachingByDefault
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
/** Task that allows to write a version to a given output file. */
@DisableCachingByDefault(because = "Doesn't benefit from caching")
@@ -44,6 +46,9 @@
writer.println(version.get())
writer.close()
}
+ internal companion object {
+ const val TASK_NAME = "writeVersionFile"
+ }
}
/**
@@ -55,27 +60,14 @@
libraryAndroidComponentsExtension: LibraryAndroidComponentsExtension,
androidXExtension: AndroidXExtension
) {
- val writeVersionFile = tasks.register("writeVersionFile", VersionFileWriterTask::class.java)
+ val writeVersionFile = tasks.register(
+ VersionFileWriterTask.TASK_NAME,
+ VersionFileWriterTask::class.java
+ )
afterEvaluate {
- writeVersionFile.configure {
- val group = findProperty("group") as String
- val artifactId = findProperty("name") as String
- val version =
- if (androidXExtension.shouldPublish()) {
- version().toString()
- } else {
- "0.0.0"
- }
-
- it.version.set(version)
- it.relativePath.set(String.format("META-INF/%s_%s.version", group, artifactId))
-
- // We only add version file if is a library that is publishing.
- it.enabled = androidXExtension.shouldPublish()
- }
+ configureVersionFile(writeVersionFile, androidXExtension)
}
-
libraryAndroidComponentsExtension.onVariants {
it.sources.resources!!.addGeneratedSourceDirectory(
writeVersionFile,
@@ -83,3 +75,49 @@
)
}
}
+
+fun Project.configureVersionFileWriter(
+ kmpExtension: KotlinMultiplatformExtension,
+ androidXExtension: AndroidXExtension
+) {
+ val writeVersionFile = tasks.register(
+ VersionFileWriterTask.TASK_NAME,
+ VersionFileWriterTask::class.java
+ )
+ writeVersionFile.configure {
+ it.outputDir.set(layout.buildDirectory.dir("generatedVersionFile"))
+ }
+ val sourceSet = kmpExtension.sourceSets.getByName("androidMain")
+ val resources = sourceSet.resources
+ val includes = resources.includes
+ resources.srcDir(writeVersionFile.map { it.outputDir })
+ if (includes.isNotEmpty()) {
+ includes.add("META-INF/*.version")
+ resources.setIncludes(includes)
+ }
+ afterEvaluate {
+ configureVersionFile(writeVersionFile, androidXExtension)
+ }
+}
+
+private fun Project.configureVersionFile(
+ writeVersionFile: TaskProvider<VersionFileWriterTask>,
+ androidXExtension: AndroidXExtension
+) {
+ writeVersionFile.configure {
+ val group = findProperty("group") as String
+ val artifactId = findProperty("name") as String
+ val version =
+ if (androidXExtension.shouldPublish()) {
+ version().toString()
+ } else {
+ "0.0.0"
+ }
+
+ it.version.set(version)
+ it.relativePath.set(String.format("META-INF/%s_%s.version", group, artifactId))
+
+ // We only add version file if is a library that is publishing.
+ it.enabled = androidXExtension.shouldPublish()
+ }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/clang/MultiTargetNativeCompilation.kt b/buildSrc/private/src/main/kotlin/androidx/build/clang/MultiTargetNativeCompilation.kt
index 2a72d2f..a81736c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/clang/MultiTargetNativeCompilation.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/clang/MultiTargetNativeCompilation.kt
@@ -108,7 +108,10 @@
val nativeTarget = if (nativeTargets.names.contains(konanTarget.name)) {
nativeTargets.named(konanTarget.name)
} else {
- nativeTargets.register(konanTarget.name)
+ nativeTargets.register(konanTarget.name).also {
+ // force evaluation of target so that tasks are registered b/325518502
+ nativeTargets.getByName(konanTarget.name)
+ }
}
if (action != null) {
nativeTarget.configure(action)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
index 32d2d2a..e2d62fc 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
@@ -290,6 +290,15 @@
"play" to "https://developer.android.com/reference/",
// From developer.android.com/reference/com/google/android/material/package-list
"material" to "https://developer.android.com/reference",
+ "okhttp3" to "https://square.github.io/okhttp/5.x/",
+ "truth" to "https://truth.dev/api/0.41/",
+ // From developer.android.com/reference/android/support/wearable/package-list
+ "wearable" to "https://developer.android.com/reference/",
+ // Filtered to just java.awt and javax packages (base java packages are included in
+ // the android package-list)
+ "javase8" to "https://docs.oracle.com/javase/8/docs/api/",
+ "javaee7" to "https://docs.oracle.com/javaee%2F7%2Fapi%2F%2F",
+ "findbugs" to "https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/latest/",
// All package-lists below were created manually
"mlkit" to "https://developers.google.com/android/reference/",
"dagger" to "https://dagger.dev/api/latest/",
@@ -299,6 +308,15 @@
"https://javadoc.io/doc/org.jetbrains/annotations/latest/",
"auto-value" to
"https://www.javadoc.io/doc/com.google.auto.value/auto-value/latest/",
+ "robolectric" to "https://robolectric.org/javadoc/4.11/",
+ "interactive-media" to
+ "https://developers.google.com/interactive-media-ads/docs/sdks/android/" +
+ "client-side/api/reference/com/google/ads/interactivemedia/v3",
+ "errorprone" to "https://errorprone.info/api/latest/",
+ "gms" to "https://developers.google.com/android/reference",
+ "checkerframework" to "https://checkerframework.org/api/",
+ "chromium" to
+ "https://developer.android.com/develop/connectivity/cronet/reference/",
)
}
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index 3c98b3e..3e8faa3 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -49,8 +49,6 @@
args +
listOf(
"--hide",
- "HiddenSuperclass", // We allow having a hidden parent class
- "--hide",
// Removing final from a method does not cause compatibility issues for AndroidX.
"RemovedFinalStrict",
"--error",
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
index e7b4444..3ef68f2 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
@@ -17,6 +17,7 @@
package androidx.build.metalava
import androidx.build.AndroidXExtension
+import androidx.build.LibraryType
import androidx.build.addFilterableTasks
import androidx.build.addToBuildOnServer
import androidx.build.addToCheckTask
@@ -51,6 +52,9 @@
// implemented by excluding APIs with this annotation from the restricted API file.
val generateRestrictToLibraryGroupAPIs = !extension.mavenGroup!!.requireSameVersion
val kotlinSourceLevel: Provider<KotlinVersion> = extension.kotlinApiVersion
+ val targetsJavaConsumers = (extension.type != LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY &&
+ extension.type != LibraryType.PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY
+ )
val generateApi =
project.tasks.register("generateApi", GenerateApiTask::class.java) { task ->
task.group = "API"
@@ -59,7 +63,7 @@
task.metalavaClasspath.from(metalavaClasspath)
task.generateRestrictToLibraryGroupAPIs = generateRestrictToLibraryGroupAPIs
task.baselines.set(baselinesApiLocation)
- task.targetsJavaConsumers = extension.targetsJavaConsumers
+ task.targetsJavaConsumers = targetsJavaConsumers
task.k2UastEnabled.set(extension.metalavaK2UastEnabled)
task.kotlinSourceLevel.set(kotlinSourceLevel)
@@ -120,7 +124,7 @@
) { task ->
task.metalavaClasspath.from(metalavaClasspath)
task.baselines.set(baselinesApiLocation)
- task.targetsJavaConsumers.set(extension.targetsJavaConsumers)
+ task.targetsJavaConsumers.set(targetsJavaConsumers)
task.k2UastEnabled.set(extension.metalavaK2UastEnabled)
task.kotlinSourceLevel.set(kotlinSourceLevel)
processManifest?.let { task.manifestPath.set(processManifest.manifestOutputFile) }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/resources/PublicResourcesStubHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/resources/PublicResourcesStubHelper.kt
index 3138064..5a65827 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/resources/PublicResourcesStubHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/resources/PublicResourcesStubHelper.kt
@@ -17,10 +17,13 @@
package androidx.build.resources
import androidx.build.getSupportRootFolder
+import com.android.build.api.dsl.KotlinMultiplatformAndroidTarget
import com.android.build.gradle.LibraryExtension
import java.io.File
import org.gradle.api.Project
import org.gradle.api.tasks.Copy
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
fun Project.configurePublicResourcesStub(libraryExtension: LibraryExtension) {
val targetRes = project.layout.buildDirectory.dir("generated/res/public-stub")
@@ -37,3 +40,24 @@
)
}
}
+
+fun Project.configurePublicResourcesStub(kmpExtension: KotlinMultiplatformExtension) {
+ val targetRes = project.layout.buildDirectory.dir("generated/res/public-stub")
+
+ val generatePublicResourcesStub = tasks.register(
+ "generatePublicResourcesStub",
+ Copy::class.java
+ ) { task ->
+ task.from(File(project.getSupportRootFolder(), "buildSrc/res"))
+ task.into(targetRes)
+ }
+ val sourceSet = kmpExtension
+ .targets
+ .withType(KotlinMultiplatformAndroidTarget::class.java)
+ .single()
+ .compilations
+ .getByName(KotlinCompilation.MAIN_COMPILATION_NAME).defaultSourceSet
+ sourceSet.resources.srcDir(
+ generatePublicResourcesStub.flatMap { project.provider { it.destinationDir } }
+ )
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt b/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
index 83186c0..a06869c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
@@ -83,7 +83,10 @@
"bundleDebugLocalLintAar",
"bundleReleaseLocalLintAar",
"bundleDebugAar",
- "bundleReleaseAar"
+ "bundleReleaseAar",
+ "bundleAndroidMainAar",
+ "bundleAndroidMainLocalLintAar",
+ "repackageAndroidMainAar",
)
/**
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/KmpPlatforms.kt b/buildSrc/public/src/main/kotlin/androidx/build/KmpPlatforms.kt
index 5d22a95..ab190af 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/KmpPlatforms.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/KmpPlatforms.kt
@@ -20,6 +20,7 @@
import org.gradle.api.Project
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.findByType
+import org.jetbrains.kotlin.konan.target.HostManager
/**
* A comma-separated list of target platform groups you wish to enable or disable.
@@ -48,7 +49,7 @@
* Do *not* enable [JS] unless you have read and understand this:
* https://blog.jetbrains.com/kotlin/2021/10/important-ua-parser-js-exploit-and-kotlin-js/
*/
- val enabledByDefault = listOf(JVM, DESKTOP)
+ val enabledByDefault = listOf(JVM, DESKTOP, MAC, LINUX, ANDROID_NATIVE)
}
}
@@ -112,14 +113,6 @@
return extension.enabledKmpPlatforms
}
-/** Returns true if kotlin native targets should be enabled. */
-private fun Project.isKotlinNativeEnabled(): Boolean {
- return "KMP".equals(System.getenv()["ANDROIDX_PROJECTS"], ignoreCase = true) ||
- "INFRAROGUE".equals(System.getenv()["ANDROIDX_PROJECTS"], ignoreCase = true) ||
- ProjectLayoutType.isPlayground(project) ||
- project.providers.gradleProperty("androidx.kmp.native.enabled").orNull?.toBoolean() == true
-}
-
/** Extension used to store parsed KMP configuration information. */
private open class KmpPlatformsExtension(project: Project) {
val enabledKmpPlatforms =
@@ -128,14 +121,14 @@
fun Project.enableJs(): Boolean = enabledKmpPlatforms.contains(PlatformGroup.JS)
-fun Project.enableMac(): Boolean =
- enabledKmpPlatforms.contains(PlatformGroup.MAC) || isKotlinNativeEnabled()
+fun Project.enableAndroidNative(): Boolean =
+ enabledKmpPlatforms.contains(PlatformGroup.ANDROID_NATIVE)
-fun Project.enableLinux(): Boolean =
- enabledKmpPlatforms.contains(PlatformGroup.LINUX) || isKotlinNativeEnabled()
+fun Project.enableMac(): Boolean =
+ enabledKmpPlatforms.contains(PlatformGroup.MAC) && HostManager.hostIsMac
+
+fun Project.enableLinux(): Boolean = enabledKmpPlatforms.contains(PlatformGroup.LINUX)
fun Project.enableJvm(): Boolean = enabledKmpPlatforms.contains(PlatformGroup.JVM)
fun Project.enableDesktop(): Boolean = enabledKmpPlatforms.contains(PlatformGroup.DESKTOP)
-
-fun Project.enableNative(): Boolean = enableMac() && enableLinux()
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt b/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
index ebf4f53..2ed87dc 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
@@ -72,7 +72,9 @@
companion object {
val PUBLISHED_LIBRARY = PublishedLibrary()
+ val PUBLISHED_KOTLIN_ONLY_LIBRARY = PublishedKotlinOnlyLibrary()
val PUBLISHED_TEST_LIBRARY = PublishedTestLibrary()
+ val PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY = PublishedKotlinOnlyTestLibrary()
val INTERNAL_TEST_LIBRARY = InternalTestLibrary()
val INTERNAL_HOST_TEST_LIBRARY = InternalHostTestLibrary()
val SAMPLES = Samples()
@@ -93,7 +95,9 @@
private val allTypes =
mapOf(
"PUBLISHED_LIBRARY" to PUBLISHED_LIBRARY,
+ "PUBLISHED_KOTLIN_ONLY_LIBRARY" to PUBLISHED_KOTLIN_ONLY_LIBRARY,
"PUBLISHED_TEST_LIBRARY" to PUBLISHED_TEST_LIBRARY,
+ "PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY" to PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY,
"INTERNAL_TEST_LIBRARY" to INTERNAL_TEST_LIBRARY,
"INTERNAL_HOST_TEST_LIBRARY" to INTERNAL_HOST_TEST_LIBRARY,
"SAMPLES" to SAMPLES,
@@ -125,6 +129,14 @@
allowCallingVisibleForTestsApis = allowCallingVisibleForTestsApis
)
+ open class PublishedKotlinOnlyLibrary(allowCallingVisibleForTestsApis: Boolean = false) :
+ LibraryType(
+ publish = Publish.SNAPSHOT_AND_RELEASE,
+ sourceJars = true,
+ checkApi = RunApiTasks.Yes(),
+ allowCallingVisibleForTestsApis = allowCallingVisibleForTestsApis
+ )
+
open class InternalLibrary(
compilationTarget: CompilationTarget = CompilationTarget.DEVICE,
allowCallingVisibleForTestsApis: Boolean = false
@@ -137,6 +149,9 @@
class PublishedTestLibrary() : PublishedLibrary(allowCallingVisibleForTestsApis = true)
+ class PublishedKotlinOnlyTestLibrary() : PublishedKotlinOnlyLibrary(
+ allowCallingVisibleForTestsApis = true)
+
class InternalTestLibrary() : InternalLibrary(allowCallingVisibleForTestsApis = true)
class InternalHostTestLibrary() : InternalLibrary(CompilationTarget.HOST)
diff --git a/buildSrc/shared-dependencies.gradle b/buildSrc/shared-dependencies.gradle
index 5fee377..bd16885 100644
--- a/buildSrc/shared-dependencies.gradle
+++ b/buildSrc/shared-dependencies.gradle
@@ -25,9 +25,8 @@
// Force jsoup upgrade on spdx (b/309773103)
implementation(libs.jsoup)
- // variety of json parsers
+ // json parser
implementation(libs.gson)
- implementation(libs.jsonSimple)
// XML parsers used in MavenUploadHelper.kt
implementation(libs.dom4j) {
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt
index df0e65b..0d94938 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt
@@ -31,6 +31,8 @@
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.TimeUnit
+import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -65,6 +67,11 @@
)
}
+ @After
+ fun tearDown() {
+ CameraXUtil.shutdown()[10000, TimeUnit.MILLISECONDS]
+ }
+
@Test
fun canGetOutputFormats() {
val formats = streamConfigurationMapCompat.getOutputFormats()!!.toList()
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
index da840e51..19a9552 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
@@ -50,6 +50,7 @@
import androidx.camera.camera2.pipe.integration.impl.UseCaseSurfaceManager
import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
import androidx.camera.camera2.pipe.integration.impl.toMap
+import androidx.camera.core.ImageCapture
import androidx.camera.core.UseCase
import androidx.camera.core.impl.CaptureConfig
import androidx.camera.core.impl.Config
@@ -122,7 +123,7 @@
sessionConfigOptions: Config,
captureMode: Int,
flashType: Int,
- flashMode: Int
+ @ImageCapture.FlashMode flashMode: Int
): List<Deferred<Void?>> {
throw NotImplementedError("Not implemented")
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
index c0f375e..2611c7e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
@@ -38,6 +38,7 @@
import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
import androidx.camera.core.FocusMeteringAction
import androidx.camera.core.FocusMeteringResult
+import androidx.camera.core.ImageCapture
import androidx.camera.core.impl.CameraControlInternal
import androidx.camera.core.impl.CaptureConfig
import androidx.camera.core.impl.Config
@@ -124,7 +125,7 @@
return flashControl.flashMode
}
- override fun setFlashMode(flashMode: Int) {
+ override fun setFlashMode(@ImageCapture.FlashMode flashMode: Int) {
flashControl.setFlashAsync(flashMode)
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.kt
index 0c22433..2ec7ae5 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.kt
@@ -51,8 +51,9 @@
if (supportExtraFullConfigurationsSamsungDevice()) {
return getLimitedDeviceExtraSupportedFullConfigurations(hardwareLevel)
}
- return if (supportExtraLevel3ConfigurationsGoogleDevice()) {
- listOf(LEVEL_3_LEVEL_PRIV_PRIV_YUV_RAW_CONFIGURATION)
+ return if (supportExtraLevel3ConfigurationsGoogleDevice() ||
+ supportExtraLevel3ConfigurationsSamsungDevice()) {
+ listOf(LEVEL_3_LEVEL_PRIV_PRIV_YUV_SUBSET_CONFIGURATION)
} else emptyList()
}
@@ -82,8 +83,8 @@
private const val TAG = "ExtraSupportedSurfaceCombinationsQuirk"
private val FULL_LEVEL_YUV_PRIV_YUV_CONFIGURATION = createFullYuvPrivYuvConfiguration()
private val FULL_LEVEL_YUV_YUV_YUV_CONFIGURATION = createFullYuvYuvYuvConfiguration()
- private val LEVEL_3_LEVEL_PRIV_PRIV_YUV_RAW_CONFIGURATION =
- createLevel3PrivPrivYuvRawConfiguration()
+ private val LEVEL_3_LEVEL_PRIV_PRIV_YUV_SUBSET_CONFIGURATION =
+ createLevel3PrivPrivYuvSubsetConfiguration()
private val SUPPORT_EXTRA_FULL_CONFIGURATIONS_SAMSUNG_MODELS: Set<String> =
setOf(
"SM-A515F", // Galaxy A51
@@ -242,9 +243,16 @@
"PIXEL 7 PRO"
)
+ private val SUPPORT_EXTRA_LEVEL_3_CONFIGURATIONS_SAMSUNG_MODELS: Set<String> =
+ setOf(
+ "SM-S926B", // Galaxy S24+
+ "SM-S928U" // Galaxy S24 Ultra
+ )
+
fun isEnabled(): Boolean {
return (isSamsungS7 || supportExtraFullConfigurationsSamsungDevice() ||
- supportExtraLevel3ConfigurationsGoogleDevice())
+ supportExtraLevel3ConfigurationsGoogleDevice() ||
+ supportExtraLevel3ConfigurationsSamsungDevice())
}
internal val isSamsungS7: Boolean
@@ -271,6 +279,16 @@
return SUPPORT_EXTRA_LEVEL_3_CONFIGURATIONS_GOOGLE_MODELS.contains(capitalModelName)
}
+ internal fun supportExtraLevel3ConfigurationsSamsungDevice(): Boolean {
+ if (!"samsung".equals(Build.BRAND, ignoreCase = true)) {
+ return false;
+ }
+
+ val capitalModelName = Build.MODEL.uppercase();
+
+ return SUPPORT_EXTRA_LEVEL_3_CONFIGURATIONS_SAMSUNG_MODELS.contains(capitalModelName);
+ }
+
internal fun createFullYuvPrivYuvConfiguration(): SurfaceCombination {
// (YUV, ANALYSIS) + (PRIV, PREVIEW) + (YUV, MAXIMUM)
val surfaceCombination = SurfaceCombination()
@@ -319,8 +337,16 @@
return surfaceCombination
}
- internal fun createLevel3PrivPrivYuvRawConfiguration(): SurfaceCombination {
- // (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) + (RAW, MAXIMUM)
+ /**
+ * Creates (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) surface combination.
+ *
+ * This is a subset of LEVEL_3 camera devices'
+ * (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) + (RAW, MAXIMUM)
+ * guaranteed supported configuration. This configuration has been verified to make sure
+ * that the surface combination can work well on the target devices.
+ */
+ internal fun createLevel3PrivPrivYuvSubsetConfiguration(): SurfaceCombination {
+ // (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM)
val surfaceCombination = SurfaceCombination()
surfaceCombination.addSurfaceConfig(
SurfaceConfig.create(
@@ -340,12 +366,6 @@
SurfaceConfig.ConfigSize.MAXIMUM
)
)
- surfaceCombination.addSurfaceConfig(
- SurfaceConfig.create(
- SurfaceConfig.ConfigType.RAW,
- SurfaceConfig.ConfigSize.MAXIMUM
- )
- )
return surfaceCombination
}
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/CapturePipelineTorchCorrection.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/CapturePipelineTorchCorrection.kt
index 920ef96..83fcd35 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/CapturePipelineTorchCorrection.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/CapturePipelineTorchCorrection.kt
@@ -32,6 +32,7 @@
import androidx.camera.camera2.pipe.integration.impl.CapturePipelineImpl
import androidx.camera.camera2.pipe.integration.impl.TorchControl
import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
+import androidx.camera.core.ImageCapture
import androidx.camera.core.TorchState
import androidx.camera.core.impl.CaptureConfig
import androidx.camera.core.impl.Config
@@ -64,7 +65,7 @@
sessionConfigOptions: Config,
captureMode: Int,
flashType: Int,
- flashMode: Int
+ @ImageCapture.FlashMode flashMode: Int
): List<Deferred<Void?>> {
val needCorrectTorchState = isCorrectionRequired(configs, requestTemplate)
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 266f8d7..1a59c0a 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
@@ -62,6 +62,7 @@
import androidx.camera.core.ImageCapture.FLASH_MODE_AUTO
import androidx.camera.core.ImageCapture.FLASH_MODE_OFF
import androidx.camera.core.ImageCapture.FLASH_MODE_ON
+import androidx.camera.core.ImageCapture.FLASH_MODE_SCREEN
import androidx.camera.core.ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH
import androidx.camera.core.ImageCapture.FlashMode
import androidx.camera.core.ImageCapture.FlashType
@@ -93,7 +94,7 @@
sessionConfigOptions: Config,
captureMode: Int,
flashType: Int,
- flashMode: Int,
+ @FlashMode flashMode: Int,
): List<Deferred<Void?>>
}
@@ -125,7 +126,7 @@
sessionConfigOptions: Config,
captureMode: Int,
flashType: Int,
- flashMode: Int,
+ @FlashMode flashMode: Int,
): List<Deferred<Void?>> = if (isTorchAsFlash(flashType)) {
torchAsFlashCapture(configs, requestTemplate, sessionConfigOptions, captureMode, flashMode)
} else {
@@ -433,7 +434,8 @@
)
)
for (captureCallback in it.cameraCaptureCallbacks) {
- captureCallback.onCaptureFailed(it.id,
+ captureCallback.onCaptureFailed(
+ it.id,
CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR)
)
}
@@ -442,7 +444,8 @@
override fun onCaptureSequenceCompleted(captureSequenceId: Int) {
completeSignal.complete(null)
for (captureCallback in it.cameraCaptureCallbacks) {
- captureCallback.onCaptureCompleted(it.id,
+ captureCallback.onCaptureCompleted(
+ it.id,
CameraCaptureResult.EmptyCameraCaptureResult()
)
}
@@ -479,6 +482,9 @@
}
FLASH_MODE_OFF -> false
+
+ // TODO: b/325899701 - Turn it on once screen flash is supported.
+ FLASH_MODE_SCREEN -> false
else -> throw AssertionError(flashMode)
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
index e053c73..2ae38fc 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
@@ -59,7 +59,10 @@
private var _updateSignal: CompletableDeferred<Unit>? = null
@Volatile
+ @ImageCapture.FlashMode
private var _flashMode: Int = DEFAULT_FLASH_MODE
+
+ @ImageCapture.FlashMode
var flashMode: Int = _flashMode
get() = _flashMode
private set
@@ -72,7 +75,10 @@
}
private set
- fun setFlashAsync(flashMode: Int, cancelPreviousTask: Boolean = true): Deferred<Unit> {
+ fun setFlashAsync(
+ @ImageCapture.FlashMode flashMode: Int,
+ cancelPreviousTask: Boolean = true
+ ): Deferred<Unit> {
val signal = CompletableDeferred<Unit>()
useCaseCamera?.let {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
index 9e7daeb..04cffc6 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
@@ -130,8 +130,9 @@
}
val postviewOutputConfig =
sessionConfigAdapter.getValidSessionConfigOrNull()?.postviewOutputConfig
+ var postviewDeferrableSurface: DeferrableSurface? = null
if (postviewOutputConfig != null) {
- val postviewDeferrableSurface = postviewOutputConfig.surface
+ postviewDeferrableSurface = postviewOutputConfig.surface
postviewOutputSurface = createOutputSurface(
postviewDeferrableSurface,
postviewDeferrableSurface.surface.get()!!
@@ -147,6 +148,7 @@
try {
DeferrableSurfaces.incrementAll(deferrableSurfaces)
+ postviewDeferrableSurface?.incrementUseCount()
} catch (exception: DeferrableSurface.SurfaceClosedException) {
sessionConfigAdapter.reportSurfaceInvalid(exception.deferrableSurface)
return@launch
@@ -164,12 +166,14 @@
} catch (throwable: Throwable) {
Log.error(throwable) { "initSession() failed" }
DeferrableSurfaces.decrementAll(deferrableSurfaces)
+ postviewDeferrableSurface?.decrementUseCount()
throw throwable
}
// DecrementAll the output surfaces when ProcessorSurface terminates.
processorSessionConfig.surfaces.first().terminationFuture.addListener({
DeferrableSurfaces.decrementAll(deferrableSurfaces)
+ postviewDeferrableSurface?.decrementUseCount()
}, CameraXExecutors.directExecutor())
val processorSessionConfigAdapter =
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
index abe5af1..b263eb6 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
@@ -150,7 +150,7 @@
captureSequence: List<CaptureConfig>,
captureMode: Int,
flashType: Int,
- flashMode: Int,
+ @ImageCapture.FlashMode flashMode: Int,
): List<Deferred<Void?>>
fun close()
@@ -277,7 +277,7 @@
captureSequence: List<CaptureConfig>,
captureMode: Int,
flashType: Int,
- flashMode: Int,
+ @ImageCapture.FlashMode flashMode: Int,
) = runIfNotClosed {
if (captureSequence.hasInvalidSurface()) {
failedResults(
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirkTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirkTest.kt
index cb3d569..b010729 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirkTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedSurfaceCombinationsQuirkTest.kt
@@ -135,7 +135,7 @@
"Pixel 6",
"0",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration()
+ createLevel3PrivPrivYuvSubsetConfiguration()
),
Config(
"Google",
@@ -143,7 +143,7 @@
"Pixel 6",
"1",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration()
+ createLevel3PrivPrivYuvSubsetConfiguration()
),
Config(
"Google",
@@ -151,7 +151,7 @@
"Pixel 6 Pro",
"0",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration()
+ createLevel3PrivPrivYuvSubsetConfiguration()
),
Config(
"Google",
@@ -159,7 +159,7 @@
"Pixel 6 Pro",
"1",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration()
+ createLevel3PrivPrivYuvSubsetConfiguration()
),
Config(
"Google",
@@ -167,7 +167,7 @@
"Pixel 7",
"0",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration()
+ createLevel3PrivPrivYuvSubsetConfiguration()
),
Config(
"Google",
@@ -175,7 +175,7 @@
"Pixel 6 Pro",
"1",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration()
+ createLevel3PrivPrivYuvSubsetConfiguration()
),
Config(
"Google",
@@ -183,7 +183,7 @@
"Pixel 7 Pro",
"0",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration()
+ createLevel3PrivPrivYuvSubsetConfiguration()
),
Config(
"Google",
@@ -191,7 +191,40 @@
"Pixel 7 Pro",
"1",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration()
+ createLevel3PrivPrivYuvSubsetConfiguration()
+ ),
+ // Tests for FULL Samsung devices
+ Config(
+ "Samsung",
+ null,
+ "SM-S926B",
+ "0",
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ createLevel3PrivPrivYuvSubsetConfiguration()
+ ),
+ Config(
+ "Samsung",
+ null,
+ "SM-S926B",
+ "1",
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ createLevel3PrivPrivYuvSubsetConfiguration()
+ ),
+ Config(
+ "Samsung",
+ null,
+ "SM-S928U",
+ "0",
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ createLevel3PrivPrivYuvSubsetConfiguration()
+ ),
+ Config(
+ "Samsung",
+ null,
+ "SM-S928U",
+ "1",
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ createLevel3PrivPrivYuvSubsetConfiguration()
),
// Other cases
Config(
@@ -272,8 +305,8 @@
return arrayOf(surfaceCombination1, surfaceCombination2)
}
- private fun createLevel3PrivPrivYuvRawConfiguration(): Array<SurfaceCombination> {
- // (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) + (RAW, MAXIMUM)
+ private fun createLevel3PrivPrivYuvSubsetConfiguration(): Array<SurfaceCombination> {
+ // (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM)
val surfaceCombination = SurfaceCombination()
surfaceCombination.addSurfaceConfig(
SurfaceConfig.create(
@@ -293,12 +326,6 @@
SurfaceConfig.ConfigSize.MAXIMUM
)
)
- surfaceCombination.addSurfaceConfig(
- SurfaceConfig.create(
- SurfaceConfig.ConfigType.RAW,
- SurfaceConfig.ConfigSize.MAXIMUM
- )
- )
return arrayOf(surfaceCombination)
}
}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCapturePipeline.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCapturePipeline.kt
index 135683e..702108a 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCapturePipeline.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCapturePipeline.kt
@@ -19,6 +19,7 @@
import android.hardware.camera2.CameraDevice
import androidx.camera.camera2.pipe.RequestTemplate
import androidx.camera.camera2.pipe.integration.impl.CapturePipeline
+import androidx.camera.core.ImageCapture
import androidx.camera.core.impl.CaptureConfig
import androidx.camera.core.impl.Config
import kotlinx.coroutines.CompletableDeferred
@@ -34,7 +35,7 @@
sessionConfigOptions: Config,
captureMode: Int,
flashType: Int,
- flashMode: Int
+ @ImageCapture.FlashMode flashMode: Int
): List<Deferred<Void?>> {
return configs.map {
CompletableDeferred<Void?>(null).apply { complete(null) }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
index bb21a18..056cff48 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
@@ -36,6 +36,7 @@
import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraRequestControl
+import androidx.camera.core.ImageCapture
import androidx.camera.core.UseCase
import androidx.camera.core.impl.CaptureConfig
import androidx.camera.core.impl.Config
@@ -164,7 +165,7 @@
captureSequence: List<CaptureConfig>,
captureMode: Int,
flashType: Int,
- flashMode: Int,
+ @ImageCapture.FlashMode flashMode: Int,
): List<Deferred<Void?>> {
return captureSequence.map {
CompletableDeferred<Void?>(null).apply { complete(null) }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
index 671d198..ad8e9e1 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
@@ -162,6 +162,11 @@
checkNotNull(imageWriter) {
"Failed to create ImageWriter for capture session: $session"
}
+
+ Log.debug {
+ "Queuing image ${request.inputRequest.image} for reprocessing " +
+ "to ImageWriter $imageWriter"
+ }
// TODO(b/321603591): Queue image closer to when capture request is submitted
imageWriter.queueInputImage(request.inputRequest.image)
@@ -339,13 +344,15 @@
checkNotNull(sessionInputSurface) {
"inputSurface is required to create instance of imageWriter."
}
- AndroidImageWriter.create(
+ val androidImageWriter = AndroidImageWriter.create(
sessionInputSurface,
inputStream.id,
inputStream.maxImages,
inputStream.format,
threads.camera2Handler
)
+ Log.debug { "Created ImageWriter $androidImageWriter for session $session" }
+ androidImageWriter
} else {
null
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
index 8cf4b52..1929570 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
@@ -45,6 +45,7 @@
val virtualCamera: VirtualCameraState,
val sharedCameraIds: List<CameraId>,
val graphListener: GraphListener,
+ val isPrewarm: Boolean,
val isForegroundObserver: (Unit) -> Boolean,
) : CameraRequest()
@@ -100,10 +101,19 @@
cameraId: CameraId,
sharedCameraIds: List<CameraId>,
graphListener: GraphListener,
+ isPrewarm: Boolean = false,
isForegroundObserver: (Unit) -> Boolean,
): VirtualCamera? {
val result = VirtualCameraState(cameraId, graphListener, threads.globalScope)
- if (!offerChecked(RequestOpen(result, sharedCameraIds, graphListener, isForegroundObserver))
+ if (!offerChecked(
+ RequestOpen(
+ result,
+ sharedCameraIds,
+ graphListener,
+ isPrewarm,
+ isForegroundObserver
+ )
+ )
) {
Log.error { "Camera open request failed: VirtualCameraManager queue size exceeded" }
graphListener.onGraphError(
@@ -117,9 +127,11 @@
return result
}
- /** Connects and starts the underlying camera.*/
+ /** Connects and starts the underlying camera. Once the, ActiveCamera, timeout elapses and
+ * it hasn't been utilized, the camera is closed.
+ */
internal fun prewarm(cameraId: CameraId) {
- open(cameraId, emptyList(), NoOpGraphListener) { _ -> false }
+ open(cameraId, emptyList(), NoOpGraphListener, isPrewarm = true) { _ -> false }
}
/** Submits a request to close the underlying camera */
@@ -207,6 +219,11 @@
// B) That request was NOT a Close, or CloseAll request
val request = requests[0]
check(request is RequestOpen)
+ if (request.isPrewarm) {
+ check(request.sharedCameraIds.isEmpty()) {
+ "Prewarming concurrent cameras is not supported"
+ }
+ }
// Sanity Check: If the camera we are attempting to open is now closed or disconnected,
// skip this virtual camera request.
@@ -281,6 +298,7 @@
}) {
// If the camera of the request and the cameras it is shared with have been
// opened, we can connect the ActiveCameras.
+ check(!request.isPrewarm)
realCamera.connectTo(request.virtualCamera)
connectPendingRequestOpens(request.sharedCameraIds)
} else {
@@ -289,7 +307,9 @@
pendingRequestOpens.add(request)
}
} else {
- realCamera.connectTo(request.virtualCamera)
+ if (!request.isPrewarm) {
+ realCamera.connectTo(request.virtualCamera)
+ }
}
requests.remove(request)
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
index e50c84c..bf120f1 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
@@ -174,6 +174,16 @@
append("\n")
}
}
+ if (cameraGraph.streams.inputs.isNotEmpty()) {
+ append("Inputs:\n")
+ for (stream in cameraGraph.streams.inputs) {
+ append(" ")
+ append(stream.id.toString().padEnd(12, ' '))
+ append(stream.format.toString().padEnd(12, ' '))
+ append(stream.maxImages.toString().padEnd(12, ' '))
+ append("\n")
+ }
+ }
append("Session Template: ${graphConfig.sessionTemplate.name}\n")
appendParameters(this, "Session Parameters", graphConfig.sessionParameters)
diff --git a/camera/camera-camera2/api/1.4.0-beta01.txt b/camera/camera-camera2/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..87c79d0
--- /dev/null
+++ b/camera/camera-camera2/api/1.4.0-beta01.txt
@@ -0,0 +1,54 @@
+// Signature format: 4.0
+package androidx.camera.camera2 {
+
+ @RequiresApi(21) public final class Camera2Config {
+ method public static androidx.camera.core.CameraXConfig defaultConfig();
+ }
+
+}
+
+package androidx.camera.camera2.interop {
+
+ @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraControl {
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> addCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> clearCaptureRequestOptions();
+ method public static androidx.camera.camera2.interop.Camera2CameraControl from(androidx.camera.core.CameraControl);
+ method public androidx.camera.camera2.interop.CaptureRequestOptions getCaptureRequestOptions();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
+ }
+
+ @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraInfo {
+ method public static androidx.camera.camera2.interop.Camera2CameraInfo from(androidx.camera.core.CameraInfo);
+ method public <T> T? getCameraCharacteristic(android.hardware.camera2.CameraCharacteristics.Key<T!>);
+ method public String getCameraId();
+ }
+
+ @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2Interop {
+ }
+
+ @RequiresApi(21) public static final class Camera2Interop.Extender<T> {
+ ctor public Camera2Interop.Extender(androidx.camera.core.ExtendableBuilder<T!>);
+ method public <ValueT> androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
+ method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setDeviceStateCallback(android.hardware.camera2.CameraDevice.StateCallback);
+ method @RequiresApi(28) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setPhysicalCameraId(String);
+ method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionCaptureCallback(android.hardware.camera2.CameraCaptureSession.CaptureCallback);
+ method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionStateCallback(android.hardware.camera2.CameraCaptureSession.StateCallback);
+ method @RequiresApi(33) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setStreamUseCase(long);
+ }
+
+ @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public class CaptureRequestOptions {
+ method public <ValueT> ValueT? getCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
+ }
+
+ @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.interop.CaptureRequestOptions> {
+ ctor public CaptureRequestOptions.Builder();
+ method public androidx.camera.camera2.interop.CaptureRequestOptions build();
+ method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder clearCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
+ method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCamera2Interop {
+ }
+
+}
+
diff --git a/camera/camera-camera2/api/res-1.4.0-beta01.txt b/camera/camera-camera2/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-camera2/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-camera2/api/restricted_1.4.0-beta01.txt b/camera/camera-camera2/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..87c79d0
--- /dev/null
+++ b/camera/camera-camera2/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,54 @@
+// Signature format: 4.0
+package androidx.camera.camera2 {
+
+ @RequiresApi(21) public final class Camera2Config {
+ method public static androidx.camera.core.CameraXConfig defaultConfig();
+ }
+
+}
+
+package androidx.camera.camera2.interop {
+
+ @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraControl {
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> addCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> clearCaptureRequestOptions();
+ method public static androidx.camera.camera2.interop.Camera2CameraControl from(androidx.camera.core.CameraControl);
+ method public androidx.camera.camera2.interop.CaptureRequestOptions getCaptureRequestOptions();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
+ }
+
+ @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraInfo {
+ method public static androidx.camera.camera2.interop.Camera2CameraInfo from(androidx.camera.core.CameraInfo);
+ method public <T> T? getCameraCharacteristic(android.hardware.camera2.CameraCharacteristics.Key<T!>);
+ method public String getCameraId();
+ }
+
+ @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2Interop {
+ }
+
+ @RequiresApi(21) public static final class Camera2Interop.Extender<T> {
+ ctor public Camera2Interop.Extender(androidx.camera.core.ExtendableBuilder<T!>);
+ method public <ValueT> androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
+ method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setDeviceStateCallback(android.hardware.camera2.CameraDevice.StateCallback);
+ method @RequiresApi(28) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setPhysicalCameraId(String);
+ method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionCaptureCallback(android.hardware.camera2.CameraCaptureSession.CaptureCallback);
+ method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionStateCallback(android.hardware.camera2.CameraCaptureSession.StateCallback);
+ method @RequiresApi(33) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setStreamUseCase(long);
+ }
+
+ @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public class CaptureRequestOptions {
+ method public <ValueT> ValueT? getCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
+ }
+
+ @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.interop.CaptureRequestOptions> {
+ ctor public CaptureRequestOptions.Builder();
+ method public androidx.camera.camera2.interop.CaptureRequestOptions build();
+ method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder clearCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
+ method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCamera2Interop {
+ }
+
+}
+
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
index df46a8f..ee45347 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
@@ -187,6 +187,8 @@
private DynamicRangesCompat mDynamicRangesCompat;
+ private Quirks mCameraQuirks;
+
@Rule
public TestRule wakelockEmptyActivityRule = new WakelockEmptyActivityRule();
@@ -230,6 +232,8 @@
throw new AssumptionViolatedException("Could not retrieve camera characteristics", e);
}
+ mCameraQuirks = CameraQuirks.get(cameraId, mCameraCharacteristics);
+
mCaptureSessionOpenerBuilder = new SynchronizedCaptureSession.OpenerBuilder(mExecutor,
mScheduledExecutor, mHandler, mCaptureSessionRepository,
CameraQuirks.get(cameraId, mCameraCharacteristics), DeviceQuirks.getAll());
@@ -442,7 +446,7 @@
FakeOpener fakeOpener = new FakeOpener();
// 2. Act
- CaptureSession captureSession = new CaptureSession(mDynamicRangesCompat);
+ CaptureSession captureSession = new CaptureSession(mDynamicRangesCompat, mCameraQuirks);
captureSession.setSessionConfig(sessionConfig); // set repeating request
captureSession.open(sessionConfig,
mCameraDeviceHolder.get(), fakeOpener);
@@ -1122,7 +1126,7 @@
}
private CaptureSession createCaptureSession() {
- CaptureSession captureSession = new CaptureSession(mDynamicRangesCompat);
+ CaptureSession captureSession = new CaptureSession(mDynamicRangesCompat, mCameraQuirks);
mCaptureSessions.add(captureSession);
return captureSession;
}
@@ -1409,7 +1413,7 @@
FakeOpener fakeOpener = new FakeOpener();
// Don't use #createCaptureSession since FakeOpenerImpl won't create CameraCaptureSession
// so no need to be released.
- CaptureSession captureSession = new CaptureSession(mDynamicRangesCompat);
+ CaptureSession captureSession = new CaptureSession(mDynamicRangesCompat, mCameraQuirks);
captureSession.open(sessionConfigBuilder.build(), mCameraDeviceHolder.get(), fakeOpener);
ArgumentCaptor<SessionConfigurationCompat> captor =
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
index 1a7363d..5bc7186 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
@@ -307,7 +307,8 @@
private CaptureSessionInterface newCaptureSession() {
synchronized (mLock) {
if (mSessionProcessor == null) {
- return new CaptureSession(mDynamicRangesCompat);
+ return new CaptureSession(mDynamicRangesCompat,
+ mCameraInfoInternal.getCameraQuirks());
} else {
return new ProcessingCaptureSession(mSessionProcessor,
mCameraInfoInternal, mDynamicRangesCompat, mExecutor,
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
index d2704a1..5be543a 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
@@ -37,6 +37,8 @@
import androidx.camera.camera2.internal.compat.params.InputConfigurationCompat;
import androidx.camera.camera2.internal.compat.params.OutputConfigurationCompat;
import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat;
+import androidx.camera.camera2.internal.compat.quirk.CaptureNoResponseQuirk;
+import androidx.camera.camera2.internal.compat.workaround.RequestMonitor;
import androidx.camera.camera2.internal.compat.workaround.StillCaptureFlow;
import androidx.camera.camera2.internal.compat.workaround.TorchStateReset;
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
@@ -45,7 +47,9 @@
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CaptureConfig;
import androidx.camera.core.impl.DeferrableSurface;
+import androidx.camera.core.impl.Quirks;
import androidx.camera.core.impl.SessionConfig;
+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;
@@ -76,16 +80,6 @@
/** The configuration for the currently issued single capture requests. */
@GuardedBy("mSessionLock")
private final List<CaptureConfig> mCaptureConfigs = new ArrayList<>();
- /** Callback for handling image captures. */
- private final CameraCaptureSession.CaptureCallback mCaptureCallback =
- new CaptureCallback() {
- @Override
- public void onCaptureCompleted(
- @NonNull CameraCaptureSession session,
- @NonNull CaptureRequest request,
- @NonNull TotalCaptureResult result) {
- }
- };
@GuardedBy("mSessionLock")
private final StateCallback mCaptureSessionStateCallback;
/** The Opener to help on creating the SynchronizedCaptureSession. */
@@ -122,19 +116,29 @@
CallbackToFutureAdapter.Completer<Void> mReleaseCompleter;
@NonNull
@GuardedBy("mSessionLock")
- Map<DeferrableSurface, Long> mStreamUseCaseMap = new HashMap<>();
- final StillCaptureFlow mStillCaptureFlow = new StillCaptureFlow();
- final TorchStateReset mTorchStateReset = new TorchStateReset();
-
+ private Map<DeferrableSurface, Long> mStreamUseCaseMap = new HashMap<>();
+ private final StillCaptureFlow mStillCaptureFlow = new StillCaptureFlow();
+ private final TorchStateReset mTorchStateReset = new TorchStateReset();
+ private final RequestMonitor mRequestMonitor;
private final DynamicRangesCompat mDynamicRangesCompat;
/**
+ * Constructor for CaptureSession without CameraQuirk.
+ */
+ CaptureSession(@NonNull DynamicRangesCompat dynamicRangesCompat) {
+ this(dynamicRangesCompat, null);
+ }
+
+ /**
* Constructor for CaptureSession.
*/
- CaptureSession(@NonNull DynamicRangesCompat dynamicRangesCompat) {
+ CaptureSession(@NonNull DynamicRangesCompat dynamicRangesCompat,
+ @Nullable Quirks cameraQuirks) {
mState = State.INITIALIZED;
mDynamicRangesCompat = dynamicRangesCompat;
mCaptureSessionStateCallback = new StateCallback();
+ mRequestMonitor = new RequestMonitor(
+ cameraQuirks != null && cameraQuirks.contains(CaptureNoResponseQuirk.class));
}
@Override
@@ -445,6 +449,7 @@
"The Opener shouldn't null in state:" + mState);
mSessionOpener.stop();
mState = State.CLOSED;
+ mRequestMonitor.stop();
mSessionConfig = null;
break;
@@ -485,6 +490,7 @@
// Fall through
case OPENING:
mState = State.RELEASING;
+ mRequestMonitor.stop();
Preconditions.checkNotNull(mSessionOpener,
"The Opener shouldn't null in state:" + mState);
if (mSessionOpener.stop()) {
@@ -633,9 +639,8 @@
}
CameraCaptureSession.CaptureCallback comboCaptureCallback =
- createCamera2CaptureCallback(
- captureConfig.getCameraCaptureCallbacks(),
- mCaptureCallback);
+ mRequestMonitor.createMonitorListener(createCamera2CaptureCallback(
+ captureConfig.getCameraCaptureCallbacks()));
return mSynchronizedCaptureSession.setSingleRepeatingRequest(captureRequest,
comboCaptureCallback);
@@ -652,14 +657,18 @@
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@GuardedBy("mSessionLock")
void issuePendingCaptureRequest() {
- if (mCaptureConfigs.isEmpty()) {
- return;
- }
- try {
- issueBurstCaptureRequest(mCaptureConfigs);
- } finally {
- mCaptureConfigs.clear();
- }
+ mRequestMonitor.getRequestsProcessedFuture().addListener(() -> {
+ synchronized (mSessionLock) {
+ if (mCaptureConfigs.isEmpty()) {
+ return;
+ }
+ try {
+ issueBurstCaptureRequest(mCaptureConfigs);
+ } finally {
+ mCaptureConfigs.clear();
+ }
+ }
+ }, CameraXExecutors.directExecutor());
}
/**
@@ -1007,21 +1016,4 @@
}
}
}
-
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @GuardedBy("mSessionLock")
- List<CaptureConfig> setupConfiguredSurface(List<CaptureConfig> list) {
- List<CaptureConfig> ret = new ArrayList<>();
- for (CaptureConfig c : list) {
- CaptureConfig.Builder builder = CaptureConfig.Builder.from(c);
- builder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW);
- for (DeferrableSurface deferrableSurface :
- mSessionConfig.getRepeatingCaptureConfig().getSurfaces()) {
- builder.addSurface(deferrableSurface);
- }
- ret.add(builder.build());
- }
-
- return ret;
- }
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
index 5cdb307..1e8e2fc 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
@@ -197,19 +197,25 @@
}
}
+ DeferrableSurface postviewDeferrableSurface;
if (sessionConfig.getPostviewOutputConfig() != null) {
- DeferrableSurface postviewDeferrableSurface =
+ postviewDeferrableSurface =
sessionConfig.getPostviewOutputConfig().getSurface();
postviewOutputSurface = OutputSurface.create(
postviewDeferrableSurface.getSurface().get(),
postviewDeferrableSurface.getPrescribedSize(),
postviewDeferrableSurface.getPrescribedStreamFormat()
);
+ } else {
+ postviewDeferrableSurface = null;
}
mProcessorState = ProcessorState.SESSION_INITIALIZED;
try {
DeferrableSurfaces.incrementAll(mOutputSurfaces);
+ if (postviewDeferrableSurface != null) {
+ postviewDeferrableSurface.incrementUseCount();
+ }
} catch (DeferrableSurface.SurfaceClosedException e) {
return Futures.immediateFailedFuture(e);
}
@@ -228,6 +234,9 @@
Logger.e(TAG, "initSession failed", e);
// Ensure we decrement the output surfaces if initSession failed.
DeferrableSurfaces.decrementAll(mOutputSurfaces);
+ if (postviewDeferrableSurface != null) {
+ postviewDeferrableSurface.decrementUseCount();
+ }
throw e;
}
@@ -236,6 +245,9 @@
mProcessorSessionConfig.getSurfaces().get(0).getTerminationFuture()
.addListener(() -> {
DeferrableSurfaces.decrementAll(mOutputSurfaces);
+ if (postviewDeferrableSurface != null) {
+ postviewDeferrableSurface.decrementUseCount();
+ }
}, CameraXExecutors.directExecutor());
// Holding the Processor surfaces in case they are GCed
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionImpl.java
index 40d13d9..9b431ea 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SynchronizedCaptureSessionImpl.java
@@ -29,6 +29,8 @@
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.internal.annotation.CameraExecutor;
import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat;
+import androidx.camera.camera2.internal.compat.quirk.CaptureSessionStuckQuirk;
+import androidx.camera.camera2.internal.compat.quirk.IncorrectCaptureStateQuirk;
import androidx.camera.camera2.internal.compat.workaround.ForceCloseCaptureSession;
import androidx.camera.camera2.internal.compat.workaround.ForceCloseDeferrableSurface;
import androidx.camera.camera2.internal.compat.workaround.RequestMonitor;
@@ -84,7 +86,8 @@
@NonNull Handler compatHandler) {
super(repository, executor, scheduledExecutorService, compatHandler);
mCloseSurfaceQuirk = new ForceCloseDeferrableSurface(cameraQuirks, deviceQuirks);
- mRequestMonitor = new RequestMonitor(cameraQuirks);
+ mRequestMonitor = new RequestMonitor(cameraQuirks.contains(CaptureSessionStuckQuirk.class)
+ || cameraQuirks.contains(IncorrectCaptureStateQuirk.class));
mForceCloseSessionQuirk = new ForceCloseCaptureSession(deviceQuirks);
mSessionResetPolicy = new SessionResetPolicy(deviceQuirks);
mScheduledExecutorService = scheduledExecutorService;
@@ -165,16 +168,16 @@
@Override
public int setSingleRepeatingRequest(@NonNull CaptureRequest request,
@NonNull CameraCaptureSession.CaptureCallback listener) throws CameraAccessException {
- return mRequestMonitor.setSingleRepeatingRequest(
- request, listener, super::setSingleRepeatingRequest);
+ return super.setSingleRepeatingRequest(
+ request, mRequestMonitor.createMonitorListener(listener));
}
@ExecutedBy("mExecutor")
@Override
public int captureBurstRequests(@NonNull List<CaptureRequest> requests,
@NonNull CameraCaptureSession.CaptureCallback listener) throws CameraAccessException {
- return mRequestMonitor.captureBurstRequests(
- requests, listener, super::captureBurstRequests);
+ return super.captureBurstRequests(
+ requests, mRequestMonitor.createMonitorListener(listener));
}
@Override
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java
index a8531a4..7c457f7 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java
@@ -64,6 +64,9 @@
if (CamcorderProfileResolutionQuirk.load(cameraCharacteristicsCompat)) {
quirks.add(new CamcorderProfileResolutionQuirk(cameraCharacteristicsCompat));
}
+ if (CaptureNoResponseQuirk.load(cameraCharacteristicsCompat)) {
+ quirks.add(new CaptureNoResponseQuirk());
+ }
if (ImageCaptureWashedOutImageQuirk.load(cameraCharacteristicsCompat)) {
quirks.add(new ImageCaptureWashedOutImageQuirk());
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureNoResponseQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureNoResponseQuirk.java
new file mode 100644
index 0000000..374e821
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CaptureNoResponseQuirk.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.compat.quirk;
+
+import static android.hardware.camera2.CameraMetadata.LENS_FACING_BACK;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * Capture not response if submitting a single capture request simultaneously with repeating
+ * requests
+ *
+ * <p>QuirkSummary
+ * Bug Id: 305835396
+ * Description: This class defines a quirk related to the camera capture functionality on
+ * specific devices. It describes a scenario where single capture requests may
+ * not receive a response if they are submitted simultaneously with repeating
+ * capture requests. Single capture requests fail to receive a response
+ * approximately 10% of the time when submitted within milliseconds of a
+ * repeating capture request.
+ * Device(s): Samsung device with samsungexynos7420 hardware
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class CaptureNoResponseQuirk implements Quirk {
+
+ static boolean load(@NonNull CameraCharacteristicsCompat characteristics) {
+ return ("samsungexynos7420".equalsIgnoreCase(Build.HARDWARE)
+ || "universal7420".equalsIgnoreCase(Build.HARDWARE))
+ && characteristics.get(CameraCharacteristics.LENS_FACING) == LENS_FACING_BACK;
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java
index 40f8296..d6b7cbe 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java
@@ -56,8 +56,8 @@
private static final SurfaceCombination FULL_LEVEL_YUV_YUV_YUV_CONFIGURATION =
createFullYuvYuvYuvConfiguration();
- private static final SurfaceCombination LEVEL_3_LEVEL_PRIV_PRIV_YUV_RAW_CONFIGURATION =
- createLevel3PrivPrivYuvRawConfiguration();
+ private static final SurfaceCombination LEVEL_3_LEVEL_PRIV_PRIV_YUV_SUBSET_CONFIGURATION =
+ createLevel3PrivPrivYuvSubsetConfiguration();
private static final Set<String> SUPPORT_EXTRA_FULL_CONFIGURATIONS_SAMSUNG_MODELS =
new HashSet<>(Arrays.asList(
@@ -217,9 +217,16 @@
"PIXEL 7",
"PIXEL 7 PRO"));
+ private static final Set<String> SUPPORT_EXTRA_LEVEL_3_CONFIGURATIONS_SAMSUNG_MODELS =
+ new HashSet<>(Arrays.asList(
+ "SM-S926B", // Galaxy S24+
+ "SM-S928U" // Galaxy S24 Ultra
+ ));
+
static boolean load() {
return isSamsungS7() || supportExtraFullConfigurationsSamsungDevice()
- || supportExtraLevel3ConfigurationsGoogleDevice();
+ || supportExtraLevel3ConfigurationsGoogleDevice()
+ || supportExtraLevel3ConfigurationsSamsungDevice();
}
private static boolean isSamsungS7() {
@@ -247,6 +254,16 @@
return SUPPORT_EXTRA_LEVEL_3_CONFIGURATIONS_GOOGLE_MODELS.contains(capitalModelName);
}
+ private static boolean supportExtraLevel3ConfigurationsSamsungDevice() {
+ if (!"samsung".equalsIgnoreCase(Build.BRAND)) {
+ return false;
+ }
+
+ String capitalModelName = Build.MODEL.toUpperCase(Locale.US);
+
+ return SUPPORT_EXTRA_LEVEL_3_CONFIGURATIONS_SAMSUNG_MODELS.contains(capitalModelName);
+ }
+
/**
* Returns the extra supported surface combinations for specific camera on the device.
*/
@@ -261,8 +278,9 @@
return getLimitedDeviceExtraSupportedFullConfigurations(hardwareLevel);
}
- if (supportExtraLevel3ConfigurationsGoogleDevice()) {
- return Collections.singletonList(LEVEL_3_LEVEL_PRIV_PRIV_YUV_RAW_CONFIGURATION);
+ if (supportExtraLevel3ConfigurationsGoogleDevice()
+ || supportExtraLevel3ConfigurationsSamsungDevice()) {
+ return Collections.singletonList(LEVEL_3_LEVEL_PRIV_PRIV_YUV_SUBSET_CONFIGURATION);
}
return Collections.emptyList();
@@ -323,7 +341,15 @@
return surfaceCombination;
}
- private static SurfaceCombination createLevel3PrivPrivYuvRawConfiguration() {
+ /**
+ * Creates (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) surface combination.
+ *
+ * <p>This is a subset of LEVEL_3 camera devices'
+ * (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) + (RAW, MAXIMUM)
+ * guaranteed supported configuration. This configuration has been verified to make sure that
+ * the surface combination can work well on the target devices.
+ */
+ private static SurfaceCombination createLevel3PrivPrivYuvSubsetConfiguration() {
// (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) + (RAW, MAXIMUM)
SurfaceCombination surfaceCombination = new SurfaceCombination();
surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.PRIV,
@@ -332,8 +358,6 @@
SurfaceConfig.ConfigSize.VGA));
surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
SurfaceConfig.ConfigSize.MAXIMUM));
- surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.RAW,
- SurfaceConfig.ConfigSize.MAXIMUM));
return surfaceCombination;
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/RequestMonitor.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/RequestMonitor.java
index a34bc74..85b0c5e 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/RequestMonitor.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/RequestMonitor.java
@@ -16,18 +16,18 @@
package androidx.camera.camera2.internal.compat.workaround;
-import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.internal.Camera2CaptureCallbacks;
+import androidx.camera.camera2.internal.compat.quirk.CaptureNoResponseQuirk;
import androidx.camera.camera2.internal.compat.quirk.CaptureSessionStuckQuirk;
import androidx.camera.camera2.internal.compat.quirk.IncorrectCaptureStateQuirk;
-import androidx.camera.core.impl.Quirks;
import androidx.camera.core.impl.annotation.ExecutedBy;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.Futures;
@@ -42,32 +42,42 @@
import java.util.Objects;
/**
- * Tracking in-flight capture sequences of a CameraCaptureSession if certain Quirks are enabled.
+ * Monitors in-flight capture sequences on devices with specific quirks.
*
- * <p>If you try to open a new CameraCaptureSession before the existing CameraCaptureSession
- * processes its in-flight capture sequences on certain devices, the new session may fail to be
- * configured. To track the status of in-flight capture sequences, use the
- * RequestMonitor#getRequestsProcessedFuture() method. This method returns a ListenableFuture that
- * indicates when all in-flight capture sequences have been processed.
+ * <p>Quirks on Certain Devices:
+ * <p>Some devices may fail to configure new CameraCaptureSessions
+ * if existing in-flight capture sequences haven't completed. This class helps you work around
+ * these issues.
+ * <p>Single capture requests may not receive a response if they are submitted
+ * simultaneously with repeating capture requests. Single capture requests fail to receive a
+ * response approximately 10% of the time when submitted within milliseconds of a repeating
+ * capture request.
*
+ * <p>How it works: Use `RequestMonitor#getRequestsProcessedFuture()` to get a ListenableFuture.
+ * This future signals when all in-flight capture sequences have been processed.
+ *
+ * @see CaptureNoResponseQuirk
* @see CaptureSessionStuckQuirk
* @see IncorrectCaptureStateQuirk
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class RequestMonitor {
+
+ private static final String TAG = "RequestMonitor";
private final boolean mQuirkEnabled;
private final List<ListenableFuture<Void>> mRequestTasks =
Collections.synchronizedList(new ArrayList<>());
/** Constructor of the RequestMonitor */
- public RequestMonitor(@NonNull Quirks cameraQuirks) {
- mQuirkEnabled = cameraQuirks.contains(CaptureSessionStuckQuirk.class)
- || cameraQuirks.contains(IncorrectCaptureStateQuirk.class);
+ public RequestMonitor(boolean quirkEnabled) {
+ mQuirkEnabled = quirkEnabled;
}
/**
- * Return true if the opening of the session should wait for the other CameraCaptureSessions
- * to complete their in-flight capture sequences before opening the current session.
+ * Indicates whether capture sequence monitoring is enabled.
+ *
+ * <p>Returns true if a quirk is enabled that necessitates tracking in-flight capture requests.
+ * Returns false otherwise.
*/
public boolean shouldMonitorRequest() {
return mQuirkEnabled;
@@ -89,28 +99,33 @@
input -> null, CameraXExecutors.directExecutor()));
}
- /** Hook the setSingleRepeatingRequest() to know if it has started a repeating request. */
+ /**
+ * Creates a listener that monitors request completion for the `RequestMonitor`.
+ *
+ * <p>This listener should be assigned to the CameraCaptureSession via
+ * the `setSingleRepeatingRequest` or `captureBurstRequests` method to track when submitted
+ * requests are fully processed.
+ * The `RequestMonitor` can then use this information to ensure proper capture sequence
+ * handling.
+ *
+ * <p>Note: the created listener wraps the provided `originalListener`, ensuring any original
+ * capture callbacks still function as intended.
+ *
+ * @param originalListener The original CaptureCallback to combine with monitoring
+ * functionality.
+ * @return A new CaptureCallback that includes request completion tracking for the
+ * `RequestMonitor`.
+ */
@ExecutedBy("mExecutor")
- public int setSingleRepeatingRequest(@NonNull CaptureRequest request,
- @NonNull CameraCaptureSession.CaptureCallback listener,
- @NonNull SingleRequest singleRequest) throws CameraAccessException {
+ @NonNull
+ public CameraCaptureSession.CaptureCallback createMonitorListener(
+ @NonNull CameraCaptureSession.CaptureCallback originalListener) {
if (shouldMonitorRequest()) {
- listener =
- Camera2CaptureCallbacks.createComboCallback(createMonitorListener(), listener);
+ return Camera2CaptureCallbacks.createComboCallback(createMonitorListener(),
+ originalListener);
+ } else {
+ return originalListener;
}
- return singleRequest.run(request, listener);
- }
-
- /** Hook the captureBurstRequests() to know if it has started the requests. */
- @ExecutedBy("mExecutor")
- public int captureBurstRequests(@NonNull List<CaptureRequest> requests,
- @NonNull CameraCaptureSession.CaptureCallback listener,
- @NonNull MultiRequest multiRequest) throws CameraAccessException {
- if (shouldMonitorRequest()) {
- listener =
- Camera2CaptureCallbacks.createComboCallback(createMonitorListener(), listener);
- }
- return multiRequest.run(requests, listener);
}
private CameraCaptureSession.CaptureCallback createMonitorListener() {
@@ -118,7 +133,11 @@
ListenableFuture<Void> future = completeListener.mStartRequestFuture;
mRequestTasks.add(future);
- future.addListener(() -> mRequestTasks.remove(future), CameraXExecutors.directExecutor());
+ Log.d(TAG, "RequestListener " + completeListener + " monitoring " + this);
+ future.addListener(() -> {
+ Log.d(TAG, "RequestListener " + completeListener + " done " + this);
+ mRequestTasks.remove(future);
+ }, CameraXExecutors.directExecutor());
return completeListener;
}
@@ -181,22 +200,4 @@
}
}
}
-
- /** Interface to forward call of the setSingleRepeatingRequest() method. */
- @FunctionalInterface
- public interface SingleRequest {
- /** Run the setSingleRepeatingRequest() method. */
- int run(@NonNull CaptureRequest request,
- @NonNull CameraCaptureSession.CaptureCallback listener)
- throws CameraAccessException;
- }
-
- /** Interface to forward call of the captureBurstRequests() method. */
- @FunctionalInterface
- public interface MultiRequest {
- /** Run the captureBurstRequests() method. */
- int run(@NonNull List<CaptureRequest> requests,
- @NonNull CameraCaptureSession.CaptureCallback listener)
- throws CameraAccessException;
- }
}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ScreenFlashTaskTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ScreenFlashTaskTest.kt
index 5ae469e..69cec3c 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ScreenFlashTaskTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ScreenFlashTaskTest.kt
@@ -23,6 +23,7 @@
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat
import androidx.camera.camera2.internal.compat.quirk.TorchFlashRequiredFor3aUpdateQuirk
import androidx.camera.camera2.internal.compat.workaround.UseFlashModeTorchFor3aUpdate
+import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.ScreenFlash
import androidx.camera.core.impl.CameraControlInternal
import androidx.camera.core.impl.CaptureConfig
@@ -45,7 +46,6 @@
import org.robolectric.annotation.internal.DoNotInstrument
import org.robolectric.shadow.api.Shadow
import org.robolectric.shadows.ShadowCameraCharacteristics
-import org.robolectric.shadows.ShadowSystem
import org.robolectric.shadows.ShadowTotalCaptureResult
@Config(minSdk = 21)
@@ -64,18 +64,30 @@
executorService.shutdown()
}
+ // 3s timeout is hardcoded in ImageCapture.ScreenFlash.apply documentation
@Test
fun screenFlashApplyInvokedWithAtLeast3sTimeout_whenPreCaptureCalled() {
val screenFlashTask = createScreenFlashTask()
- val initialTime = ShadowSystem.currentTimeMillis()
+ val initialTime = System.currentTimeMillis()
screenFlashTask.preCapture(null)
shadowOf(getMainLooper()).idle()
assertThat(screenFlash.lastApplyExpirationTimeMillis).isAtLeast(
- initialTime + TimeUnit.SECONDS.toMillis(
- 3
- )
+ initialTime + TimeUnit.SECONDS.toMillis(3)
+ )
+ }
+
+ @Test
+ fun screenFlashApplyInvokedWithLessThan4sTimeout_whenPreCaptureCalled() {
+ val screenFlashTask = createScreenFlashTask()
+ val initialTime = System.currentTimeMillis()
+
+ screenFlashTask.preCapture(null)
+ shadowOf(getMainLooper()).idle()
+
+ assertThat(screenFlash.lastApplyExpirationTimeMillis).isLessThan(
+ initialTime + TimeUnit.SECONDS.toMillis(4)
)
}
@@ -170,14 +182,29 @@
}
@Test
- fun aePrecaptureNotTriggered_whenScreenFlashApplyNotCompleted() {
+ fun aePrecaptureNotTriggeredUntilTimeout_whenScreenFlashApplyNotCompleted() {
val screenFlashTask = createScreenFlashTask()
screenFlash.setApplyCompletedInstantly(false)
screenFlashTask.preCapture(null)
shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
- assertThat(cameraControl.focusMeteringControl.triggerAePrecaptureCount).isEqualTo(0)
+ cameraControl.focusMeteringControl.awaitTriggerAePrecapture(1000)
+ }
+
+ @Test
+ fun aePrecaptureTriggeredAfterTimeout_whenScreenFlashApplyNotCompleted() {
+ val screenFlashTask = createScreenFlashTask()
+ screenFlash.setApplyCompletedInstantly(false)
+
+ screenFlashTask.preCapture(null)
+ shadowOf(getMainLooper()).idle() // ScreenFlash#apply is invoked in main thread
+
+ cameraControl.focusMeteringControl.awaitTriggerAePrecapture(
+ TimeUnit.SECONDS.toMillis(
+ ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS + 1
+ )
+ )
}
@Test
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java
index 26cc0fd..432907e 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java
@@ -71,29 +71,42 @@
// Tests for FULL Pixel devices
data.add(new Object[]{new Config("Google", null, "Pixel 6", "0",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration())});
+ createLevel3PrivPrivYuvSubsetConfiguration())});
data.add(new Object[]{new Config("Google", null, "Pixel 6", "1",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration())});
+ createLevel3PrivPrivYuvSubsetConfiguration())});
data.add(new Object[]{new Config("Google", null, "Pixel 6 Pro", "0",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration())});
+ createLevel3PrivPrivYuvSubsetConfiguration())});
data.add(new Object[]{new Config("Google", null, "Pixel 6 Pro", "1",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration())});
+ createLevel3PrivPrivYuvSubsetConfiguration())});
data.add(new Object[]{new Config("Google", null, "Pixel 7", "0",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration())});
+ createLevel3PrivPrivYuvSubsetConfiguration())});
data.add(new Object[]{new Config("Google", null, "Pixel 7", "1",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration())});
+ createLevel3PrivPrivYuvSubsetConfiguration())});
data.add(new Object[]{new Config("Google", null, "Pixel 7 Pro", "0",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration())});
+ createLevel3PrivPrivYuvSubsetConfiguration())});
data.add(new Object[]{new Config("Google", null, "Pixel 7 Pro", "1",
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- createLevel3PrivPrivYuvRawConfiguration())});
+ createLevel3PrivPrivYuvSubsetConfiguration())});
+ // Tests for FULL Samsung devices
+ data.add(new Object[]{new Config("Samsung", null, "SM-S926B", "0",
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ createLevel3PrivPrivYuvSubsetConfiguration())});
+ data.add(new Object[]{new Config("Samsung", null, "SM-S926B", "1",
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ createLevel3PrivPrivYuvSubsetConfiguration())});
+ data.add(new Object[]{new Config("Samsung", null, "SM-S928U", "0",
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ createLevel3PrivPrivYuvSubsetConfiguration())});
+ data.add(new Object[]{new Config("Samsung", null, "SM-S928U", "1",
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ createLevel3PrivPrivYuvSubsetConfiguration())});
// Other cases
data.add(new Object[]{new Config(null, null, null, "0",
@@ -187,8 +200,8 @@
return new SurfaceCombination[]{surfaceCombination1, surfaceCombination2};
}
- private static SurfaceCombination[] createLevel3PrivPrivYuvRawConfiguration() {
- // (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) + (RAW, MAXIMUM)
+ private static SurfaceCombination[] createLevel3PrivPrivYuvSubsetConfiguration() {
+ // (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM)
SurfaceCombination surfaceCombination = new SurfaceCombination();
surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.PRIV,
SurfaceConfig.ConfigSize.PREVIEW));
@@ -196,8 +209,6 @@
SurfaceConfig.ConfigSize.VGA));
surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
SurfaceConfig.ConfigSize.MAXIMUM));
- surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.RAW,
- SurfaceConfig.ConfigSize.MAXIMUM));
return new SurfaceCombination[]{surfaceCombination};
}
diff --git a/camera/camera-core/api/1.4.0-beta01.txt b/camera/camera-core/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..a61f5a4
--- /dev/null
+++ b/camera/camera-core/api/1.4.0-beta01.txt
@@ -0,0 +1,669 @@
+// Signature format: 4.0
+package androidx.camera.core {
+
+ @RequiresApi(21) public class AspectRatio {
+ field public static final int RATIO_16_9 = 1; // 0x1
+ field public static final int RATIO_4_3 = 0; // 0x0
+ field public static final int RATIO_DEFAULT = -1; // 0xffffffff
+ }
+
+ @RequiresApi(21) public interface Camera {
+ method public androidx.camera.core.CameraControl getCameraControl();
+ method public androidx.camera.core.CameraInfo getCameraInfo();
+ }
+
+ @RequiresApi(21) public interface CameraControl {
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> cancelFocusAndMetering();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> setExposureCompensationIndex(int);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.camera.core.FocusMeteringResult!> startFocusAndMetering(androidx.camera.core.FocusMeteringAction);
+ }
+
+ public static final class CameraControl.OperationCanceledException extends java.lang.Exception {
+ }
+
+ @RequiresApi(21) public abstract class CameraEffect {
+ ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.ImageProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
+ ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.SurfaceProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
+ method public androidx.core.util.Consumer<java.lang.Throwable!> getErrorListener();
+ method public java.util.concurrent.Executor getExecutor();
+ method public androidx.camera.core.SurfaceProcessor? getSurfaceProcessor();
+ method public int getTargets();
+ field public static final int IMAGE_CAPTURE = 4; // 0x4
+ field public static final int PREVIEW = 1; // 0x1
+ field public static final int VIDEO_CAPTURE = 2; // 0x2
+ }
+
+ @RequiresApi(21) public interface CameraFilter {
+ method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
+ }
+
+ @RequiresApi(21) public interface CameraInfo {
+ method public androidx.camera.core.CameraSelector getCameraSelector();
+ method public androidx.lifecycle.LiveData<androidx.camera.core.CameraState!> getCameraState();
+ method public androidx.camera.core.ExposureState getExposureState();
+ method @FloatRange(from=0, fromInclusive=false) public default float getIntrinsicZoomRatio();
+ method public default int getLensFacing();
+ method public int getSensorRotationDegrees();
+ method public int getSensorRotationDegrees(int);
+ method public default java.util.Set<android.util.Range<java.lang.Integer!>!> getSupportedFrameRateRanges();
+ method public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
+ method public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
+ method public boolean hasFlashUnit();
+ method public default boolean isFocusMeteringSupported(androidx.camera.core.FocusMeteringAction);
+ method @SuppressCompatibility @androidx.camera.core.ExperimentalZeroShutterLag public default boolean isZslSupported();
+ method public static boolean mustPlayShutterSound();
+ method public default java.util.Set<androidx.camera.core.DynamicRange!> querySupportedDynamicRanges(java.util.Set<androidx.camera.core.DynamicRange!>);
+ }
+
+ @RequiresApi(21) public final class CameraInfoUnavailableException extends java.lang.Exception {
+ }
+
+ @RequiresApi(21) public interface CameraProvider {
+ method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
+ method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+ }
+
+ @RequiresApi(21) public final class CameraSelector {
+ method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
+ field public static final androidx.camera.core.CameraSelector DEFAULT_BACK_CAMERA;
+ field public static final androidx.camera.core.CameraSelector DEFAULT_FRONT_CAMERA;
+ field public static final int LENS_FACING_BACK = 1; // 0x1
+ field @SuppressCompatibility @androidx.camera.core.ExperimentalLensFacing public static final int LENS_FACING_EXTERNAL = 2; // 0x2
+ field public static final int LENS_FACING_FRONT = 0; // 0x0
+ field public static final int LENS_FACING_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public static final class CameraSelector.Builder {
+ ctor public CameraSelector.Builder();
+ method public androidx.camera.core.CameraSelector.Builder addCameraFilter(androidx.camera.core.CameraFilter);
+ method public androidx.camera.core.CameraSelector build();
+ method public androidx.camera.core.CameraSelector.Builder requireLensFacing(int);
+ }
+
+ @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class CameraState {
+ ctor public CameraState();
+ method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type);
+ method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type, androidx.camera.core.CameraState.StateError?);
+ method public abstract androidx.camera.core.CameraState.StateError? getError();
+ method public abstract androidx.camera.core.CameraState.Type getType();
+ field public static final int ERROR_CAMERA_DISABLED = 5; // 0x5
+ field public static final int ERROR_CAMERA_FATAL_ERROR = 6; // 0x6
+ field public static final int ERROR_CAMERA_IN_USE = 2; // 0x2
+ field public static final int ERROR_DO_NOT_DISTURB_MODE_ENABLED = 7; // 0x7
+ field public static final int ERROR_MAX_CAMERAS_IN_USE = 1; // 0x1
+ field public static final int ERROR_OTHER_RECOVERABLE_ERROR = 3; // 0x3
+ field public static final int ERROR_STREAM_CONFIG = 4; // 0x4
+ }
+
+ public enum CameraState.ErrorType {
+ enum_constant public static final androidx.camera.core.CameraState.ErrorType CRITICAL;
+ enum_constant public static final androidx.camera.core.CameraState.ErrorType RECOVERABLE;
+ }
+
+ @com.google.auto.value.AutoValue public abstract static class CameraState.StateError {
+ ctor public CameraState.StateError();
+ method public static androidx.camera.core.CameraState.StateError create(int);
+ method public static androidx.camera.core.CameraState.StateError create(int, Throwable?);
+ method public abstract Throwable? getCause();
+ method public abstract int getCode();
+ method public androidx.camera.core.CameraState.ErrorType getType();
+ }
+
+ public enum CameraState.Type {
+ enum_constant public static final androidx.camera.core.CameraState.Type CLOSED;
+ enum_constant public static final androidx.camera.core.CameraState.Type CLOSING;
+ enum_constant public static final androidx.camera.core.CameraState.Type OPEN;
+ enum_constant public static final androidx.camera.core.CameraState.Type OPENING;
+ enum_constant public static final androidx.camera.core.CameraState.Type PENDING_OPEN;
+ }
+
+ @RequiresApi(21) public class CameraUnavailableException extends java.lang.Exception {
+ ctor public CameraUnavailableException(int);
+ ctor public CameraUnavailableException(int, String?);
+ ctor public CameraUnavailableException(int, String?, Throwable?);
+ ctor public CameraUnavailableException(int, Throwable?);
+ method public int getReason();
+ field public static final int CAMERA_DISABLED = 1; // 0x1
+ field public static final int CAMERA_DISCONNECTED = 2; // 0x2
+ field public static final int CAMERA_ERROR = 3; // 0x3
+ field public static final int CAMERA_IN_USE = 4; // 0x4
+ field public static final int CAMERA_MAX_IN_USE = 5; // 0x5
+ field public static final int CAMERA_UNAVAILABLE_DO_NOT_DISTURB = 6; // 0x6
+ field public static final int CAMERA_UNKNOWN_ERROR = 0; // 0x0
+ }
+
+ @RequiresApi(21) public final class CameraXConfig {
+ method public androidx.camera.core.CameraSelector? getAvailableCamerasLimiter(androidx.camera.core.CameraSelector?);
+ method public java.util.concurrent.Executor? getCameraExecutor(java.util.concurrent.Executor?);
+ method public long getCameraOpenRetryMaxTimeoutInMillisWhileResuming();
+ method public int getMinimumLoggingLevel();
+ method public android.os.Handler? getSchedulerHandler(android.os.Handler?);
+ }
+
+ public static final class CameraXConfig.Builder {
+ method public androidx.camera.core.CameraXConfig build();
+ method public static androidx.camera.core.CameraXConfig.Builder fromConfig(androidx.camera.core.CameraXConfig);
+ method public androidx.camera.core.CameraXConfig.Builder setAvailableCamerasLimiter(androidx.camera.core.CameraSelector);
+ method public androidx.camera.core.CameraXConfig.Builder setCameraExecutor(java.util.concurrent.Executor);
+ method public androidx.camera.core.CameraXConfig.Builder setCameraOpenRetryMaxTimeoutInMillisWhileResuming(long);
+ method public androidx.camera.core.CameraXConfig.Builder setMinimumLoggingLevel(@IntRange(from=android.util.Log.DEBUG, to=android.util.Log.ERROR) int);
+ method public androidx.camera.core.CameraXConfig.Builder setSchedulerHandler(android.os.Handler);
+ }
+
+ public static interface CameraXConfig.Provider {
+ method public androidx.camera.core.CameraXConfig getCameraXConfig();
+ }
+
+ @RequiresApi(21) public class ConcurrentCamera {
+ ctor public ConcurrentCamera(java.util.List<androidx.camera.core.Camera!>);
+ method public java.util.List<androidx.camera.core.Camera!> getCameras();
+ }
+
+ public static final class ConcurrentCamera.SingleCameraConfig {
+ ctor public ConcurrentCamera.SingleCameraConfig(androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup, androidx.lifecycle.LifecycleOwner);
+ method public androidx.camera.core.CameraSelector getCameraSelector();
+ method public androidx.lifecycle.LifecycleOwner getLifecycleOwner();
+ method public androidx.camera.core.UseCaseGroup getUseCaseGroup();
+ }
+
+ @RequiresApi(21) public final class DisplayOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
+ ctor public DisplayOrientedMeteringPointFactory(android.view.Display, androidx.camera.core.CameraInfo, float, float);
+ }
+
+ @RequiresApi(21) public final class DynamicRange {
+ ctor public DynamicRange(int, int);
+ method public int getBitDepth();
+ method public int getEncoding();
+ field public static final int BIT_DEPTH_10_BIT = 10; // 0xa
+ field public static final int BIT_DEPTH_8_BIT = 8; // 0x8
+ field public static final int BIT_DEPTH_UNSPECIFIED = 0; // 0x0
+ field public static final androidx.camera.core.DynamicRange DOLBY_VISION_10_BIT;
+ field public static final androidx.camera.core.DynamicRange DOLBY_VISION_8_BIT;
+ field public static final int ENCODING_DOLBY_VISION = 6; // 0x6
+ field public static final int ENCODING_HDR10 = 4; // 0x4
+ field public static final int ENCODING_HDR10_PLUS = 5; // 0x5
+ field public static final int ENCODING_HDR_UNSPECIFIED = 2; // 0x2
+ field public static final int ENCODING_HLG = 3; // 0x3
+ field public static final int ENCODING_SDR = 1; // 0x1
+ field public static final int ENCODING_UNSPECIFIED = 0; // 0x0
+ field public static final androidx.camera.core.DynamicRange HDR10_10_BIT;
+ field public static final androidx.camera.core.DynamicRange HDR10_PLUS_10_BIT;
+ field public static final androidx.camera.core.DynamicRange HDR_UNSPECIFIED_10_BIT;
+ field public static final androidx.camera.core.DynamicRange HLG_10_BIT;
+ field public static final androidx.camera.core.DynamicRange SDR;
+ field public static final androidx.camera.core.DynamicRange UNSPECIFIED;
+ }
+
+ @SuppressCompatibility @RequiresApi(21) @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseApi {
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalZeroShutterLag {
+ }
+
+ @RequiresApi(21) public interface ExposureState {
+ method public int getExposureCompensationIndex();
+ method public android.util.Range<java.lang.Integer!> getExposureCompensationRange();
+ method public android.util.Rational getExposureCompensationStep();
+ method public boolean isExposureCompensationSupported();
+ }
+
+ @RequiresApi(21) public interface ExtendableBuilder<T> {
+ method public T build();
+ }
+
+ @RequiresApi(21) public final class FocusMeteringAction {
+ method public long getAutoCancelDurationInMillis();
+ method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAe();
+ method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAf();
+ method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAwb();
+ method public boolean isAutoCancelEnabled();
+ field public static final int FLAG_AE = 2; // 0x2
+ field public static final int FLAG_AF = 1; // 0x1
+ field public static final int FLAG_AWB = 4; // 0x4
+ }
+
+ public static class FocusMeteringAction.Builder {
+ ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint);
+ ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint, int);
+ method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint);
+ method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint, int);
+ method public androidx.camera.core.FocusMeteringAction build();
+ method public androidx.camera.core.FocusMeteringAction.Builder disableAutoCancel();
+ method public androidx.camera.core.FocusMeteringAction.Builder setAutoCancelDuration(@IntRange(from=1) long, java.util.concurrent.TimeUnit);
+ }
+
+ @RequiresApi(21) public final class FocusMeteringResult {
+ method public boolean isFocusSuccessful();
+ }
+
+ @RequiresApi(21) public final class ImageAnalysis extends androidx.camera.core.UseCase {
+ method public void clearAnalyzer();
+ method @SuppressCompatibility @androidx.camera.core.ExperimentalUseCaseApi public java.util.concurrent.Executor? getBackgroundExecutor();
+ method public int getBackpressureStrategy();
+ method public int getImageQueueDepth();
+ method public int getOutputImageFormat();
+ method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+ method public int getTargetRotation();
+ method public boolean isOutputImageRotationEnabled();
+ method public void setAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
+ method public void setTargetRotation(int);
+ field public static final int COORDINATE_SYSTEM_ORIGINAL = 0; // 0x0
+ field public static final int COORDINATE_SYSTEM_SENSOR = 2; // 0x2
+ field public static final int OUTPUT_IMAGE_FORMAT_RGBA_8888 = 2; // 0x2
+ field public static final int OUTPUT_IMAGE_FORMAT_YUV_420_888 = 1; // 0x1
+ field public static final int STRATEGY_BLOCK_PRODUCER = 1; // 0x1
+ field public static final int STRATEGY_KEEP_ONLY_LATEST = 0; // 0x0
+ }
+
+ public static interface ImageAnalysis.Analyzer {
+ method public void analyze(androidx.camera.core.ImageProxy);
+ method public default android.util.Size? getDefaultTargetResolution();
+ method public default int getTargetCoordinateSystem();
+ method public default void updateTransform(android.graphics.Matrix?);
+ }
+
+ public static final class ImageAnalysis.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageAnalysis> {
+ ctor public ImageAnalysis.Builder();
+ method public androidx.camera.core.ImageAnalysis build();
+ method public androidx.camera.core.ImageAnalysis.Builder setBackgroundExecutor(java.util.concurrent.Executor);
+ method public androidx.camera.core.ImageAnalysis.Builder setBackpressureStrategy(int);
+ method public androidx.camera.core.ImageAnalysis.Builder setImageQueueDepth(int);
+ method public androidx.camera.core.ImageAnalysis.Builder setOutputImageFormat(int);
+ method @RequiresApi(23) public androidx.camera.core.ImageAnalysis.Builder setOutputImageRotationEnabled(boolean);
+ method public androidx.camera.core.ImageAnalysis.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+ method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetAspectRatio(int);
+ method public androidx.camera.core.ImageAnalysis.Builder setTargetName(String);
+ method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetResolution(android.util.Size);
+ method public androidx.camera.core.ImageAnalysis.Builder setTargetRotation(int);
+ }
+
+ @RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
+ method public int getCaptureMode();
+ method public int getFlashMode();
+ method public static androidx.camera.core.ImageCaptureCapabilities getImageCaptureCapabilities(androidx.camera.core.CameraInfo);
+ method @IntRange(from=1, to=100) public int getJpegQuality();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector? getPostviewResolutionSelector();
+ method public androidx.camera.core.ImageCaptureLatencyEstimate getRealtimeCaptureLatencyEstimate();
+ method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+ method public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
+ method public int getTargetRotation();
+ method public boolean isPostviewEnabled();
+ method public void setCropAspectRatio(android.util.Rational);
+ method public void setFlashMode(int);
+ method public void setScreenFlash(androidx.camera.core.ImageCapture.ScreenFlash?);
+ method public void setTargetRotation(int);
+ method public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
+ method public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
+ field public static final int CAPTURE_MODE_MAXIMIZE_QUALITY = 0; // 0x0
+ field public static final int CAPTURE_MODE_MINIMIZE_LATENCY = 1; // 0x1
+ field @SuppressCompatibility @androidx.camera.core.ExperimentalZeroShutterLag public static final int CAPTURE_MODE_ZERO_SHUTTER_LAG = 2; // 0x2
+ field public static final int ERROR_CAMERA_CLOSED = 3; // 0x3
+ field public static final int ERROR_CAPTURE_FAILED = 2; // 0x2
+ field public static final int ERROR_FILE_IO = 1; // 0x1
+ field public static final int ERROR_INVALID_CAMERA = 4; // 0x4
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ field public static final int FLASH_MODE_AUTO = 0; // 0x0
+ field public static final int FLASH_MODE_OFF = 2; // 0x2
+ field public static final int FLASH_MODE_ON = 1; // 0x1
+ field public static final int FLASH_MODE_SCREEN = 3; // 0x3
+ }
+
+ public static final class ImageCapture.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageCapture> {
+ ctor public ImageCapture.Builder();
+ method public androidx.camera.core.ImageCapture build();
+ method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
+ method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
+ method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
+ method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
+ method public androidx.camera.core.ImageCapture.Builder setPostviewEnabled(boolean);
+ method public androidx.camera.core.ImageCapture.Builder setPostviewResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+ method public androidx.camera.core.ImageCapture.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+ method public androidx.camera.core.ImageCapture.Builder setScreenFlash(androidx.camera.core.ImageCapture.ScreenFlash);
+ method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
+ method public androidx.camera.core.ImageCapture.Builder setTargetName(String);
+ method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetResolution(android.util.Size);
+ method public androidx.camera.core.ImageCapture.Builder setTargetRotation(int);
+ }
+
+ public static final class ImageCapture.Metadata {
+ ctor public ImageCapture.Metadata();
+ method public android.location.Location? getLocation();
+ method public boolean isReversedHorizontal();
+ method public boolean isReversedVertical();
+ method public void setLocation(android.location.Location?);
+ method public void setReversedHorizontal(boolean);
+ method public void setReversedVertical(boolean);
+ }
+
+ public abstract static class ImageCapture.OnImageCapturedCallback {
+ ctor public ImageCapture.OnImageCapturedCallback();
+ method public void onCaptureProcessProgressed(int);
+ method public void onCaptureStarted();
+ method public void onCaptureSuccess(androidx.camera.core.ImageProxy);
+ method public void onError(androidx.camera.core.ImageCaptureException);
+ method public void onPostviewBitmapAvailable(android.graphics.Bitmap);
+ }
+
+ public static interface ImageCapture.OnImageSavedCallback {
+ method public default void onCaptureProcessProgressed(int);
+ method public default void onCaptureStarted();
+ method public void onError(androidx.camera.core.ImageCaptureException);
+ method public void onImageSaved(androidx.camera.core.ImageCapture.OutputFileResults);
+ method public default void onPostviewBitmapAvailable(android.graphics.Bitmap);
+ }
+
+ public static final class ImageCapture.OutputFileOptions {
+ }
+
+ public static final class ImageCapture.OutputFileOptions.Builder {
+ ctor public ImageCapture.OutputFileOptions.Builder(android.content.ContentResolver, android.net.Uri, android.content.ContentValues);
+ ctor public ImageCapture.OutputFileOptions.Builder(java.io.File);
+ ctor public ImageCapture.OutputFileOptions.Builder(java.io.OutputStream);
+ method public androidx.camera.core.ImageCapture.OutputFileOptions build();
+ method public androidx.camera.core.ImageCapture.OutputFileOptions.Builder setMetadata(androidx.camera.core.ImageCapture.Metadata);
+ }
+
+ public static class ImageCapture.OutputFileResults {
+ method public android.net.Uri? getSavedUri();
+ }
+
+ public static interface ImageCapture.ScreenFlash {
+ method @UiThread public void apply(long, androidx.camera.core.ImageCapture.ScreenFlashListener);
+ method @UiThread public void clear();
+ }
+
+ public static interface ImageCapture.ScreenFlashListener {
+ method public void onCompleted();
+ }
+
+ public interface ImageCaptureCapabilities {
+ method public boolean isCaptureProcessProgressSupported();
+ method public boolean isPostviewSupported();
+ }
+
+ @RequiresApi(21) public class ImageCaptureException extends java.lang.Exception {
+ ctor public ImageCaptureException(int, String, Throwable?);
+ method public int getImageCaptureError();
+ }
+
+ @RequiresApi(21) public class ImageCaptureLatencyEstimate {
+ method public long getCaptureLatencyMillis();
+ method public long getProcessingLatencyMillis();
+ method public long getTotalCaptureLatencyMillis();
+ field public static final long UNDEFINED_CAPTURE_LATENCY = -1L; // 0xffffffffffffffffL
+ field public static final androidx.camera.core.ImageCaptureLatencyEstimate UNDEFINED_IMAGE_CAPTURE_LATENCY;
+ field public static final long UNDEFINED_PROCESSING_LATENCY = -1L; // 0xffffffffffffffffL
+ }
+
+ @RequiresApi(21) public interface ImageInfo {
+ method public int getRotationDegrees();
+ method public default android.graphics.Matrix getSensorToBufferTransformMatrix();
+ method public long getTimestamp();
+ }
+
+ public interface ImageProcessor {
+ method public androidx.camera.core.ImageProcessor.Response process(androidx.camera.core.ImageProcessor.Request) throws androidx.camera.core.ProcessingException;
+ }
+
+ public static interface ImageProcessor.Request {
+ method public androidx.camera.core.ImageProxy getInputImage();
+ method public int getOutputFormat();
+ }
+
+ public static interface ImageProcessor.Response {
+ method public androidx.camera.core.ImageProxy getOutputImage();
+ }
+
+ @RequiresApi(21) public interface ImageProxy extends java.lang.AutoCloseable {
+ method public void close();
+ method public android.graphics.Rect getCropRect();
+ method public int getFormat();
+ method public int getHeight();
+ method @SuppressCompatibility @androidx.camera.core.ExperimentalGetImage public android.media.Image? getImage();
+ method public androidx.camera.core.ImageInfo getImageInfo();
+ method public androidx.camera.core.ImageProxy.PlaneProxy![] getPlanes();
+ method public int getWidth();
+ method public void setCropRect(android.graphics.Rect?);
+ method public default android.graphics.Bitmap toBitmap();
+ }
+
+ public static interface ImageProxy.PlaneProxy {
+ method public java.nio.ByteBuffer getBuffer();
+ method public int getPixelStride();
+ method public int getRowStride();
+ }
+
+ @RequiresApi(21) public class InitializationException extends java.lang.Exception {
+ ctor public InitializationException(String?);
+ ctor public InitializationException(String?, Throwable?);
+ ctor public InitializationException(Throwable?);
+ }
+
+ @RequiresApi(21) public class MeteringPoint {
+ method public float getSize();
+ }
+
+ @RequiresApi(21) public abstract class MeteringPointFactory {
+ method public final androidx.camera.core.MeteringPoint createPoint(float, float);
+ method public final androidx.camera.core.MeteringPoint createPoint(float, float, float);
+ method public static float getDefaultPointSize();
+ }
+
+ @RequiresApi(21) public class MirrorMode {
+ field public static final int MIRROR_MODE_OFF = 0; // 0x0
+ field public static final int MIRROR_MODE_ON = 1; // 0x1
+ field public static final int MIRROR_MODE_ON_FRONT_ONLY = 2; // 0x2
+ }
+
+ @RequiresApi(21) public final class Preview extends androidx.camera.core.UseCase {
+ method public androidx.camera.core.DynamicRange getDynamicRange();
+ method public static androidx.camera.core.PreviewCapabilities getPreviewCapabilities(androidx.camera.core.CameraInfo);
+ method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+ method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
+ method public int getTargetRotation();
+ method public boolean isPreviewStabilizationEnabled();
+ method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
+ method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
+ method public void setTargetRotation(int);
+ }
+
+ public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview> {
+ ctor public Preview.Builder();
+ method public androidx.camera.core.Preview build();
+ method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+ method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
+ method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+ method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
+ method public androidx.camera.core.Preview.Builder setTargetFrameRate(android.util.Range<java.lang.Integer!>);
+ method public androidx.camera.core.Preview.Builder setTargetName(String);
+ method @Deprecated public androidx.camera.core.Preview.Builder setTargetResolution(android.util.Size);
+ method public androidx.camera.core.Preview.Builder setTargetRotation(int);
+ }
+
+ public static interface Preview.SurfaceProvider {
+ method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+ }
+
+ @RequiresApi(21) public interface PreviewCapabilities {
+ method public boolean isStabilizationSupported();
+ }
+
+ public class ProcessingException extends java.lang.Exception {
+ ctor public ProcessingException();
+ }
+
+ @RequiresApi(21) public class ResolutionInfo {
+ ctor public ResolutionInfo(android.util.Size, android.graphics.Rect, int);
+ method public android.graphics.Rect getCropRect();
+ method public android.util.Size getResolution();
+ method public int getRotationDegrees();
+ }
+
+ @RequiresApi(21) public class SurfaceOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
+ ctor public SurfaceOrientedMeteringPointFactory(float, float);
+ ctor public SurfaceOrientedMeteringPointFactory(float, float, androidx.camera.core.UseCase);
+ }
+
+ public interface SurfaceOutput extends java.io.Closeable {
+ method public void close();
+ method public default android.graphics.Matrix getSensorToBufferTransform();
+ method public android.util.Size getSize();
+ method public android.view.Surface getSurface(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceOutput.Event!>);
+ method public int getTargets();
+ method public void updateTransformMatrix(float[], float[]);
+ }
+
+ @com.google.auto.value.AutoValue public abstract static class SurfaceOutput.Event {
+ method public abstract int getEventCode();
+ method public abstract androidx.camera.core.SurfaceOutput getSurfaceOutput();
+ field public static final int EVENT_REQUEST_CLOSE = 0; // 0x0
+ }
+
+ public interface SurfaceProcessor {
+ method public void onInputSurface(androidx.camera.core.SurfaceRequest) throws androidx.camera.core.ProcessingException;
+ method public void onOutputSurface(androidx.camera.core.SurfaceOutput) throws androidx.camera.core.ProcessingException;
+ }
+
+ @RequiresApi(21) public final class SurfaceRequest {
+ method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
+ method public void clearTransformationInfoListener();
+ method public androidx.camera.core.DynamicRange getDynamicRange();
+ method public android.util.Size getResolution();
+ method public boolean invalidate();
+ method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
+ method public void setTransformationInfoListener(java.util.concurrent.Executor, androidx.camera.core.SurfaceRequest.TransformationInfoListener);
+ method public boolean willNotProvideSurface();
+ }
+
+ @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.Result {
+ method public abstract int getResultCode();
+ method public abstract android.view.Surface getSurface();
+ field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
+ field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
+ field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
+ field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
+ field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
+ }
+
+ @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.TransformationInfo {
+ method public abstract android.graphics.Rect getCropRect();
+ method public abstract int getRotationDegrees();
+ method public abstract android.graphics.Matrix getSensorToBufferTransform();
+ method public abstract boolean hasCameraTransform();
+ method public abstract boolean isMirroring();
+ }
+
+ public static interface SurfaceRequest.TransformationInfoListener {
+ method public void onTransformationInfoUpdate(androidx.camera.core.SurfaceRequest.TransformationInfo);
+ }
+
+ @RequiresApi(21) public class TorchState {
+ field public static final int OFF = 0; // 0x0
+ field public static final int ON = 1; // 0x1
+ }
+
+ @RequiresApi(21) public abstract class UseCase {
+ method public static int snapToSurfaceRotation(@IntRange(from=0, to=359) int);
+ }
+
+ @RequiresApi(21) public final class UseCaseGroup {
+ method public java.util.List<androidx.camera.core.CameraEffect!> getEffects();
+ method public java.util.List<androidx.camera.core.UseCase!> getUseCases();
+ method public androidx.camera.core.ViewPort? getViewPort();
+ }
+
+ public static final class UseCaseGroup.Builder {
+ ctor public UseCaseGroup.Builder();
+ method public androidx.camera.core.UseCaseGroup.Builder addEffect(androidx.camera.core.CameraEffect);
+ method public androidx.camera.core.UseCaseGroup.Builder addUseCase(androidx.camera.core.UseCase);
+ method public androidx.camera.core.UseCaseGroup build();
+ method public androidx.camera.core.UseCaseGroup.Builder setViewPort(androidx.camera.core.ViewPort);
+ }
+
+ @RequiresApi(21) public final class ViewPort {
+ method public android.util.Rational getAspectRatio();
+ method public int getLayoutDirection();
+ method public int getRotation();
+ method public int getScaleType();
+ field public static final int FILL_CENTER = 1; // 0x1
+ field public static final int FILL_END = 2; // 0x2
+ field public static final int FILL_START = 0; // 0x0
+ field public static final int FIT = 3; // 0x3
+ }
+
+ public static final class ViewPort.Builder {
+ ctor public ViewPort.Builder(android.util.Rational, int);
+ method public androidx.camera.core.ViewPort build();
+ method public androidx.camera.core.ViewPort.Builder setLayoutDirection(int);
+ method public androidx.camera.core.ViewPort.Builder setScaleType(int);
+ }
+
+ @RequiresApi(21) public interface ZoomState {
+ method public float getLinearZoom();
+ method public float getMaxZoomRatio();
+ method public float getMinZoomRatio();
+ method public float getZoomRatio();
+ }
+
+}
+
+package androidx.camera.core.resolutionselector {
+
+ @RequiresApi(21) public final class AspectRatioStrategy {
+ ctor public AspectRatioStrategy(int, int);
+ method public int getFallbackRule();
+ method public int getPreferredAspectRatio();
+ field public static final int FALLBACK_RULE_AUTO = 1; // 0x1
+ field public static final int FALLBACK_RULE_NONE = 0; // 0x0
+ field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_16_9_FALLBACK_AUTO_STRATEGY;
+ field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_4_3_FALLBACK_AUTO_STRATEGY;
+ }
+
+ @RequiresApi(21) public interface ResolutionFilter {
+ method public java.util.List<android.util.Size!> filter(java.util.List<android.util.Size!>, int);
+ }
+
+ @RequiresApi(21) public final class ResolutionSelector {
+ method public int getAllowedResolutionMode();
+ method public androidx.camera.core.resolutionselector.AspectRatioStrategy getAspectRatioStrategy();
+ method public androidx.camera.core.resolutionselector.ResolutionFilter? getResolutionFilter();
+ method public androidx.camera.core.resolutionselector.ResolutionStrategy? getResolutionStrategy();
+ field public static final int PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION = 0; // 0x0
+ field public static final int PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE = 1; // 0x1
+ }
+
+ public static final class ResolutionSelector.Builder {
+ ctor public ResolutionSelector.Builder();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector build();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAllowedResolutionMode(int);
+ method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAspectRatioStrategy(androidx.camera.core.resolutionselector.AspectRatioStrategy);
+ method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionFilter(androidx.camera.core.resolutionselector.ResolutionFilter);
+ method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionStrategy(androidx.camera.core.resolutionselector.ResolutionStrategy);
+ }
+
+ @RequiresApi(21) public final class ResolutionStrategy {
+ ctor public ResolutionStrategy(android.util.Size, int);
+ method public android.util.Size? getBoundSize();
+ method public int getFallbackRule();
+ field public static final int FALLBACK_RULE_CLOSEST_HIGHER = 2; // 0x2
+ field public static final int FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER = 1; // 0x1
+ field public static final int FALLBACK_RULE_CLOSEST_LOWER = 4; // 0x4
+ field public static final int FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER = 3; // 0x3
+ field public static final int FALLBACK_RULE_NONE = 0; // 0x0
+ field public static final androidx.camera.core.resolutionselector.ResolutionStrategy HIGHEST_AVAILABLE_STRATEGY;
+ }
+
+}
+
diff --git a/camera/camera-core/api/res-1.4.0-beta01.txt b/camera/camera-core/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-core/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-core/api/restricted_1.4.0-beta01.txt b/camera/camera-core/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..a61f5a4
--- /dev/null
+++ b/camera/camera-core/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,669 @@
+// Signature format: 4.0
+package androidx.camera.core {
+
+ @RequiresApi(21) public class AspectRatio {
+ field public static final int RATIO_16_9 = 1; // 0x1
+ field public static final int RATIO_4_3 = 0; // 0x0
+ field public static final int RATIO_DEFAULT = -1; // 0xffffffff
+ }
+
+ @RequiresApi(21) public interface Camera {
+ method public androidx.camera.core.CameraControl getCameraControl();
+ method public androidx.camera.core.CameraInfo getCameraInfo();
+ }
+
+ @RequiresApi(21) public interface CameraControl {
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> cancelFocusAndMetering();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> setExposureCompensationIndex(int);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.camera.core.FocusMeteringResult!> startFocusAndMetering(androidx.camera.core.FocusMeteringAction);
+ }
+
+ public static final class CameraControl.OperationCanceledException extends java.lang.Exception {
+ }
+
+ @RequiresApi(21) public abstract class CameraEffect {
+ ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.ImageProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
+ ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.SurfaceProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
+ method public androidx.core.util.Consumer<java.lang.Throwable!> getErrorListener();
+ method public java.util.concurrent.Executor getExecutor();
+ method public androidx.camera.core.SurfaceProcessor? getSurfaceProcessor();
+ method public int getTargets();
+ field public static final int IMAGE_CAPTURE = 4; // 0x4
+ field public static final int PREVIEW = 1; // 0x1
+ field public static final int VIDEO_CAPTURE = 2; // 0x2
+ }
+
+ @RequiresApi(21) public interface CameraFilter {
+ method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
+ }
+
+ @RequiresApi(21) public interface CameraInfo {
+ method public androidx.camera.core.CameraSelector getCameraSelector();
+ method public androidx.lifecycle.LiveData<androidx.camera.core.CameraState!> getCameraState();
+ method public androidx.camera.core.ExposureState getExposureState();
+ method @FloatRange(from=0, fromInclusive=false) public default float getIntrinsicZoomRatio();
+ method public default int getLensFacing();
+ method public int getSensorRotationDegrees();
+ method public int getSensorRotationDegrees(int);
+ method public default java.util.Set<android.util.Range<java.lang.Integer!>!> getSupportedFrameRateRanges();
+ method public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
+ method public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
+ method public boolean hasFlashUnit();
+ method public default boolean isFocusMeteringSupported(androidx.camera.core.FocusMeteringAction);
+ method @SuppressCompatibility @androidx.camera.core.ExperimentalZeroShutterLag public default boolean isZslSupported();
+ method public static boolean mustPlayShutterSound();
+ method public default java.util.Set<androidx.camera.core.DynamicRange!> querySupportedDynamicRanges(java.util.Set<androidx.camera.core.DynamicRange!>);
+ }
+
+ @RequiresApi(21) public final class CameraInfoUnavailableException extends java.lang.Exception {
+ }
+
+ @RequiresApi(21) public interface CameraProvider {
+ method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
+ method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+ }
+
+ @RequiresApi(21) public final class CameraSelector {
+ method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
+ field public static final androidx.camera.core.CameraSelector DEFAULT_BACK_CAMERA;
+ field public static final androidx.camera.core.CameraSelector DEFAULT_FRONT_CAMERA;
+ field public static final int LENS_FACING_BACK = 1; // 0x1
+ field @SuppressCompatibility @androidx.camera.core.ExperimentalLensFacing public static final int LENS_FACING_EXTERNAL = 2; // 0x2
+ field public static final int LENS_FACING_FRONT = 0; // 0x0
+ field public static final int LENS_FACING_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public static final class CameraSelector.Builder {
+ ctor public CameraSelector.Builder();
+ method public androidx.camera.core.CameraSelector.Builder addCameraFilter(androidx.camera.core.CameraFilter);
+ method public androidx.camera.core.CameraSelector build();
+ method public androidx.camera.core.CameraSelector.Builder requireLensFacing(int);
+ }
+
+ @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class CameraState {
+ ctor public CameraState();
+ method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type);
+ method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type, androidx.camera.core.CameraState.StateError?);
+ method public abstract androidx.camera.core.CameraState.StateError? getError();
+ method public abstract androidx.camera.core.CameraState.Type getType();
+ field public static final int ERROR_CAMERA_DISABLED = 5; // 0x5
+ field public static final int ERROR_CAMERA_FATAL_ERROR = 6; // 0x6
+ field public static final int ERROR_CAMERA_IN_USE = 2; // 0x2
+ field public static final int ERROR_DO_NOT_DISTURB_MODE_ENABLED = 7; // 0x7
+ field public static final int ERROR_MAX_CAMERAS_IN_USE = 1; // 0x1
+ field public static final int ERROR_OTHER_RECOVERABLE_ERROR = 3; // 0x3
+ field public static final int ERROR_STREAM_CONFIG = 4; // 0x4
+ }
+
+ public enum CameraState.ErrorType {
+ enum_constant public static final androidx.camera.core.CameraState.ErrorType CRITICAL;
+ enum_constant public static final androidx.camera.core.CameraState.ErrorType RECOVERABLE;
+ }
+
+ @com.google.auto.value.AutoValue public abstract static class CameraState.StateError {
+ ctor public CameraState.StateError();
+ method public static androidx.camera.core.CameraState.StateError create(int);
+ method public static androidx.camera.core.CameraState.StateError create(int, Throwable?);
+ method public abstract Throwable? getCause();
+ method public abstract int getCode();
+ method public androidx.camera.core.CameraState.ErrorType getType();
+ }
+
+ public enum CameraState.Type {
+ enum_constant public static final androidx.camera.core.CameraState.Type CLOSED;
+ enum_constant public static final androidx.camera.core.CameraState.Type CLOSING;
+ enum_constant public static final androidx.camera.core.CameraState.Type OPEN;
+ enum_constant public static final androidx.camera.core.CameraState.Type OPENING;
+ enum_constant public static final androidx.camera.core.CameraState.Type PENDING_OPEN;
+ }
+
+ @RequiresApi(21) public class CameraUnavailableException extends java.lang.Exception {
+ ctor public CameraUnavailableException(int);
+ ctor public CameraUnavailableException(int, String?);
+ ctor public CameraUnavailableException(int, String?, Throwable?);
+ ctor public CameraUnavailableException(int, Throwable?);
+ method public int getReason();
+ field public static final int CAMERA_DISABLED = 1; // 0x1
+ field public static final int CAMERA_DISCONNECTED = 2; // 0x2
+ field public static final int CAMERA_ERROR = 3; // 0x3
+ field public static final int CAMERA_IN_USE = 4; // 0x4
+ field public static final int CAMERA_MAX_IN_USE = 5; // 0x5
+ field public static final int CAMERA_UNAVAILABLE_DO_NOT_DISTURB = 6; // 0x6
+ field public static final int CAMERA_UNKNOWN_ERROR = 0; // 0x0
+ }
+
+ @RequiresApi(21) public final class CameraXConfig {
+ method public androidx.camera.core.CameraSelector? getAvailableCamerasLimiter(androidx.camera.core.CameraSelector?);
+ method public java.util.concurrent.Executor? getCameraExecutor(java.util.concurrent.Executor?);
+ method public long getCameraOpenRetryMaxTimeoutInMillisWhileResuming();
+ method public int getMinimumLoggingLevel();
+ method public android.os.Handler? getSchedulerHandler(android.os.Handler?);
+ }
+
+ public static final class CameraXConfig.Builder {
+ method public androidx.camera.core.CameraXConfig build();
+ method public static androidx.camera.core.CameraXConfig.Builder fromConfig(androidx.camera.core.CameraXConfig);
+ method public androidx.camera.core.CameraXConfig.Builder setAvailableCamerasLimiter(androidx.camera.core.CameraSelector);
+ method public androidx.camera.core.CameraXConfig.Builder setCameraExecutor(java.util.concurrent.Executor);
+ method public androidx.camera.core.CameraXConfig.Builder setCameraOpenRetryMaxTimeoutInMillisWhileResuming(long);
+ method public androidx.camera.core.CameraXConfig.Builder setMinimumLoggingLevel(@IntRange(from=android.util.Log.DEBUG, to=android.util.Log.ERROR) int);
+ method public androidx.camera.core.CameraXConfig.Builder setSchedulerHandler(android.os.Handler);
+ }
+
+ public static interface CameraXConfig.Provider {
+ method public androidx.camera.core.CameraXConfig getCameraXConfig();
+ }
+
+ @RequiresApi(21) public class ConcurrentCamera {
+ ctor public ConcurrentCamera(java.util.List<androidx.camera.core.Camera!>);
+ method public java.util.List<androidx.camera.core.Camera!> getCameras();
+ }
+
+ public static final class ConcurrentCamera.SingleCameraConfig {
+ ctor public ConcurrentCamera.SingleCameraConfig(androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup, androidx.lifecycle.LifecycleOwner);
+ method public androidx.camera.core.CameraSelector getCameraSelector();
+ method public androidx.lifecycle.LifecycleOwner getLifecycleOwner();
+ method public androidx.camera.core.UseCaseGroup getUseCaseGroup();
+ }
+
+ @RequiresApi(21) public final class DisplayOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
+ ctor public DisplayOrientedMeteringPointFactory(android.view.Display, androidx.camera.core.CameraInfo, float, float);
+ }
+
+ @RequiresApi(21) public final class DynamicRange {
+ ctor public DynamicRange(int, int);
+ method public int getBitDepth();
+ method public int getEncoding();
+ field public static final int BIT_DEPTH_10_BIT = 10; // 0xa
+ field public static final int BIT_DEPTH_8_BIT = 8; // 0x8
+ field public static final int BIT_DEPTH_UNSPECIFIED = 0; // 0x0
+ field public static final androidx.camera.core.DynamicRange DOLBY_VISION_10_BIT;
+ field public static final androidx.camera.core.DynamicRange DOLBY_VISION_8_BIT;
+ field public static final int ENCODING_DOLBY_VISION = 6; // 0x6
+ field public static final int ENCODING_HDR10 = 4; // 0x4
+ field public static final int ENCODING_HDR10_PLUS = 5; // 0x5
+ field public static final int ENCODING_HDR_UNSPECIFIED = 2; // 0x2
+ field public static final int ENCODING_HLG = 3; // 0x3
+ field public static final int ENCODING_SDR = 1; // 0x1
+ field public static final int ENCODING_UNSPECIFIED = 0; // 0x0
+ field public static final androidx.camera.core.DynamicRange HDR10_10_BIT;
+ field public static final androidx.camera.core.DynamicRange HDR10_PLUS_10_BIT;
+ field public static final androidx.camera.core.DynamicRange HDR_UNSPECIFIED_10_BIT;
+ field public static final androidx.camera.core.DynamicRange HLG_10_BIT;
+ field public static final androidx.camera.core.DynamicRange SDR;
+ field public static final androidx.camera.core.DynamicRange UNSPECIFIED;
+ }
+
+ @SuppressCompatibility @RequiresApi(21) @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseApi {
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalZeroShutterLag {
+ }
+
+ @RequiresApi(21) public interface ExposureState {
+ method public int getExposureCompensationIndex();
+ method public android.util.Range<java.lang.Integer!> getExposureCompensationRange();
+ method public android.util.Rational getExposureCompensationStep();
+ method public boolean isExposureCompensationSupported();
+ }
+
+ @RequiresApi(21) public interface ExtendableBuilder<T> {
+ method public T build();
+ }
+
+ @RequiresApi(21) public final class FocusMeteringAction {
+ method public long getAutoCancelDurationInMillis();
+ method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAe();
+ method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAf();
+ method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAwb();
+ method public boolean isAutoCancelEnabled();
+ field public static final int FLAG_AE = 2; // 0x2
+ field public static final int FLAG_AF = 1; // 0x1
+ field public static final int FLAG_AWB = 4; // 0x4
+ }
+
+ public static class FocusMeteringAction.Builder {
+ ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint);
+ ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint, int);
+ method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint);
+ method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint, int);
+ method public androidx.camera.core.FocusMeteringAction build();
+ method public androidx.camera.core.FocusMeteringAction.Builder disableAutoCancel();
+ method public androidx.camera.core.FocusMeteringAction.Builder setAutoCancelDuration(@IntRange(from=1) long, java.util.concurrent.TimeUnit);
+ }
+
+ @RequiresApi(21) public final class FocusMeteringResult {
+ method public boolean isFocusSuccessful();
+ }
+
+ @RequiresApi(21) public final class ImageAnalysis extends androidx.camera.core.UseCase {
+ method public void clearAnalyzer();
+ method @SuppressCompatibility @androidx.camera.core.ExperimentalUseCaseApi public java.util.concurrent.Executor? getBackgroundExecutor();
+ method public int getBackpressureStrategy();
+ method public int getImageQueueDepth();
+ method public int getOutputImageFormat();
+ method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+ method public int getTargetRotation();
+ method public boolean isOutputImageRotationEnabled();
+ method public void setAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
+ method public void setTargetRotation(int);
+ field public static final int COORDINATE_SYSTEM_ORIGINAL = 0; // 0x0
+ field public static final int COORDINATE_SYSTEM_SENSOR = 2; // 0x2
+ field public static final int OUTPUT_IMAGE_FORMAT_RGBA_8888 = 2; // 0x2
+ field public static final int OUTPUT_IMAGE_FORMAT_YUV_420_888 = 1; // 0x1
+ field public static final int STRATEGY_BLOCK_PRODUCER = 1; // 0x1
+ field public static final int STRATEGY_KEEP_ONLY_LATEST = 0; // 0x0
+ }
+
+ public static interface ImageAnalysis.Analyzer {
+ method public void analyze(androidx.camera.core.ImageProxy);
+ method public default android.util.Size? getDefaultTargetResolution();
+ method public default int getTargetCoordinateSystem();
+ method public default void updateTransform(android.graphics.Matrix?);
+ }
+
+ public static final class ImageAnalysis.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageAnalysis> {
+ ctor public ImageAnalysis.Builder();
+ method public androidx.camera.core.ImageAnalysis build();
+ method public androidx.camera.core.ImageAnalysis.Builder setBackgroundExecutor(java.util.concurrent.Executor);
+ method public androidx.camera.core.ImageAnalysis.Builder setBackpressureStrategy(int);
+ method public androidx.camera.core.ImageAnalysis.Builder setImageQueueDepth(int);
+ method public androidx.camera.core.ImageAnalysis.Builder setOutputImageFormat(int);
+ method @RequiresApi(23) public androidx.camera.core.ImageAnalysis.Builder setOutputImageRotationEnabled(boolean);
+ method public androidx.camera.core.ImageAnalysis.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+ method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetAspectRatio(int);
+ method public androidx.camera.core.ImageAnalysis.Builder setTargetName(String);
+ method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetResolution(android.util.Size);
+ method public androidx.camera.core.ImageAnalysis.Builder setTargetRotation(int);
+ }
+
+ @RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
+ method public int getCaptureMode();
+ method public int getFlashMode();
+ method public static androidx.camera.core.ImageCaptureCapabilities getImageCaptureCapabilities(androidx.camera.core.CameraInfo);
+ method @IntRange(from=1, to=100) public int getJpegQuality();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector? getPostviewResolutionSelector();
+ method public androidx.camera.core.ImageCaptureLatencyEstimate getRealtimeCaptureLatencyEstimate();
+ method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+ method public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
+ method public int getTargetRotation();
+ method public boolean isPostviewEnabled();
+ method public void setCropAspectRatio(android.util.Rational);
+ method public void setFlashMode(int);
+ method public void setScreenFlash(androidx.camera.core.ImageCapture.ScreenFlash?);
+ method public void setTargetRotation(int);
+ method public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
+ method public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
+ field public static final int CAPTURE_MODE_MAXIMIZE_QUALITY = 0; // 0x0
+ field public static final int CAPTURE_MODE_MINIMIZE_LATENCY = 1; // 0x1
+ field @SuppressCompatibility @androidx.camera.core.ExperimentalZeroShutterLag public static final int CAPTURE_MODE_ZERO_SHUTTER_LAG = 2; // 0x2
+ field public static final int ERROR_CAMERA_CLOSED = 3; // 0x3
+ field public static final int ERROR_CAPTURE_FAILED = 2; // 0x2
+ field public static final int ERROR_FILE_IO = 1; // 0x1
+ field public static final int ERROR_INVALID_CAMERA = 4; // 0x4
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ field public static final int FLASH_MODE_AUTO = 0; // 0x0
+ field public static final int FLASH_MODE_OFF = 2; // 0x2
+ field public static final int FLASH_MODE_ON = 1; // 0x1
+ field public static final int FLASH_MODE_SCREEN = 3; // 0x3
+ }
+
+ public static final class ImageCapture.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageCapture> {
+ ctor public ImageCapture.Builder();
+ method public androidx.camera.core.ImageCapture build();
+ method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
+ method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
+ method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
+ method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
+ method public androidx.camera.core.ImageCapture.Builder setPostviewEnabled(boolean);
+ method public androidx.camera.core.ImageCapture.Builder setPostviewResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+ method public androidx.camera.core.ImageCapture.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+ method public androidx.camera.core.ImageCapture.Builder setScreenFlash(androidx.camera.core.ImageCapture.ScreenFlash);
+ method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
+ method public androidx.camera.core.ImageCapture.Builder setTargetName(String);
+ method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetResolution(android.util.Size);
+ method public androidx.camera.core.ImageCapture.Builder setTargetRotation(int);
+ }
+
+ public static final class ImageCapture.Metadata {
+ ctor public ImageCapture.Metadata();
+ method public android.location.Location? getLocation();
+ method public boolean isReversedHorizontal();
+ method public boolean isReversedVertical();
+ method public void setLocation(android.location.Location?);
+ method public void setReversedHorizontal(boolean);
+ method public void setReversedVertical(boolean);
+ }
+
+ public abstract static class ImageCapture.OnImageCapturedCallback {
+ ctor public ImageCapture.OnImageCapturedCallback();
+ method public void onCaptureProcessProgressed(int);
+ method public void onCaptureStarted();
+ method public void onCaptureSuccess(androidx.camera.core.ImageProxy);
+ method public void onError(androidx.camera.core.ImageCaptureException);
+ method public void onPostviewBitmapAvailable(android.graphics.Bitmap);
+ }
+
+ public static interface ImageCapture.OnImageSavedCallback {
+ method public default void onCaptureProcessProgressed(int);
+ method public default void onCaptureStarted();
+ method public void onError(androidx.camera.core.ImageCaptureException);
+ method public void onImageSaved(androidx.camera.core.ImageCapture.OutputFileResults);
+ method public default void onPostviewBitmapAvailable(android.graphics.Bitmap);
+ }
+
+ public static final class ImageCapture.OutputFileOptions {
+ }
+
+ public static final class ImageCapture.OutputFileOptions.Builder {
+ ctor public ImageCapture.OutputFileOptions.Builder(android.content.ContentResolver, android.net.Uri, android.content.ContentValues);
+ ctor public ImageCapture.OutputFileOptions.Builder(java.io.File);
+ ctor public ImageCapture.OutputFileOptions.Builder(java.io.OutputStream);
+ method public androidx.camera.core.ImageCapture.OutputFileOptions build();
+ method public androidx.camera.core.ImageCapture.OutputFileOptions.Builder setMetadata(androidx.camera.core.ImageCapture.Metadata);
+ }
+
+ public static class ImageCapture.OutputFileResults {
+ method public android.net.Uri? getSavedUri();
+ }
+
+ public static interface ImageCapture.ScreenFlash {
+ method @UiThread public void apply(long, androidx.camera.core.ImageCapture.ScreenFlashListener);
+ method @UiThread public void clear();
+ }
+
+ public static interface ImageCapture.ScreenFlashListener {
+ method public void onCompleted();
+ }
+
+ public interface ImageCaptureCapabilities {
+ method public boolean isCaptureProcessProgressSupported();
+ method public boolean isPostviewSupported();
+ }
+
+ @RequiresApi(21) public class ImageCaptureException extends java.lang.Exception {
+ ctor public ImageCaptureException(int, String, Throwable?);
+ method public int getImageCaptureError();
+ }
+
+ @RequiresApi(21) public class ImageCaptureLatencyEstimate {
+ method public long getCaptureLatencyMillis();
+ method public long getProcessingLatencyMillis();
+ method public long getTotalCaptureLatencyMillis();
+ field public static final long UNDEFINED_CAPTURE_LATENCY = -1L; // 0xffffffffffffffffL
+ field public static final androidx.camera.core.ImageCaptureLatencyEstimate UNDEFINED_IMAGE_CAPTURE_LATENCY;
+ field public static final long UNDEFINED_PROCESSING_LATENCY = -1L; // 0xffffffffffffffffL
+ }
+
+ @RequiresApi(21) public interface ImageInfo {
+ method public int getRotationDegrees();
+ method public default android.graphics.Matrix getSensorToBufferTransformMatrix();
+ method public long getTimestamp();
+ }
+
+ public interface ImageProcessor {
+ method public androidx.camera.core.ImageProcessor.Response process(androidx.camera.core.ImageProcessor.Request) throws androidx.camera.core.ProcessingException;
+ }
+
+ public static interface ImageProcessor.Request {
+ method public androidx.camera.core.ImageProxy getInputImage();
+ method public int getOutputFormat();
+ }
+
+ public static interface ImageProcessor.Response {
+ method public androidx.camera.core.ImageProxy getOutputImage();
+ }
+
+ @RequiresApi(21) public interface ImageProxy extends java.lang.AutoCloseable {
+ method public void close();
+ method public android.graphics.Rect getCropRect();
+ method public int getFormat();
+ method public int getHeight();
+ method @SuppressCompatibility @androidx.camera.core.ExperimentalGetImage public android.media.Image? getImage();
+ method public androidx.camera.core.ImageInfo getImageInfo();
+ method public androidx.camera.core.ImageProxy.PlaneProxy![] getPlanes();
+ method public int getWidth();
+ method public void setCropRect(android.graphics.Rect?);
+ method public default android.graphics.Bitmap toBitmap();
+ }
+
+ public static interface ImageProxy.PlaneProxy {
+ method public java.nio.ByteBuffer getBuffer();
+ method public int getPixelStride();
+ method public int getRowStride();
+ }
+
+ @RequiresApi(21) public class InitializationException extends java.lang.Exception {
+ ctor public InitializationException(String?);
+ ctor public InitializationException(String?, Throwable?);
+ ctor public InitializationException(Throwable?);
+ }
+
+ @RequiresApi(21) public class MeteringPoint {
+ method public float getSize();
+ }
+
+ @RequiresApi(21) public abstract class MeteringPointFactory {
+ method public final androidx.camera.core.MeteringPoint createPoint(float, float);
+ method public final androidx.camera.core.MeteringPoint createPoint(float, float, float);
+ method public static float getDefaultPointSize();
+ }
+
+ @RequiresApi(21) public class MirrorMode {
+ field public static final int MIRROR_MODE_OFF = 0; // 0x0
+ field public static final int MIRROR_MODE_ON = 1; // 0x1
+ field public static final int MIRROR_MODE_ON_FRONT_ONLY = 2; // 0x2
+ }
+
+ @RequiresApi(21) public final class Preview extends androidx.camera.core.UseCase {
+ method public androidx.camera.core.DynamicRange getDynamicRange();
+ method public static androidx.camera.core.PreviewCapabilities getPreviewCapabilities(androidx.camera.core.CameraInfo);
+ method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+ method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
+ method public int getTargetRotation();
+ method public boolean isPreviewStabilizationEnabled();
+ method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
+ method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
+ method public void setTargetRotation(int);
+ }
+
+ public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview> {
+ ctor public Preview.Builder();
+ method public androidx.camera.core.Preview build();
+ method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+ method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
+ method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+ method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
+ method public androidx.camera.core.Preview.Builder setTargetFrameRate(android.util.Range<java.lang.Integer!>);
+ method public androidx.camera.core.Preview.Builder setTargetName(String);
+ method @Deprecated public androidx.camera.core.Preview.Builder setTargetResolution(android.util.Size);
+ method public androidx.camera.core.Preview.Builder setTargetRotation(int);
+ }
+
+ public static interface Preview.SurfaceProvider {
+ method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+ }
+
+ @RequiresApi(21) public interface PreviewCapabilities {
+ method public boolean isStabilizationSupported();
+ }
+
+ public class ProcessingException extends java.lang.Exception {
+ ctor public ProcessingException();
+ }
+
+ @RequiresApi(21) public class ResolutionInfo {
+ ctor public ResolutionInfo(android.util.Size, android.graphics.Rect, int);
+ method public android.graphics.Rect getCropRect();
+ method public android.util.Size getResolution();
+ method public int getRotationDegrees();
+ }
+
+ @RequiresApi(21) public class SurfaceOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
+ ctor public SurfaceOrientedMeteringPointFactory(float, float);
+ ctor public SurfaceOrientedMeteringPointFactory(float, float, androidx.camera.core.UseCase);
+ }
+
+ public interface SurfaceOutput extends java.io.Closeable {
+ method public void close();
+ method public default android.graphics.Matrix getSensorToBufferTransform();
+ method public android.util.Size getSize();
+ method public android.view.Surface getSurface(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceOutput.Event!>);
+ method public int getTargets();
+ method public void updateTransformMatrix(float[], float[]);
+ }
+
+ @com.google.auto.value.AutoValue public abstract static class SurfaceOutput.Event {
+ method public abstract int getEventCode();
+ method public abstract androidx.camera.core.SurfaceOutput getSurfaceOutput();
+ field public static final int EVENT_REQUEST_CLOSE = 0; // 0x0
+ }
+
+ public interface SurfaceProcessor {
+ method public void onInputSurface(androidx.camera.core.SurfaceRequest) throws androidx.camera.core.ProcessingException;
+ method public void onOutputSurface(androidx.camera.core.SurfaceOutput) throws androidx.camera.core.ProcessingException;
+ }
+
+ @RequiresApi(21) public final class SurfaceRequest {
+ method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
+ method public void clearTransformationInfoListener();
+ method public androidx.camera.core.DynamicRange getDynamicRange();
+ method public android.util.Size getResolution();
+ method public boolean invalidate();
+ method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
+ method public void setTransformationInfoListener(java.util.concurrent.Executor, androidx.camera.core.SurfaceRequest.TransformationInfoListener);
+ method public boolean willNotProvideSurface();
+ }
+
+ @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.Result {
+ method public abstract int getResultCode();
+ method public abstract android.view.Surface getSurface();
+ field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
+ field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
+ field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
+ field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
+ field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
+ }
+
+ @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.TransformationInfo {
+ method public abstract android.graphics.Rect getCropRect();
+ method public abstract int getRotationDegrees();
+ method public abstract android.graphics.Matrix getSensorToBufferTransform();
+ method public abstract boolean hasCameraTransform();
+ method public abstract boolean isMirroring();
+ }
+
+ public static interface SurfaceRequest.TransformationInfoListener {
+ method public void onTransformationInfoUpdate(androidx.camera.core.SurfaceRequest.TransformationInfo);
+ }
+
+ @RequiresApi(21) public class TorchState {
+ field public static final int OFF = 0; // 0x0
+ field public static final int ON = 1; // 0x1
+ }
+
+ @RequiresApi(21) public abstract class UseCase {
+ method public static int snapToSurfaceRotation(@IntRange(from=0, to=359) int);
+ }
+
+ @RequiresApi(21) public final class UseCaseGroup {
+ method public java.util.List<androidx.camera.core.CameraEffect!> getEffects();
+ method public java.util.List<androidx.camera.core.UseCase!> getUseCases();
+ method public androidx.camera.core.ViewPort? getViewPort();
+ }
+
+ public static final class UseCaseGroup.Builder {
+ ctor public UseCaseGroup.Builder();
+ method public androidx.camera.core.UseCaseGroup.Builder addEffect(androidx.camera.core.CameraEffect);
+ method public androidx.camera.core.UseCaseGroup.Builder addUseCase(androidx.camera.core.UseCase);
+ method public androidx.camera.core.UseCaseGroup build();
+ method public androidx.camera.core.UseCaseGroup.Builder setViewPort(androidx.camera.core.ViewPort);
+ }
+
+ @RequiresApi(21) public final class ViewPort {
+ method public android.util.Rational getAspectRatio();
+ method public int getLayoutDirection();
+ method public int getRotation();
+ method public int getScaleType();
+ field public static final int FILL_CENTER = 1; // 0x1
+ field public static final int FILL_END = 2; // 0x2
+ field public static final int FILL_START = 0; // 0x0
+ field public static final int FIT = 3; // 0x3
+ }
+
+ public static final class ViewPort.Builder {
+ ctor public ViewPort.Builder(android.util.Rational, int);
+ method public androidx.camera.core.ViewPort build();
+ method public androidx.camera.core.ViewPort.Builder setLayoutDirection(int);
+ method public androidx.camera.core.ViewPort.Builder setScaleType(int);
+ }
+
+ @RequiresApi(21) public interface ZoomState {
+ method public float getLinearZoom();
+ method public float getMaxZoomRatio();
+ method public float getMinZoomRatio();
+ method public float getZoomRatio();
+ }
+
+}
+
+package androidx.camera.core.resolutionselector {
+
+ @RequiresApi(21) public final class AspectRatioStrategy {
+ ctor public AspectRatioStrategy(int, int);
+ method public int getFallbackRule();
+ method public int getPreferredAspectRatio();
+ field public static final int FALLBACK_RULE_AUTO = 1; // 0x1
+ field public static final int FALLBACK_RULE_NONE = 0; // 0x0
+ field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_16_9_FALLBACK_AUTO_STRATEGY;
+ field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_4_3_FALLBACK_AUTO_STRATEGY;
+ }
+
+ @RequiresApi(21) public interface ResolutionFilter {
+ method public java.util.List<android.util.Size!> filter(java.util.List<android.util.Size!>, int);
+ }
+
+ @RequiresApi(21) public final class ResolutionSelector {
+ method public int getAllowedResolutionMode();
+ method public androidx.camera.core.resolutionselector.AspectRatioStrategy getAspectRatioStrategy();
+ method public androidx.camera.core.resolutionselector.ResolutionFilter? getResolutionFilter();
+ method public androidx.camera.core.resolutionselector.ResolutionStrategy? getResolutionStrategy();
+ field public static final int PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION = 0; // 0x0
+ field public static final int PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE = 1; // 0x1
+ }
+
+ public static final class ResolutionSelector.Builder {
+ ctor public ResolutionSelector.Builder();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector build();
+ method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAllowedResolutionMode(int);
+ method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAspectRatioStrategy(androidx.camera.core.resolutionselector.AspectRatioStrategy);
+ method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionFilter(androidx.camera.core.resolutionselector.ResolutionFilter);
+ method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionStrategy(androidx.camera.core.resolutionselector.ResolutionStrategy);
+ }
+
+ @RequiresApi(21) public final class ResolutionStrategy {
+ ctor public ResolutionStrategy(android.util.Size, int);
+ method public android.util.Size? getBoundSize();
+ method public int getFallbackRule();
+ field public static final int FALLBACK_RULE_CLOSEST_HIGHER = 2; // 0x2
+ field public static final int FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER = 1; // 0x1
+ field public static final int FALLBACK_RULE_CLOSEST_LOWER = 4; // 0x4
+ field public static final int FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER = 3; // 0x3
+ field public static final int FALLBACK_RULE_NONE = 0; // 0x0
+ field public static final androidx.camera.core.resolutionselector.ResolutionStrategy HIGHEST_AVAILABLE_STRATEGY;
+ }
+
+}
+
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java
index ffec29fc..d06bb8b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java
@@ -18,6 +18,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import java.util.List;
@@ -59,4 +60,24 @@
*/
@NonNull
List<CameraInfo> getAvailableCameraInfos();
+
+ /**
+ * Returns the {@link CameraInfo} instance of the camera resulted from the
+ * specified {@link CameraSelector}.
+ *
+ * <p>The returned {@link CameraInfo} is corresponded to the camera that will be bound
+ * when calling {@code bindToLifecycle} with the specified {@link CameraSelector}.
+ *
+ * @param cameraSelector the {@link CameraSelector} to get the {@link CameraInfo} that is
+ * corresponded to.
+ * @return the corresponding {@link CameraInfo}.
+ * @throws UnsupportedOperationException if the camera provider is not implemented properly.
+ * @throws IllegalArgumentException if the given {@link CameraSelector} can't result in a
+ * valid camera to provide the {@link CameraInfo}.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @NonNull
+ default CameraInfo getCameraInfo(@NonNull CameraSelector cameraSelector) {
+ throw new UnsupportedOperationException("The camera provider is not implemented properly.");
+ }
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java
index 3a7be54..3a49499 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java
@@ -218,7 +218,7 @@
}
/** A builder for generating {@link CameraXConfig} objects. */
- @SuppressWarnings("ObjectToString")
+ @SuppressWarnings({"ObjectToString", "HiddenSuperclass"})
public static final class Builder
implements TargetConfig.Builder<CameraX, CameraXConfig.Builder> {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index d9f262c..ed8b596 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -1000,7 +1000,7 @@
}
/** Builder for a {@link ImageAnalysis}. */
- @SuppressWarnings("ObjectToString")
+ @SuppressWarnings({"ObjectToString", "HiddenSuperclass"})
public static final class Builder
implements ImageOutputConfig.Builder<Builder>,
ThreadConfig.Builder<Builder>,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 2176913..400babe 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -2127,7 +2127,7 @@
}
/** Builder for an {@link ImageCapture}. */
- @SuppressWarnings({"ObjectToString", "unused"})
+ @SuppressWarnings({"ObjectToString", "unused", "HiddenSuperclass"})
public static final class Builder implements
UseCaseConfig.Builder<ImageCapture, ImageCaptureConfig, Builder>,
ImageOutputConfig.Builder<Builder>,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index cc9ebd4..02d910b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -817,7 +817,7 @@
}
/** Builder for a {@link Preview}. */
- @SuppressWarnings("ObjectToString")
+ @SuppressWarnings({"ObjectToString", "HiddenSuperclass"})
public static final class Builder
implements UseCaseConfig.Builder<Preview, PreviewConfig, Builder>,
ImageOutputConfig.Builder<Builder>,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraControl.java
index 6fc61f8..442518a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraControl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraControl.java
@@ -21,56 +21,31 @@
import androidx.annotation.RequiresApi;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.FocusMeteringResult;
+import androidx.camera.core.impl.utils.SessionProcessorUtil;
import androidx.camera.core.impl.utils.futures.Futures;
import com.google.common.util.concurrent.ListenableFuture;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
/**
- * A {@link CameraControlInternal} whose capabilities can be restricted via
- * {@link #enableRestrictedOperations(boolean, Set)}.
+ * A {@link CameraControlInternal} whose capabilities can be restricted by the associated
+ * {@link SessionProcessor}. Only the camera operations that can be retrieved from
+ * {@link SessionProcessor#getSupportedCameraOperations()} can be supported by the
+ * RestrictedCameraControl.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class RestrictedCameraControl extends ForwardingCameraControl {
- /**
- * Defines the list of supported camera operations.
- */
- public static final int ZOOM = 0;
- public static final int AUTO_FOCUS = 1;
- public static final int AF_REGION = 2;
- public static final int AE_REGION = 3;
- public static final int AWB_REGION = 4;
- public static final int FLASH = 5;
- public static final int TORCH = 6;
- public static final int EXPOSURE_COMPENSATION = 7;
-
- public @interface CameraOperation {
- }
-
private final CameraControlInternal mCameraControl;
- private volatile boolean mUseRestrictedCameraOperations = false;
@Nullable
- private volatile @CameraOperation Set<Integer> mRestrictedCameraOperations;
+ private final SessionProcessor mSessionProcessor;
/**
* Creates the restricted version of the given {@link CameraControlInternal}.
*/
- public RestrictedCameraControl(@NonNull CameraControlInternal cameraControl) {
+ public RestrictedCameraControl(@NonNull CameraControlInternal cameraControl,
+ @Nullable SessionProcessor sessionProcessor) {
super(cameraControl);
mCameraControl = cameraControl;
- }
-
- /**
- * Enable or disable the restricted operations. If disabled, it works just like the origin
- * CameraControlInternal instance.
- */
- public void enableRestrictedOperations(boolean enable,
- @Nullable @CameraOperation Set<Integer> restrictedOperations) {
- mUseRestrictedCameraOperations = enable;
- mRestrictedCameraOperations = restrictedOperations;
+ mSessionProcessor = sessionProcessor;
}
/**
@@ -82,25 +57,19 @@
return mCameraControl;
}
- boolean isOperationSupported(
- @NonNull @CameraOperation int... operations) {
- if (!mUseRestrictedCameraOperations || mRestrictedCameraOperations == null) {
- return true;
- }
-
- // Arrays.asList doesn't work for int array.
- List<Integer> operationList = new ArrayList<>(operations.length);
- for (int operation : operations) {
- operationList.add(operation);
- }
-
- return mRestrictedCameraOperations.containsAll(operationList);
+ /**
+ * Returns the {@link SessionProcessor} associated with the RestrictedCameraControl.
+ */
+ @Nullable
+ public SessionProcessor getSessionProcessor() {
+ return mSessionProcessor;
}
@NonNull
@Override
public ListenableFuture<Void> enableTorch(boolean torch) {
- if (!isOperationSupported(TORCH)) {
+ if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor,
+ RestrictedCameraInfo.CAMERA_OPERATION_TORCH)) {
return Futures.immediateFailedFuture(
new IllegalStateException("Torch is not supported"));
}
@@ -111,7 +80,8 @@
@Override
public ListenableFuture<FocusMeteringResult> startFocusAndMetering(
@NonNull FocusMeteringAction action) {
- FocusMeteringAction modifiedAction = getModifiedFocusMeteringAction(action);
+ FocusMeteringAction modifiedAction =
+ SessionProcessorUtil.getModifiedFocusMeteringAction(mSessionProcessor, action);
if (modifiedAction == null) {
return Futures.immediateFailedFuture(
new IllegalStateException("FocusMetering is not supported"));
@@ -129,7 +99,8 @@
@NonNull
@Override
public ListenableFuture<Void> setZoomRatio(float ratio) {
- if (!isOperationSupported(ZOOM)) {
+ if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor,
+ RestrictedCameraInfo.CAMERA_OPERATION_ZOOM)) {
return Futures.immediateFailedFuture(
new IllegalStateException("Zoom is not supported"));
}
@@ -139,7 +110,8 @@
@NonNull
@Override
public ListenableFuture<Void> setLinearZoom(float linearZoom) {
- if (!isOperationSupported(ZOOM)) {
+ if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor,
+ RestrictedCameraInfo.CAMERA_OPERATION_ZOOM)) {
return Futures.immediateFailedFuture(
new IllegalStateException("Zoom is not supported"));
}
@@ -149,51 +121,11 @@
@NonNull
@Override
public ListenableFuture<Integer> setExposureCompensationIndex(int value) {
- if (!isOperationSupported(EXPOSURE_COMPENSATION)) {
+ if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor,
+ RestrictedCameraInfo.CAMERA_OPERATION_EXPOSURE_COMPENSATION)) {
return Futures.immediateFailedFuture(
new IllegalStateException("ExposureCompensation is not supported"));
}
return mCameraControl.setExposureCompensationIndex(value);
}
-
- /**
- * Returns the modified {@link FocusMeteringAction} that filters out unsupported AE/AF/AWB
- * regions. Returns null if none of AF/AE/AWB regions can be supported after the filtering.
- */
- @Nullable
- FocusMeteringAction getModifiedFocusMeteringAction(@NonNull FocusMeteringAction action) {
- boolean shouldModify = false;
- FocusMeteringAction.Builder builder = new FocusMeteringAction.Builder(action);
- if (!action.getMeteringPointsAf().isEmpty()
- && !isOperationSupported(AUTO_FOCUS, AF_REGION)) {
- shouldModify = true;
- builder.removePoints(FocusMeteringAction.FLAG_AF);
- }
-
- if (!action.getMeteringPointsAe().isEmpty()
- && !isOperationSupported(AE_REGION)) {
- shouldModify = true;
- builder.removePoints(FocusMeteringAction.FLAG_AE);
- }
-
- if (!action.getMeteringPointsAwb().isEmpty()
- && !isOperationSupported(AWB_REGION)) {
- shouldModify = true;
- builder.removePoints(FocusMeteringAction.FLAG_AWB);
- }
-
- // Returns origin action if no need to modify.
- if (!shouldModify) {
- return action;
- }
-
- FocusMeteringAction modifyAction = builder.build();
- if (modifyAction.getMeteringPointsAf().isEmpty()
- && modifyAction.getMeteringPointsAe().isEmpty()
- && modifyAction.getMeteringPointsAwb().isEmpty()) {
- // All regions are not allowed, return null.
- return null;
- }
- return builder.build();
- }
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraInfo.java
index 5892153..65ecd94 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraInfo.java
@@ -19,32 +19,71 @@
import android.util.Range;
import android.util.Rational;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.ExposureState;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.TorchState;
import androidx.camera.core.ZoomState;
+import androidx.camera.core.impl.utils.SessionProcessorUtil;
import androidx.camera.core.internal.ImmutableZoomState;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A {@link CameraInfoInternal} that returns disabled state if the corresponding operation in the
* given {@link RestrictedCameraControl} is disabled.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class RestrictedCameraInfo extends ForwardingCameraInfo {
+ /**
+ * Defines the list of supported camera operations.
+ */
+ public static final int CAMERA_OPERATION_ZOOM = 0;
+ public static final int CAMERA_OPERATION_AUTO_FOCUS = 1;
+ public static final int CAMERA_OPERATION_AF_REGION = 2;
+ public static final int CAMERA_OPERATION_AE_REGION = 3;
+ public static final int CAMERA_OPERATION_AWB_REGION = 4;
+ public static final int CAMERA_OPERATION_FLASH = 5;
+ public static final int CAMERA_OPERATION_TORCH = 6;
+ public static final int CAMERA_OPERATION_EXPOSURE_COMPENSATION = 7;
+ public static final int CAMERA_OPERATION_EXTENSION_STRENGTH = 8;
+
+ @IntDef({CAMERA_OPERATION_ZOOM, CAMERA_OPERATION_AUTO_FOCUS, CAMERA_OPERATION_AF_REGION,
+ CAMERA_OPERATION_AE_REGION, CAMERA_OPERATION_AWB_REGION, CAMERA_OPERATION_FLASH,
+ CAMERA_OPERATION_TORCH, CAMERA_OPERATION_EXPOSURE_COMPENSATION,
+ CAMERA_OPERATION_EXTENSION_STRENGTH})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CameraOperation {
+ }
+
private final CameraInfoInternal mCameraInfo;
- private final RestrictedCameraControl mRestrictedCameraControl;
+ @Nullable
+ private final SessionProcessor mSessionProcessor;
private boolean mIsPostviewSupported = false;
private boolean mIsCaptureProcessProgressSupported = false;
+ @NonNull
+ private final CameraConfig mCameraConfig;
public RestrictedCameraInfo(@NonNull CameraInfoInternal cameraInfo,
- @NonNull RestrictedCameraControl restrictedCameraControl) {
+ @NonNull CameraConfig cameraConfig) {
super(cameraInfo);
mCameraInfo = cameraInfo;
- mRestrictedCameraControl = restrictedCameraControl;
+ mCameraConfig = cameraConfig;
+ mSessionProcessor = cameraConfig.getSessionProcessor(null);
+
+ setPostviewSupported(cameraConfig.isPostviewSupported());
+ setCaptureProcessProgressSupported(cameraConfig.isCaptureProcessProgressSupported());
+ }
+
+ @NonNull
+ public CameraConfig getCameraConfig() {
+ return mCameraConfig;
}
@NonNull
@@ -53,9 +92,17 @@
return mCameraInfo;
}
+ /**
+ * Returns the session processor associated with the RestrictedCameraInfo.
+ */
+ @Nullable
+ public SessionProcessor getSessionProcessor() {
+ return mSessionProcessor;
+ }
+
@Override
public boolean hasFlashUnit() {
- if (!mRestrictedCameraControl.isOperationSupported(RestrictedCameraControl.FLASH)) {
+ if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor, CAMERA_OPERATION_FLASH)) {
return false;
}
@@ -65,7 +112,7 @@
@NonNull
@Override
public LiveData<Integer> getTorchState() {
- if (!mRestrictedCameraControl.isOperationSupported(RestrictedCameraControl.TORCH)) {
+ if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor, CAMERA_OPERATION_TORCH)) {
return new MutableLiveData<>(TorchState.OFF);
}
@@ -75,7 +122,7 @@
@NonNull
@Override
public LiveData<ZoomState> getZoomState() {
- if (!mRestrictedCameraControl.isOperationSupported(RestrictedCameraControl.ZOOM)) {
+ if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor, CAMERA_OPERATION_ZOOM)) {
return new MutableLiveData<>(ImmutableZoomState.create(
/* zoomRatio */1f, /* maxZoomRatio */ 1f,
/* minZoomRatio */ 1f, /* linearZoom*/ 0f));
@@ -86,8 +133,8 @@
@NonNull
@Override
public ExposureState getExposureState() {
- if (!mRestrictedCameraControl.isOperationSupported(
- RestrictedCameraControl.EXPOSURE_COMPENSATION)) {
+ if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor,
+ CAMERA_OPERATION_EXPOSURE_COMPENSATION)) {
return new ExposureState() {
@Override
public int getExposureCompensationIndex() {
@@ -117,10 +164,12 @@
@Override
public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
- if (mRestrictedCameraControl.getModifiedFocusMeteringAction(action) == null) {
+ FocusMeteringAction modifiedAction =
+ SessionProcessorUtil.getModifiedFocusMeteringAction(mSessionProcessor, action);
+ if (modifiedAction == null) {
return false;
}
- return mCameraInfo.isFocusMeteringSupported(action);
+ return mCameraInfo.isFocusMeteringSupported(modifiedAction);
}
/**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java
index f00b373..59ef594 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java
@@ -48,6 +48,7 @@
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public interface SessionProcessor {
+
/**
* Initializes the session and returns a transformed {@link SessionConfig} which should be
* used to configure the camera instead of original one.
@@ -139,7 +140,7 @@
* Returns the supported camera operations when the SessionProcessor is enabled.
*/
@NonNull
- default @RestrictedCameraControl.CameraOperation Set<Integer> getSupportedCameraOperations() {
+ default @RestrictedCameraInfo.CameraOperation Set<Integer> getSupportedCameraOperations() {
return Collections.emptySet();
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/SessionProcessorUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/SessionProcessorUtil.java
new file mode 100644
index 0000000..d37020c
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/SessionProcessorUtil.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.impl.utils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.impl.RestrictedCameraInfo;
+import androidx.camera.core.impl.SessionProcessor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for session processor related operations.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public final class SessionProcessorUtil {
+ private SessionProcessorUtil() {
+
+ }
+
+ /**
+ * Returns whether the camera operations are supported by the SessionProcessor.
+ *
+ * @param operations the camera operations to check.
+ * @return {@code true} if the operations can be supported, otherwise {@code false}.
+ */
+ public static boolean isOperationSupported(@Nullable SessionProcessor sessionProcessor,
+ @NonNull @RestrictedCameraInfo.CameraOperation int... operations) {
+ if (sessionProcessor == null) {
+ return true;
+ }
+ // Arrays.asList doesn't work for int array.
+ List<Integer> operationList = new ArrayList<>(operations.length);
+ for (int operation : operations) {
+ operationList.add(operation);
+ }
+
+ return sessionProcessor.getSupportedCameraOperations().containsAll(operationList);
+ }
+
+ /**
+ * Returns the modified {@link FocusMeteringAction} that filters out unsupported AE/AF/AWB
+ * regions. Returns {@code null} if none of AF/AE/AWB regions can be supported after the
+ * filtering.
+ */
+ @Nullable
+ public static FocusMeteringAction getModifiedFocusMeteringAction(
+ @Nullable SessionProcessor sessionProcessor, @NonNull FocusMeteringAction action) {
+ if (sessionProcessor == null) {
+ return action;
+ }
+ boolean shouldModify = false;
+ FocusMeteringAction.Builder builder = new FocusMeteringAction.Builder(action);
+ if (!action.getMeteringPointsAf().isEmpty()
+ && !isOperationSupported(sessionProcessor,
+ RestrictedCameraInfo.CAMERA_OPERATION_AUTO_FOCUS,
+ RestrictedCameraInfo.CAMERA_OPERATION_AF_REGION)) {
+ shouldModify = true;
+ builder.removePoints(FocusMeteringAction.FLAG_AF);
+ }
+
+ if (!action.getMeteringPointsAe().isEmpty()
+ && !isOperationSupported(sessionProcessor,
+ RestrictedCameraInfo.CAMERA_OPERATION_AE_REGION)) {
+ shouldModify = true;
+ builder.removePoints(FocusMeteringAction.FLAG_AE);
+ }
+
+ if (!action.getMeteringPointsAwb().isEmpty()
+ && !isOperationSupported(sessionProcessor,
+ RestrictedCameraInfo.CAMERA_OPERATION_AWB_REGION)) {
+ shouldModify = true;
+ builder.removePoints(FocusMeteringAction.FLAG_AWB);
+ }
+
+ // Returns origin action if no need to modify.
+ if (!shouldModify) {
+ return action;
+ }
+
+ FocusMeteringAction modifyAction = builder.build();
+ if (modifyAction.getMeteringPointsAf().isEmpty()
+ && modifyAction.getMeteringPointsAe().isEmpty()
+ && modifyAction.getMeteringPointsAwb().isEmpty()) {
+ // All regions are not allowed, return null.
+ return null;
+ }
+ return builder.build();
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index 4232ac1..784151f 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -68,10 +68,10 @@
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.CameraMode;
import androidx.camera.core.impl.Config;
+import androidx.camera.core.impl.Identifier;
import androidx.camera.core.impl.MutableOptionsBundle;
import androidx.camera.core.impl.PreviewConfig;
import androidx.camera.core.impl.RestrictedCameraControl;
-import androidx.camera.core.impl.RestrictedCameraControl.CameraOperation;
import androidx.camera.core.impl.RestrictedCameraInfo;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.SessionProcessor;
@@ -85,6 +85,8 @@
import androidx.camera.core.streamsharing.StreamSharing;
import androidx.core.util.Preconditions;
+import com.google.auto.value.AutoValue;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -109,6 +111,8 @@
private static final String TAG = "CameraUseCaseAdapter";
+ private final CameraId mId;
+
// UseCases from the app. This does not include internal UseCases created by CameraX.
@GuardedBy("mLock")
private final List<UseCase> mAppUseCases = new ArrayList<>();
@@ -172,60 +176,60 @@
@NonNull CameraCoordinator cameraCoordinator,
@NonNull CameraDeviceSurfaceManager cameraDeviceSurfaceManager,
@NonNull UseCaseConfigFactory useCaseConfigFactory) {
- this(camera, cameraCoordinator, cameraDeviceSurfaceManager, useCaseConfigFactory,
- CameraConfigs.defaultConfig());
+ this(camera,
+ new RestrictedCameraInfo(camera.getCameraInfoInternal(),
+ CameraConfigs.defaultConfig()),
+ cameraCoordinator,
+ cameraDeviceSurfaceManager,
+ useCaseConfigFactory);
}
/**
* Create a new {@link CameraUseCaseAdapter} instance.
*
* @param camera The camera that is wrapped.
+ * @param restrictedCameraInfo The {@link RestrictedCameraInfo} that contains the extra
+ * information to configure the {@link CameraInternal} when
+ * attaching the uses cases of this adapter to the camera.
* @param cameraCoordinator Camera coordinator that exposes concurrent camera mode.
* @param cameraDeviceSurfaceManager A class that checks for whether a specific camera
* can support the set of Surface with set resolutions.
* @param useCaseConfigFactory UseCase config factory that exposes configuration for
* each UseCase.
- * @param cameraConfig the CameraConfig to configure the {@link CameraInternal}
- * when attaching the uses cases of this adapter to the
- * camera.
*/
public CameraUseCaseAdapter(@NonNull CameraInternal camera,
+ @NonNull RestrictedCameraInfo restrictedCameraInfo,
@NonNull CameraCoordinator cameraCoordinator,
@NonNull CameraDeviceSurfaceManager cameraDeviceSurfaceManager,
- @NonNull UseCaseConfigFactory useCaseConfigFactory,
- @NonNull CameraConfig cameraConfig) {
+ @NonNull UseCaseConfigFactory useCaseConfigFactory) {
mCameraInternal = camera;
mCameraCoordinator = cameraCoordinator;
mCameraDeviceSurfaceManager = cameraDeviceSurfaceManager;
mUseCaseConfigFactory = useCaseConfigFactory;
- // TODO(b/279996499): bind the same restricted CameraControl and CameraInfo to use cases.
- mAdapterCameraControl =
- new RestrictedCameraControl(mCameraInternal.getCameraControlInternal());
- mAdapterCameraInfo =
- new RestrictedCameraInfo(mCameraInternal.getCameraInfoInternal(),
- mAdapterCameraControl);
-
- mCameraConfig = cameraConfig;
+ mCameraConfig = restrictedCameraInfo.getCameraConfig();
SessionProcessor sessionProcessor = mCameraConfig.getSessionProcessor(null);
- if (sessionProcessor != null) {
- @CameraOperation Set<Integer> supportedOps =
- sessionProcessor.getSupportedCameraOperations();
- mAdapterCameraControl.enableRestrictedOperations(true, supportedOps);
- } else {
- mAdapterCameraControl.enableRestrictedOperations(false, null);
- }
- mAdapterCameraInfo.setPostviewSupported(
- mCameraConfig.isPostviewSupported());
- mAdapterCameraInfo.setCaptureProcessProgressSupported(
- mCameraConfig.isCaptureProcessProgressSupported());
+ // TODO(b/279996499): bind the same restricted CameraControl and CameraInfo to use cases.
+ mAdapterCameraControl = new RestrictedCameraControl(
+ mCameraInternal.getCameraControlInternal(), sessionProcessor);
+ mAdapterCameraInfo = restrictedCameraInfo;
+ mId = generateCameraId(mAdapterCameraInfo);
+ }
+
+ /**
+ * Generate a identifier for the {@link RestrictedCameraInfo}.
+ */
+ @NonNull
+ public static CameraId generateCameraId(@NonNull RestrictedCameraInfo cameraInfo) {
+ return CameraId.create(cameraInfo.getCameraId(),
+ cameraInfo.getCameraConfig().getCompatibilityId());
}
/**
* Returns the identifier for this {@link CameraUseCaseAdapter}.
*/
@NonNull
- public String getCameraId() {
- return mCameraInternal.getCameraInfoInternal().getCameraId();
+ public CameraId getCameraId() {
+ return mId;
}
/**
@@ -938,6 +942,30 @@
}
/**
+ * An identifier for a {@link CameraUseCaseAdapter}.
+ *
+ * <p>This identifies the actual camera instances that are wrapped by the
+ * CameraUseCaseAdapter and is used to determine if 2 different instances of
+ * CameraUseCaseAdapter are actually equivalent.
+ */
+ @AutoValue
+ public abstract static class CameraId {
+ /** Creates a identifier for a {@link CameraUseCaseAdapter}. */
+ @NonNull
+ public static CameraId create(@NonNull String cameraIdString,
+ @NonNull Identifier cameraConfigId) {
+ return new AutoValue_CameraUseCaseAdapter_CameraId(cameraIdString, cameraConfigId);
+ }
+
+ /** Gets the camera ID string. */
+ @NonNull
+ public abstract String getCameraIdString();
+ /** Gets the camera configuration. */
+ @NonNull
+ public abstract Identifier getCameraConfigId();
+ }
+
+ /**
* An exception thrown when the {@link CameraUseCaseAdapter} errors in one of its operations.
*/
public static final class CameraException extends Exception {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java
index 438acf5..bd05429 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java
@@ -34,9 +34,9 @@
* 0's padding data. For example, Samsung A5 (2017) series devices have the
* problem and result in the output JPEG image to be extremely large (about 32 MB).
* Device(s): Samsung Galaxy A5 (2017), A52, A70, A71, A72, M51, S7, S22, S22+ series devices
- * and Vivo S16 device. This issue might also happen on some other Samsung devices
- * . Therefore, a generic rule is added to force check the invalid JPEG data if
- * the captured image size is larger than 10 MB.
+ * and Vivo S16, X60, X60 Pro devices. This issue might also happen on some other
+ * Samsung devices. Therefore, a generic rule is added to force check the invalid
+ * JPEG data if the captured image size is larger than 10 MB.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class LargeJpegImageQuirk implements Quirk {
@@ -88,7 +88,11 @@
private static final Set<String> VIVO_DEVICE_MODELS = new HashSet<>(Arrays.asList(
// Vivo S16
- "V2244A"
+ "V2244A",
+ // Vivo X60
+ "V2045",
+ // Vivo X60 Pro
+ "V2046"
));
static boolean load() {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
index dd3088d..5b4fc63 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
@@ -17,7 +17,7 @@
package androidx.camera.core.processing;
import static androidx.camera.core.impl.ImageOutputConfig.ROTATION_NOT_SPECIFIED;
-import static androidx.camera.core.impl.utils.Threads.isMainThread;
+import static androidx.camera.core.impl.utils.Threads.runOnMain;
import static androidx.camera.core.impl.utils.TransformUtils.getRectToRect;
import static androidx.camera.core.impl.utils.TransformUtils.getRotatedSize;
import static androidx.camera.core.impl.utils.TransformUtils.isAspectRatioMatchingWithRoundingError;
@@ -286,7 +286,9 @@
@Override
public void release() {
mSurfaceProcessor.release();
- runOnMainThread(() -> {
+ // Required for b/309409701. For some reason, the cleanup posted on {@link #release()} is
+ // not executed in unit tests which causes failures.
+ runOnMain(() -> {
if (mOutput != null) {
for (SurfaceEdge surface : mOutput.values()) {
// The output DeferrableSurface will later be terminated by the processor.
@@ -297,23 +299,6 @@
}
/**
- * Runs the given {@link Runnable} on the main thread.
- *
- * <p>If the current thread is the main thread, the runnable is executed immediately.
- * Otherwise, the runnable is posted to the main thread.
- *
- * <p>This is added for b/309409701. For some reason, the cleanup posted on
- * {@link #release()} is not executed in unit tests which causes failures.
- */
- private static void runOnMainThread(@NonNull Runnable runnable) {
- if (isMainThread()) {
- runnable.run();
- } else {
- mainThreadExecutor().execute(runnable);
- }
- }
-
- /**
* Gets the {@link SurfaceProcessorInternal} used by this node.
*/
@NonNull
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraAdapter.java
index 36c58e1..a3528b1 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraAdapter.java
@@ -330,7 +330,6 @@
public void onUseCaseReset(@NonNull UseCase useCase) {
checkMainThread();
SurfaceEdge edge = getUseCaseEdge(useCase);
- edge.invalidate();
if (!isUseCaseActive(useCase)) {
// No-op if the child is inactive. It will connect when it becomes active.
return;
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index b3500ae1..3d2b8d3 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -42,12 +42,15 @@
import androidx.camera.core.ViewPort
import androidx.camera.core.concurrent.CameraCoordinator
import androidx.camera.core.impl.CameraConfig
+import androidx.camera.core.impl.CameraConfigs
import androidx.camera.core.impl.CameraInfoInternal
+import androidx.camera.core.impl.CameraInternal
import androidx.camera.core.impl.Config
import androidx.camera.core.impl.Identifier
import androidx.camera.core.impl.MutableOptionsBundle
import androidx.camera.core.impl.OptionsBundle
import androidx.camera.core.impl.RestrictedCameraControl
+import androidx.camera.core.impl.RestrictedCameraInfo
import androidx.camera.core.impl.SessionProcessor
import androidx.camera.core.impl.StreamSpec
import androidx.camera.core.impl.UseCaseConfigFactory
@@ -102,7 +105,6 @@
class CameraUseCaseAdapterTest {
private lateinit var effects: List<CameraEffect>
private lateinit var executor: ExecutorService
-
private lateinit var fakeCameraDeviceSurfaceManager: FakeCameraDeviceSurfaceManager
private lateinit var fakeCamera: FakeCamera
private lateinit var useCaseConfigFactory: UseCaseConfigFactory
@@ -113,12 +115,13 @@
private lateinit var surfaceProcessorInternal: FakeSurfaceProcessorInternal
private lateinit var fakeCameraControl: FakeCameraControl
private lateinit var fakeCameraInfo: FakeCameraInfoInternal
+ private lateinit var adapter: CameraUseCaseAdapter
private val imageEffect = GrayscaleImageEffect()
private val preview = Preview.Builder().build()
private val video = createFakeVideoCaptureUseCase()
private val image = ImageCapture.Builder().build()
private val analysis = ImageAnalysis.Builder().build()
- private lateinit var adapter: CameraUseCaseAdapter
+ private val adaptersToDetach = mutableListOf<CameraUseCaseAdapter>()
@Before
fun setUp() {
@@ -143,12 +146,7 @@
surfaceProcessorInternal
)
effects = listOf(previewEffect, imageEffect, videoEffect)
- adapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
+ adapter = createCameraUseCaseAdapter(fakeCamera)
DefaultSurfaceProcessor.Factory.setSupplier { surfaceProcessorInternal }
}
@@ -156,6 +154,9 @@
fun tearDown() {
surfaceProcessorInternal.cleanUp()
executor.shutdown()
+ for (adapter in adaptersToDetach) {
+ adapter.updateUseCases(emptySet())
+ }
}
@Test(expected = CameraException::class)
@@ -203,13 +204,7 @@
fun addUseCases_cameraConfigIsConfigured() {
// Arrange: Prepare two sets of CameraConfig and CameraUseCaseAdapter.
val cameraConfig = FakeCameraConfig()
- val adapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
- cameraConfig
- )
+ val adapter = createCameraUseCaseAdapter(fakeCamera, cameraConfig)
// Act: Add use cases.
adapter.addUseCases(setOf(preview, video, image))
@@ -222,21 +217,15 @@
fun attachUseCases_cameraConfigIsConfigured() {
// Arrange: Prepare two sets of CameraConfig and CameraUseCaseAdapter.
val cameraConfig1 = FakeCameraConfig()
- val adapter1 = CameraUseCaseAdapter(
+ val adapter1 = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
cameraConfig1
)
val cameraConfig2 = FakeCameraConfig()
- val adapter2 = CameraUseCaseAdapter(
+ val adapter2 = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
- cameraConfig2,
- )
+ cameraConfig2
+ )
val preview2 = Preview.Builder().build()
val video2 = createFakeVideoCaptureUseCase()
val image2 = ImageCapture.Builder().build()
@@ -275,14 +264,9 @@
@Test(expected = CameraException::class)
fun useHDRWithExtensions_throwsException() {
// Arrange: enable extensions.
- val extensionsConfig = createCoexistingRequiredRuleCameraConfig(FakeSessionProcessor())
-
- val adapter = CameraUseCaseAdapter(
+ val adapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
- extensionsConfig,
+ createCoexistingRequiredRuleCameraConfig(FakeSessionProcessor())
)
// Act: add UseCase that uses HDR.
val hdrUseCase = FakeUseCaseConfig.Builder().setDynamicRange(HDR_UNSPECIFIED_10_BIT).build()
@@ -436,13 +420,9 @@
@Test
fun extensionEnabledAndVideoCaptureExisted_streamSharingOn() {
// Arrange: enable extensions.
- val extensionsConfig = createCoexistingRequiredRuleCameraConfig(FakeSessionProcessor())
- val adapter = CameraUseCaseAdapter(
+ val adapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
- extensionsConfig,
+ createCoexistingRequiredRuleCameraConfig(FakeSessionProcessor())
)
// Act: add UseCases that require StreamSharing.
adapter.addUseCases(setOf(preview, video, image))
@@ -459,13 +439,9 @@
@Test
fun extensionEnabledAndOnlyVideoCaptureAttached_streamSharingOn() {
// Arrange: enable extensions.
- val extensionsConfig = createCoexistingRequiredRuleCameraConfig(FakeSessionProcessor())
- val adapter = CameraUseCaseAdapter(
+ val adapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
- extensionsConfig,
+ createCoexistingRequiredRuleCameraConfig(FakeSessionProcessor())
)
// Act: add UseCases that require StreamSharing.
adapter.addUseCases(setOf(video))
@@ -512,15 +488,9 @@
@Test
fun detachUseCases() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
val fakeUseCase = FakeUseCase()
- cameraUseCaseAdapter.addUseCases(listOf(fakeUseCase))
- cameraUseCaseAdapter.removeUseCases(listOf(fakeUseCase))
+ adapter.addUseCases(listOf(fakeUseCase))
+ adapter.removeUseCases(listOf(fakeUseCase))
assertThat(fakeUseCase.camera).isNull()
}
@@ -535,18 +505,8 @@
val originalConfig = MutableOptionsBundle.create()
originalConfig.insertOption(option, value)
fakeCamera.cameraControlInternal.addInteropConfig(originalConfig)
- val cameraUseCaseAdapter1 = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
- val cameraUseCaseAdapter2 = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
+ val cameraUseCaseAdapter1 = createCameraUseCaseAdapter(fakeCamera)
+ val cameraUseCaseAdapter2 = createCameraUseCaseAdapter(fakeCamera)
// This caches the original config and clears it from CameraControl internally.
cameraUseCaseAdapter1.detachUseCases()
@@ -580,15 +540,9 @@
// Set an config to CameraControl.
val config: Config = MutableOptionsBundle.create()
fakeCamera.cameraControlInternal.addInteropConfig(config)
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
// This caches the original config and clears it from CameraControl internally.
- cameraUseCaseAdapter.detachUseCases()
+ adapter.detachUseCases()
// Check the config in CameraControl is empty.
assertThat(fakeCamera.cameraControlInternal.interopConfig.listOptions()).isEmpty()
@@ -596,57 +550,30 @@
@Test
fun closeCameraUseCaseAdapter() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
val fakeUseCase = FakeUseCase()
- cameraUseCaseAdapter.addUseCases(listOf(fakeUseCase))
- cameraUseCaseAdapter.detachUseCases()
+ adapter.addUseCases(listOf(fakeUseCase))
+ adapter.detachUseCases()
assertThat(fakeUseCase.camera).isEqualTo(fakeCamera)
assertThat(fakeCamera.attachedUseCases).isEmpty()
}
@Test
fun cameraIdEquals() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
- assertThat(cameraUseCaseAdapter.cameraId == fakeCamera.cameraInfoInternal.cameraId).isTrue()
+ val otherCameraId = createCameraUseCaseAdapter(fakeCamera).cameraId
+ assertThat(adapter.cameraId == otherCameraId).isTrue()
}
@Test
fun cameraEquivalent() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
- val otherCameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(fakeCamera)
+ val otherCameraUseCaseAdapter = createCameraUseCaseAdapter(fakeCamera)
assertThat(cameraUseCaseAdapter.isEquivalent(otherCameraUseCaseAdapter)).isTrue()
}
@Test
fun useCase_onAttach() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
val fakeUseCase = spy(FakeUseCase())
- cameraUseCaseAdapter.addUseCases(listOf(fakeUseCase))
+ adapter.addUseCases(listOf(fakeUseCase))
verify(fakeUseCase).bindToCamera(
eq(fakeCamera),
isNull(),
@@ -656,44 +583,26 @@
@Test
fun useCase_onDetach() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
val fakeUseCase = spy(FakeUseCase())
- cameraUseCaseAdapter.addUseCases(listOf(fakeUseCase))
- cameraUseCaseAdapter.removeUseCases(listOf(fakeUseCase))
+ adapter.addUseCases(listOf(fakeUseCase))
+ adapter.removeUseCases(listOf(fakeUseCase))
verify(fakeUseCase).unbindFromCamera(fakeCamera)
}
@Test
fun eventCallbackOnBind() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
val callback = mock(UseCase.EventCallback::class.java)
val fakeUseCase = FakeUseCaseConfig.Builder().setUseCaseEventCallback(callback).build()
- cameraUseCaseAdapter.addUseCases(listOf(fakeUseCase))
+ adapter.addUseCases(listOf(fakeUseCase))
verify(callback).onBind(fakeCamera.cameraInfoInternal)
}
@Test
fun eventCallbackOnUnbind() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
val callback = mock(UseCase.EventCallback::class.java)
val fakeUseCase = FakeUseCaseConfig.Builder().setUseCaseEventCallback(callback).build()
- cameraUseCaseAdapter.addUseCases(listOf(fakeUseCase))
- cameraUseCaseAdapter.removeUseCases(listOf(fakeUseCase))
+ adapter.addUseCases(listOf(fakeUseCase))
+ adapter.removeUseCases(listOf(fakeUseCase))
verify(callback).onUnbind()
}
@@ -703,17 +612,11 @@
val aspectRatio2 = Rational(2, 1)
// Arrange: set up adapter with aspect ratio 1.
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
- cameraUseCaseAdapter.setViewPort(
+ adapter.setViewPort(
ViewPort.Builder(aspectRatio1, Surface.ROTATION_0).build()
)
val fakeUseCase = spy(FakeUseCase())
- cameraUseCaseAdapter.addUseCases(listOf(fakeUseCase))
+ adapter.addUseCases(listOf(fakeUseCase))
// Use case gets aspect ratio 1
assertThat(fakeUseCase.viewPortCropRect).isNotNull()
assertThat(
@@ -724,10 +627,10 @@
).isEqualTo(aspectRatio1)
// Act: set aspect ratio 2 and attach the same use case.
- cameraUseCaseAdapter.setViewPort(
+ adapter.setViewPort(
ViewPort.Builder(aspectRatio2, Surface.ROTATION_0).build()
)
- cameraUseCaseAdapter.addUseCases(listOf(fakeUseCase))
+ adapter.addUseCases(listOf(fakeUseCase))
// Assert: the viewport has aspect ratio 2.
assertThat(fakeUseCase.viewPortCropRect).isNotNull()
@@ -758,15 +661,9 @@
* 3023 |-----------------| 3022 |-----------------|
* 3024 |-----------------|
*/
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
- cameraUseCaseAdapter.setViewPort(ViewPort.Builder(aspectRatio, Surface.ROTATION_0).build())
+ adapter.setViewPort(ViewPort.Builder(aspectRatio, Surface.ROTATION_0).build())
val fakeUseCase = FakeUseCase()
- cameraUseCaseAdapter.addUseCases(listOf(fakeUseCase))
+ adapter.addUseCases(listOf(fakeUseCase))
assertThat(fakeUseCase.viewPortCropRect).isEqualTo(Rect(505, 0, 3527, 3022))
assertThat(fakeUseCase.sensorToBufferTransformMatrix).isEqualTo(Matrix().apply {
// From 4032x3024 to 4032x3022 with Crop Inside, no scale and Y shift 1.
@@ -781,11 +678,8 @@
@Test
fun noExtraUseCase_whenBindEmptyUseCaseList() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
createCoexistingRequiredRuleCameraConfig()
)
cameraUseCaseAdapter.addUseCases(emptyList())
@@ -795,11 +689,8 @@
@Test
fun addExtraImageCapture_whenOnlyBindPreview() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
createCoexistingRequiredRuleCameraConfig()
)
val preview = Preview.Builder().build()
@@ -813,11 +704,8 @@
@Test
fun removeExtraImageCapture_afterBindImageCapture() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
createCoexistingRequiredRuleCameraConfig()
)
val preview = Preview.Builder().build()
@@ -838,11 +726,8 @@
@Test
fun addExtraImageCapture_whenUnbindImageCapture() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
createCoexistingRequiredRuleCameraConfig()
)
val useCases = mutableListOf<UseCase>()
@@ -866,11 +751,8 @@
@Test
fun addExtraPreview_whenOnlyBindImageCapture() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
createCoexistingRequiredRuleCameraConfig()
)
val imageCapture = ImageCapture.Builder().build()
@@ -884,11 +766,8 @@
@Test
fun removeExtraPreview_afterBindPreview() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
createCoexistingRequiredRuleCameraConfig()
)
val imageCapture = ImageCapture.Builder().build()
@@ -908,11 +787,8 @@
@Test
fun addExtraPreview_whenUnbindPreview() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
createCoexistingRequiredRuleCameraConfig()
)
val useCases = mutableListOf<UseCase>()
@@ -936,11 +812,8 @@
@Test
fun noExtraUseCase_whenUnbindBothPreviewAndImageCapture() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
createCoexistingRequiredRuleCameraConfig()
)
val useCases = mutableListOf<UseCase>()
@@ -964,36 +837,24 @@
@Test
fun noExtraImageCapture_whenOnlyBindPreviewWithoutRule() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
val preview = Preview.Builder().build()
// Adds a Preview only
- cameraUseCaseAdapter.addUseCases(listOf(preview))
+ adapter.addUseCases(listOf(preview))
// Checks that no extra use case is added.
- assertThat(cameraUseCaseAdapter.useCases.size).isEqualTo(1)
+ assertThat(adapter.useCases.size).isEqualTo(1)
}
@Test
fun noExtraPreview_whenOnlyBindImageCaptureWithoutRule() {
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory
- )
val imageCapture = ImageCapture.Builder().build()
// Adds an ImageCapture only
- cameraUseCaseAdapter.addUseCases(listOf(imageCapture))
+ adapter.addUseCases(listOf(imageCapture))
// Checks that no extra use case is added.
- assertThat(cameraUseCaseAdapter.useCases.size).isEqualTo(1)
+ assertThat(adapter.useCases.size).isEqualTo(1)
}
@Test(expected = IllegalStateException::class)
@@ -1058,22 +919,14 @@
@RequiresApi(23)
private fun createAdapterWithSupportedCameraOperations(
- @RestrictedCameraControl.CameraOperation supportedOps: Set<Int>
+ @RestrictedCameraInfo.CameraOperation supportedOps: Set<Int>
): CameraUseCaseAdapter {
val fakeSessionProcessor = FakeSessionProcessor()
// no camera operations are supported.
fakeSessionProcessor.restrictedCameraOperations = supportedOps
val cameraConfig: CameraConfig = FakeCameraConfig(fakeSessionProcessor)
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
- fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
- cameraConfig
- )
-
- return cameraUseCaseAdapter
+ return createCameraUseCaseAdapter(fakeCamera, cameraConfig)
}
@org.robolectric.annotation.Config(minSdk = 23)
@@ -1118,7 +971,7 @@
// 1. Arrange
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
- supportedOps = setOf(RestrictedCameraControl.ZOOM))
+ supportedOps = setOf(RestrictedCameraInfo.CAMERA_OPERATION_ZOOM))
// 2. Act && Assert
cameraUseCaseAdapter.cameraControl.setZoomRatio(2.0f).await()
@@ -1133,7 +986,7 @@
// 1. Arrange
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
- supportedOps = setOf(RestrictedCameraControl.TORCH))
+ supportedOps = setOf(RestrictedCameraInfo.CAMERA_OPERATION_TORCH))
// 2. Act
cameraUseCaseAdapter.cameraControl.enableTorch(true).await()
@@ -1149,8 +1002,8 @@
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
supportedOps = setOf(
- RestrictedCameraControl.AUTO_FOCUS,
- RestrictedCameraControl.AF_REGION,
+ RestrictedCameraInfo.CAMERA_OPERATION_AUTO_FOCUS,
+ RestrictedCameraInfo.CAMERA_OPERATION_AF_REGION,
))
// 2. Act
@@ -1175,7 +1028,7 @@
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
supportedOps = setOf(
- RestrictedCameraControl.AE_REGION,
+ RestrictedCameraInfo.CAMERA_OPERATION_AE_REGION,
))
// 2. Act
@@ -1200,7 +1053,7 @@
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
supportedOps = setOf(
- RestrictedCameraControl.AWB_REGION,
+ RestrictedCameraInfo.CAMERA_OPERATION_AWB_REGION,
))
// 2. Act
@@ -1225,7 +1078,7 @@
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
supportedOps = setOf(
- RestrictedCameraControl.AE_REGION,
+ RestrictedCameraInfo.CAMERA_OPERATION_AE_REGION,
))
// 2. Act && Assert
@@ -1243,7 +1096,7 @@
// 1. Arrange
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
- supportedOps = setOf(RestrictedCameraControl.EXPOSURE_COMPENSATION))
+ supportedOps = setOf(RestrictedCameraInfo.CAMERA_OPERATION_EXPOSURE_COMPENSATION))
// 2. Act
cameraUseCaseAdapter.cameraControl.setExposureCompensationIndex(0).await()
@@ -1296,7 +1149,7 @@
// 1. Arrange
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
- supportedOps = setOf(RestrictedCameraControl.ZOOM)
+ supportedOps = setOf(RestrictedCameraInfo.CAMERA_OPERATION_ZOOM)
)
fakeCameraInfo.setZoom(10f, 0.6f, 10f, 1f)
@@ -1317,7 +1170,7 @@
// 1. Arrange
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
- supportedOps = setOf(RestrictedCameraControl.TORCH)
+ supportedOps = setOf(RestrictedCameraInfo.CAMERA_OPERATION_TORCH)
)
fakeCameraInfo.setTorch(TorchState.ON)
@@ -1333,8 +1186,8 @@
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
supportedOps = setOf(
- RestrictedCameraControl.AUTO_FOCUS,
- RestrictedCameraControl.AF_REGION
+ RestrictedCameraInfo.CAMERA_OPERATION_AUTO_FOCUS,
+ RestrictedCameraInfo.CAMERA_OPERATION_AF_REGION
)
)
fakeCameraInfo.setIsFocusMeteringSupported(true)
@@ -1352,7 +1205,7 @@
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
supportedOps = setOf(
- RestrictedCameraControl.EXPOSURE_COMPENSATION,
+ RestrictedCameraInfo.CAMERA_OPERATION_EXPOSURE_COMPENSATION,
)
)
fakeCameraInfo.setExposureState(2, Range.create(0, 10), Rational(1, 1), true)
@@ -1374,7 +1227,7 @@
// 1. Arrange
val cameraUseCaseAdapter =
createAdapterWithSupportedCameraOperations(
- supportedOps = setOf(RestrictedCameraControl.FLASH)
+ supportedOps = setOf(RestrictedCameraInfo.CAMERA_OPERATION_FLASH)
)
// 2. Act && Assert
@@ -1385,13 +1238,9 @@
@Test
fun cameraInfo_postviewSupported(): Unit = runBlocking {
// 1. Arrange
- val cameraConfig: CameraConfig = FakeCameraConfig(postviewSupported = true)
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
- cameraConfig
+ FakeCameraConfig(postviewSupported = true)
)
val cameraInfoInternal = cameraUseCaseAdapter.cameraInfo as CameraInfoInternal
// 2. Act && Assert
@@ -1401,13 +1250,9 @@
@Test
fun cameraInfo_captureProcessProgressSupported(): Unit = runBlocking {
// 1. Arrange
- val cameraConfig: CameraConfig = FakeCameraConfig(captureProcessProgressSupported = true)
- val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
fakeCamera,
- cameraCoordinator,
- fakeCameraDeviceSurfaceManager,
- useCaseConfigFactory,
- cameraConfig
+ FakeCameraConfig(captureProcessProgressSupported = true)
)
val cameraInfoInternal = cameraUseCaseAdapter.cameraInfo as CameraInfoInternal
@@ -1415,6 +1260,22 @@
assertThat(cameraInfoInternal.isCaptureProcessProgressSupported).isTrue()
}
+ @RequiresApi(23)
+ @Test
+ fun returnsCorrectSessionProcessorFromRestrictedCameraControl() {
+ val fakeSessionProcessor = FakeSessionProcessor()
+ val cameraUseCaseAdapter = createCameraUseCaseAdapter(
+ fakeCamera,
+ FakeCameraConfig(fakeSessionProcessor)
+ )
+
+ val cameraControl = cameraUseCaseAdapter.cameraControl
+ assertThat(cameraControl).isInstanceOf(RestrictedCameraControl::class.java)
+ assertThat((cameraControl as RestrictedCameraControl).sessionProcessor).isSameInstanceAs(
+ fakeSessionProcessor
+ )
+ }
+
private fun createFakeVideoCaptureUseCase(): FakeUseCase {
return FakeUseCaseConfig.Builder()
.setCaptureType(CaptureType.VIDEO_CAPTURE)
@@ -1473,4 +1334,23 @@
}
return false
}
+
+ /**
+ * Creates a [CameraUseCaseAdapter] with the given parameters and adds it to the list of
+ * adapters to detach.
+ */
+ private fun createCameraUseCaseAdapter(
+ cameraInternal: CameraInternal,
+ cameraConfig: CameraConfig = CameraConfigs.defaultConfig()
+ ): CameraUseCaseAdapter {
+ val adapter = CameraUseCaseAdapter(
+ cameraInternal,
+ RestrictedCameraInfo(cameraInternal.cameraInfoInternal, cameraConfig),
+ cameraCoordinator,
+ fakeCameraDeviceSurfaceManager,
+ useCaseConfigFactory
+ )
+ adaptersToDetach.add(adapter)
+ return adapter
+ }
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
index f9dbd40..0f4ad5b 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt
@@ -49,7 +49,6 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@@ -379,7 +378,6 @@
}
}
- @Ignore("Flaking in presubmit (b/323202283)")
@Test(expected = IllegalArgumentException::class)
fun cropSizeMismatchesOutputSize_throwsException() {
createSurfaceProcessorNode()
diff --git a/camera/camera-effects-still-portrait/api/1.4.0-beta01.txt b/camera/camera-effects-still-portrait/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/camera/camera-effects-still-portrait/api/1.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/camera/camera-effects-still-portrait/api/res-1.4.0-beta01.txt b/camera/camera-effects-still-portrait/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-effects-still-portrait/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-effects-still-portrait/api/restricted_1.4.0-beta01.txt b/camera/camera-effects-still-portrait/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/camera/camera-effects-still-portrait/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/camera/camera-effects/api/1.4.0-beta01.txt b/camera/camera-effects/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..e47a913
--- /dev/null
+++ b/camera/camera-effects/api/1.4.0-beta01.txt
@@ -0,0 +1,30 @@
+// Signature format: 4.0
+package androidx.camera.effects {
+
+ @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class Frame {
+ ctor public Frame();
+ method public abstract android.graphics.Rect getCropRect();
+ method public android.graphics.Canvas getOverlayCanvas();
+ method @IntRange(from=0, to=359) public abstract int getRotationDegrees();
+ method public abstract android.graphics.Matrix getSensorToBufferTransform();
+ method public abstract android.util.Size getSize();
+ method public abstract long getTimestampNanos();
+ method public abstract boolean isMirroring();
+ }
+
+ @RequiresApi(21) public class OverlayEffect extends androidx.camera.core.CameraEffect implements java.lang.AutoCloseable {
+ ctor public OverlayEffect(int, int, android.os.Handler, androidx.core.util.Consumer<java.lang.Throwable!>);
+ method public void clearOnDrawListener();
+ method public void close();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> drawFrameAsync(long);
+ method public android.os.Handler getHandler();
+ method public int getQueueDepth();
+ method public void setOnDrawListener(androidx.arch.core.util.Function<androidx.camera.effects.Frame!,java.lang.Boolean!>);
+ field public static final int RESULT_CANCELLED_BY_CALLER = 4; // 0x4
+ field public static final int RESULT_FRAME_NOT_FOUND = 2; // 0x2
+ field public static final int RESULT_INVALID_SURFACE = 3; // 0x3
+ field public static final int RESULT_SUCCESS = 1; // 0x1
+ }
+
+}
+
diff --git a/camera/camera-effects/api/res-1.4.0-beta01.txt b/camera/camera-effects/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-effects/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-effects/api/restricted_1.4.0-beta01.txt b/camera/camera-effects/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..e47a913
--- /dev/null
+++ b/camera/camera-effects/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,30 @@
+// Signature format: 4.0
+package androidx.camera.effects {
+
+ @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class Frame {
+ ctor public Frame();
+ method public abstract android.graphics.Rect getCropRect();
+ method public android.graphics.Canvas getOverlayCanvas();
+ method @IntRange(from=0, to=359) public abstract int getRotationDegrees();
+ method public abstract android.graphics.Matrix getSensorToBufferTransform();
+ method public abstract android.util.Size getSize();
+ method public abstract long getTimestampNanos();
+ method public abstract boolean isMirroring();
+ }
+
+ @RequiresApi(21) public class OverlayEffect extends androidx.camera.core.CameraEffect implements java.lang.AutoCloseable {
+ ctor public OverlayEffect(int, int, android.os.Handler, androidx.core.util.Consumer<java.lang.Throwable!>);
+ method public void clearOnDrawListener();
+ method public void close();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> drawFrameAsync(long);
+ method public android.os.Handler getHandler();
+ method public int getQueueDepth();
+ method public void setOnDrawListener(androidx.arch.core.util.Function<androidx.camera.effects.Frame!,java.lang.Boolean!>);
+ field public static final int RESULT_CANCELLED_BY_CALLER = 4; // 0x4
+ field public static final int RESULT_FRAME_NOT_FOUND = 2; // 0x2
+ field public static final int RESULT_INVALID_SURFACE = 3; // 0x3
+ field public static final int RESULT_SUCCESS = 1; // 0x1
+ }
+
+}
+
diff --git a/camera/camera-extensions/api/1.4.0-beta01.txt b/camera/camera-extensions/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..ab74c80
--- /dev/null
+++ b/camera/camera-extensions/api/1.4.0-beta01.txt
@@ -0,0 +1,35 @@
+// Signature format: 4.0
+package androidx.camera.extensions {
+
+ public interface CameraExtensionsControl {
+ method public default void setExtensionStrength(@IntRange(from=0, to=100) int);
+ }
+
+ public interface CameraExtensionsInfo {
+ method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getCurrentExtensionType();
+ method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getExtensionStrength();
+ method public default boolean isCurrentExtensionTypeAvailable();
+ method public default boolean isExtensionStrengthAvailable();
+ }
+
+ @RequiresApi(21) public final class ExtensionMode {
+ field public static final int AUTO = 5; // 0x5
+ field public static final int BOKEH = 1; // 0x1
+ field public static final int FACE_RETOUCH = 4; // 0x4
+ field public static final int HDR = 2; // 0x2
+ field public static final int NIGHT = 3; // 0x3
+ field public static final int NONE = 0; // 0x0
+ }
+
+ @RequiresApi(21) public final class ExtensionsManager {
+ method public androidx.camera.extensions.CameraExtensionsControl? getCameraExtensionsControl(androidx.camera.core.CameraControl);
+ method public androidx.camera.extensions.CameraExtensionsInfo getCameraExtensionsInfo(androidx.camera.core.CameraInfo);
+ method public android.util.Range<java.lang.Long!>? getEstimatedCaptureLatencyRange(androidx.camera.core.CameraSelector, int);
+ method public androidx.camera.core.CameraSelector getExtensionEnabledCameraSelector(androidx.camera.core.CameraSelector, int);
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.extensions.ExtensionsManager!> getInstanceAsync(android.content.Context, androidx.camera.core.CameraProvider);
+ method public boolean isExtensionAvailable(androidx.camera.core.CameraSelector, int);
+ method public boolean isImageAnalysisSupported(androidx.camera.core.CameraSelector, int);
+ }
+
+}
+
diff --git a/camera/camera-extensions/api/current.txt b/camera/camera-extensions/api/current.txt
index 5c6e740..ab74c80 100644
--- a/camera/camera-extensions/api/current.txt
+++ b/camera/camera-extensions/api/current.txt
@@ -1,6 +1,17 @@
// Signature format: 4.0
package androidx.camera.extensions {
+ public interface CameraExtensionsControl {
+ method public default void setExtensionStrength(@IntRange(from=0, to=100) int);
+ }
+
+ public interface CameraExtensionsInfo {
+ method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getCurrentExtensionType();
+ method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getExtensionStrength();
+ method public default boolean isCurrentExtensionTypeAvailable();
+ method public default boolean isExtensionStrengthAvailable();
+ }
+
@RequiresApi(21) public final class ExtensionMode {
field public static final int AUTO = 5; // 0x5
field public static final int BOKEH = 1; // 0x1
@@ -11,6 +22,8 @@
}
@RequiresApi(21) public final class ExtensionsManager {
+ method public androidx.camera.extensions.CameraExtensionsControl? getCameraExtensionsControl(androidx.camera.core.CameraControl);
+ method public androidx.camera.extensions.CameraExtensionsInfo getCameraExtensionsInfo(androidx.camera.core.CameraInfo);
method public android.util.Range<java.lang.Long!>? getEstimatedCaptureLatencyRange(androidx.camera.core.CameraSelector, int);
method public androidx.camera.core.CameraSelector getExtensionEnabledCameraSelector(androidx.camera.core.CameraSelector, int);
method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.extensions.ExtensionsManager!> getInstanceAsync(android.content.Context, androidx.camera.core.CameraProvider);
diff --git a/camera/camera-extensions/api/res-1.4.0-beta01.txt b/camera/camera-extensions/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-extensions/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-extensions/api/restricted_1.4.0-beta01.txt b/camera/camera-extensions/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..ab74c80
--- /dev/null
+++ b/camera/camera-extensions/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,35 @@
+// Signature format: 4.0
+package androidx.camera.extensions {
+
+ public interface CameraExtensionsControl {
+ method public default void setExtensionStrength(@IntRange(from=0, to=100) int);
+ }
+
+ public interface CameraExtensionsInfo {
+ method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getCurrentExtensionType();
+ method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getExtensionStrength();
+ method public default boolean isCurrentExtensionTypeAvailable();
+ method public default boolean isExtensionStrengthAvailable();
+ }
+
+ @RequiresApi(21) public final class ExtensionMode {
+ field public static final int AUTO = 5; // 0x5
+ field public static final int BOKEH = 1; // 0x1
+ field public static final int FACE_RETOUCH = 4; // 0x4
+ field public static final int HDR = 2; // 0x2
+ field public static final int NIGHT = 3; // 0x3
+ field public static final int NONE = 0; // 0x0
+ }
+
+ @RequiresApi(21) public final class ExtensionsManager {
+ method public androidx.camera.extensions.CameraExtensionsControl? getCameraExtensionsControl(androidx.camera.core.CameraControl);
+ method public androidx.camera.extensions.CameraExtensionsInfo getCameraExtensionsInfo(androidx.camera.core.CameraInfo);
+ method public android.util.Range<java.lang.Long!>? getEstimatedCaptureLatencyRange(androidx.camera.core.CameraSelector, int);
+ method public androidx.camera.core.CameraSelector getExtensionEnabledCameraSelector(androidx.camera.core.CameraSelector, int);
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.extensions.ExtensionsManager!> getInstanceAsync(android.content.Context, androidx.camera.core.CameraProvider);
+ method public boolean isExtensionAvailable(androidx.camera.core.CameraSelector, int);
+ method public boolean isImageAnalysisSupported(androidx.camera.core.CameraSelector, int);
+ }
+
+}
+
diff --git a/camera/camera-extensions/api/restricted_current.txt b/camera/camera-extensions/api/restricted_current.txt
index 5c6e740..ab74c80 100644
--- a/camera/camera-extensions/api/restricted_current.txt
+++ b/camera/camera-extensions/api/restricted_current.txt
@@ -1,6 +1,17 @@
// Signature format: 4.0
package androidx.camera.extensions {
+ public interface CameraExtensionsControl {
+ method public default void setExtensionStrength(@IntRange(from=0, to=100) int);
+ }
+
+ public interface CameraExtensionsInfo {
+ method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getCurrentExtensionType();
+ method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getExtensionStrength();
+ method public default boolean isCurrentExtensionTypeAvailable();
+ method public default boolean isExtensionStrengthAvailable();
+ }
+
@RequiresApi(21) public final class ExtensionMode {
field public static final int AUTO = 5; // 0x5
field public static final int BOKEH = 1; // 0x1
@@ -11,6 +22,8 @@
}
@RequiresApi(21) public final class ExtensionsManager {
+ method public androidx.camera.extensions.CameraExtensionsControl? getCameraExtensionsControl(androidx.camera.core.CameraControl);
+ method public androidx.camera.extensions.CameraExtensionsInfo getCameraExtensionsInfo(androidx.camera.core.CameraInfo);
method public android.util.Range<java.lang.Long!>? getEstimatedCaptureLatencyRange(androidx.camera.core.CameraSelector, int);
method public androidx.camera.core.CameraSelector getExtensionEnabledCameraSelector(androidx.camera.core.CameraSelector, int);
method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.extensions.ExtensionsManager!> getInstanceAsync(android.content.Context, androidx.camera.core.CameraProvider);
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
index 18e7195..261260b 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
@@ -18,6 +18,8 @@
import android.content.Context
import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
+import android.util.Pair
import android.util.Range
import android.util.Size
import androidx.annotation.NonNull
@@ -28,12 +30,21 @@
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.impl.CameraInfoInternal
import androidx.camera.core.impl.MutableStateObservable
+import androidx.camera.core.impl.RestrictedCameraInfo
+import androidx.camera.core.impl.SessionProcessor
import androidx.camera.extensions.impl.ExtensionsTestlibControl
+import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImpl
+import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl
+import androidx.camera.extensions.impl.advanced.OutputSurfaceConfigurationImpl
+import androidx.camera.extensions.impl.advanced.OutputSurfaceImpl
+import androidx.camera.extensions.impl.advanced.RequestProcessorImpl
+import androidx.camera.extensions.impl.advanced.SessionProcessorImpl
import androidx.camera.extensions.internal.ClientVersion
import androidx.camera.extensions.internal.ExtensionVersion
import androidx.camera.extensions.internal.ExtensionsUtils
import androidx.camera.extensions.internal.VendorExtender
import androidx.camera.extensions.internal.Version
+import androidx.camera.extensions.internal.sessionprocessor.AdvancedSessionProcessor
import androidx.camera.extensions.util.ExtensionsTestUtil
import androidx.camera.extensions.util.ExtensionsTestUtil.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.lifecycle.ProcessCameraProvider
@@ -50,6 +61,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.assertThrows
import com.google.common.truth.Truth.assertThat
+import java.util.Collections
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
@@ -640,6 +652,115 @@
assertThat(camera.extendedConfig.isCaptureProcessProgressSupported).isTrue()
}
+ @Test
+ fun returnsCorrectInitialTypeFromSessionProcessor() = runBlocking {
+ val extensionCameraSelector = checkExtensionAvailabilityAndInit()
+
+ val camera = withContext(Dispatchers.Main) {
+ cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
+ }
+
+ val sessionProcessor = camera.extendedConfig.sessionProcessor
+ val cameraExtensionsInfo = sessionProcessor as CameraExtensionsInfo
+ val currentType = cameraExtensionsInfo.currentExtensionType
+ if (cameraExtensionsInfo.isCurrentExtensionTypeAvailable) {
+ assertThat(currentType!!.value).isEqualTo(extensionMode)
+ } else {
+ assertThat(currentType).isNull()
+ }
+ }
+
+ @Test
+ fun returnsCorrectExtensionTypeFromCameraExtensionsInfo() = runBlocking {
+ val extensionCameraSelector = checkExtensionAvailabilityAndInit()
+
+ val camera = withContext(Dispatchers.Main) {
+ cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
+ }
+
+ val cameraExtensionsInfo = extensionsManager.getCameraExtensionsInfo(camera.cameraInfo)
+
+ if (cameraExtensionsInfo.isCurrentExtensionTypeAvailable) {
+ assertThat(cameraExtensionsInfo.currentExtensionType!!.value).isEqualTo(
+ extensionMode
+ )
+ } else {
+ assertThat(cameraExtensionsInfo.currentExtensionType).isNull()
+ }
+ }
+
+ @Test
+ fun returnsCorrectExtensionStrengthAvailabilityFromCameraExtensionsInfo() = runBlocking {
+ val extensionCameraSelector = checkExtensionAvailabilityAndInit()
+
+ val camera = withContext(Dispatchers.Main) {
+ cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
+ }
+
+ val cameraExtensionsInfo = extensionsManager.getCameraExtensionsInfo(camera.cameraInfo)
+
+ assertThat(cameraExtensionsInfo.isExtensionStrengthAvailable).isEqualTo(
+ camera.extendedConfig.sessionProcessor.supportedCameraOperations.contains(
+ RestrictedCameraInfo.CAMERA_OPERATION_EXTENSION_STRENGTH
+ )
+ )
+ }
+
+ @Test
+ fun returnsCorrectCurrentExtensionTypeAvailabilityFromCameraExtensionsInfo() = runBlocking {
+ assumeTrue(ExtensionVersion.isAdvancedExtenderSupported())
+ assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
+ val extensionCameraSelector = checkExtensionAvailabilityAndInit()
+
+ // Inject fake VendorExtenderFactory to provide custom VendorExtender
+ extensionsManager.setVendorExtenderFactory {
+ object : VendorExtender {
+ override fun isExtensionAvailable(
+ cameraId: String,
+ characteristicsMap: MutableMap<String, CameraCharacteristics>
+ ): Boolean {
+ return true
+ }
+
+ override fun isCurrentExtensionTypeAvailable(): Boolean {
+ return true
+ }
+
+ override fun createSessionProcessor(context: Context): SessionProcessor? {
+ return AdvancedSessionProcessor(
+ FakeSessionProcessorImpl(),
+ Collections.emptyList(),
+ this,
+ context,
+ extensionMode
+ )
+ }
+ }
+ }
+
+ val camera = withContext(Dispatchers.Main) {
+ cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
+ }
+ val cameraExtensionsInfo = extensionsManager.getCameraExtensionsInfo(camera.cameraInfo)
+ assertThat(cameraExtensionsInfo.isCurrentExtensionTypeAvailable).isTrue()
+ }
+
+ @Test
+ fun returnsCorrectInitialExtensionStrengthFromCameraExtensionsInfo() = runBlocking {
+ val extensionCameraSelector = checkExtensionAvailabilityAndInit()
+
+ val camera = withContext(Dispatchers.Main) {
+ cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
+ }
+
+ val cameraExtensionsInfo = extensionsManager.getCameraExtensionsInfo(camera.cameraInfo)
+ if (cameraExtensionsInfo.isExtensionStrengthAvailable) {
+ assertThat(cameraExtensionsInfo.extensionStrength!!.value).isEqualTo(100)
+ } else {
+ assertThat(cameraExtensionsInfo.extensionStrength).isNull()
+ }
+ }
+
private fun checkExtensionAvailabilityAndInit(): CameraSelector {
extensionsManager = ExtensionsManager.getInstanceAsync(
context,
@@ -659,6 +780,43 @@
)
}
+ @Test
+ fun returnsCorrectExtensionStrengthFromCameraExtensionsInfoForNormalMode() = runBlocking {
+ // Runs the test only when the parameterized extension mode is BOKEH to avoid wasting time
+ assumeTrue(extensionMode == ExtensionMode.BOKEH)
+ extensionsManager = ExtensionsManager.getInstanceAsync(
+ context,
+ cameraProvider
+ )[10000, TimeUnit.MILLISECONDS]
+
+ val camera = withContext(Dispatchers.Main) {
+ cameraProvider.bindToLifecycle(FakeLifecycleOwner(), baseCameraSelector)
+ }
+
+ val cameraExtensionsInfo = extensionsManager.getCameraExtensionsInfo(camera.cameraInfo)
+ assertThat(cameraExtensionsInfo.isExtensionStrengthAvailable).isFalse()
+ assertThat(cameraExtensionsInfo.extensionStrength).isNull()
+ }
+
+ @Test
+ fun retrievesCameraExtensionsControlFromCameraControl(): Unit = runBlocking {
+ val extensionCameraSelector = checkExtensionAvailabilityAndInit()
+
+ // Retrieves null CameraExtensionsControl from normal mode camera's CameraControl
+ withContext(Dispatchers.Main) {
+ cameraProvider.bindToLifecycle(FakeLifecycleOwner(), baseCameraSelector)
+ }.also {
+ assertThat(extensionsManager.getCameraExtensionsControl(it.cameraControl)).isNull()
+ }
+
+ // Retrieves non-null CameraExtensionsControl from extensions-enabled camera's CameraControl
+ withContext(Dispatchers.Main) {
+ cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
+ }.also {
+ assertThat(extensionsManager.getCameraExtensionsControl(it.cameraControl)).isNotNull()
+ }
+ }
+
private fun isExtensionAvailableByCameraInfo(cameraInfo: CameraInfo): Boolean {
var vendorExtender = ExtensionsTestUtil.createVendorExtender(extensionMode)
val cameraId = (cameraInfo as CameraInfoInternal).cameraId
@@ -692,4 +850,49 @@
this.sourceState = sourceState
}
}
+
+ private class FakeSessionProcessorImpl : SessionProcessorImpl {
+ override fun initSession(
+ cameraId: String,
+ cameraCharacteristicsMap: MutableMap<String, CameraCharacteristics>,
+ context: Context,
+ surfaceConfigs: OutputSurfaceConfigurationImpl
+ ): Camera2SessionConfigImpl = FakeCamera2SessionConfigImpl()
+
+ override fun initSession(
+ cameraId: String,
+ cameraCharacteristicsMap: MutableMap<String, CameraCharacteristics>,
+ context: Context,
+ previewSurfaceConfig: OutputSurfaceImpl,
+ imageCaptureSurfaceConfig: OutputSurfaceImpl,
+ imageAnalysisSurfaceConfig: OutputSurfaceImpl?
+ ): Camera2SessionConfigImpl = FakeCamera2SessionConfigImpl()
+
+ override fun deInitSession() {}
+ override fun setParameters(parameters: MutableMap<CaptureRequest.Key<*>, Any>) {}
+ override fun startTrigger(
+ triggers: MutableMap<CaptureRequest.Key<*>, Any>,
+ callback: SessionProcessorImpl.CaptureCallback
+ ): Int = 0
+
+ override fun onCaptureSessionStart(requestProcessor: RequestProcessorImpl) {}
+ override fun onCaptureSessionEnd() {}
+ override fun startRepeating(callback: SessionProcessorImpl.CaptureCallback): Int = 0
+ override fun stopRepeating() {
+ }
+
+ override fun startCapture(callback: SessionProcessorImpl.CaptureCallback): Int = 0
+ override fun startCaptureWithPostview(callback: SessionProcessorImpl.CaptureCallback): Int =
+ 0
+
+ override fun abortCapture(captureSequenceId: Int) {}
+ override fun getRealtimeCaptureLatency(): Pair<Long, Long>? = null
+ }
+
+ private class FakeCamera2SessionConfigImpl : Camera2SessionConfigImpl {
+ override fun getOutputConfigs(): MutableList<Camera2OutputConfigImpl> = mutableListOf()
+ override fun getSessionParameters(): MutableMap<CaptureRequest.Key<*>, Any> = mutableMapOf()
+ override fun getSessionTemplateId(): Int = 0
+ override fun getSessionType(): Int = 0
+ }
}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
index 1fb4e40..5a723c0 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
@@ -21,6 +21,7 @@
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraExtensionCharacteristics.EXTENSION_FACE_RETOUCH
import android.hardware.camera2.CaptureFailure
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.CaptureResult
@@ -31,6 +32,7 @@
import android.media.ImageWriter
import android.os.Build
import android.util.Pair
+import android.util.Range
import android.util.Size
import android.view.Surface
import androidx.annotation.RequiresApi
@@ -54,15 +56,17 @@
import androidx.camera.core.impl.OutputSurfaceConfiguration
import androidx.camera.core.impl.SessionProcessor
import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.extensions.ExtensionMode
+import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl
import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImpl
import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImplBuilder
import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl
-import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImplBuilder
import androidx.camera.extensions.impl.advanced.OutputSurfaceConfigurationImpl
import androidx.camera.extensions.impl.advanced.OutputSurfaceImpl
import androidx.camera.extensions.impl.advanced.RequestProcessorImpl
import androidx.camera.extensions.impl.advanced.SessionProcessorImpl
import androidx.camera.extensions.internal.sessionprocessor.AdvancedSessionProcessor
+import androidx.camera.extensions.util.Camera2SessionConfigImplBuilder
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.testing.fakes.FakeCameraInfoInternal
import androidx.camera.testing.impl.CameraUtil
@@ -238,6 +242,245 @@
assertThat(realtimeCaptureLatencyEstimate?.second).isEqualTo(10L)
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun isCurrentExtensionTypeAvailableReturnsCorrectFalseValue() =
+ runBlocking {
+ assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
+ ClientVersion.setCurrentVersion(ClientVersion("1.4.0"))
+
+ val advancedVendorExtender = AdvancedVendorExtender(FakeAdvancedExtenderImpl())
+
+ val advancedSessionProcessor = AdvancedSessionProcessor(
+ FakeSessionProcessImpl(), emptyList(), advancedVendorExtender, context
+ )
+
+ assertThat(advancedSessionProcessor.isCurrentExtensionTypeAvailable).isFalse()
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun isCurrentExtensionTypeAvailableReturnsCorrectTrueValue() =
+ runBlocking {
+ assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
+ ClientVersion.setCurrentVersion(ClientVersion("1.4.0"))
+
+ val advancedVendorExtender = AdvancedVendorExtender(
+ FakeAdvancedExtenderImpl(
+ testCaptureResultKeys = mutableListOf(
+ CaptureResult.EXTENSION_CURRENT_TYPE as CaptureResult.Key<Any>
+ )
+ )
+ )
+
+ val advancedSessionProcessor = AdvancedSessionProcessor(
+ FakeSessionProcessImpl(), emptyList(), advancedVendorExtender, context
+ )
+
+ assertThat(advancedSessionProcessor.isCurrentExtensionTypeAvailable).isTrue()
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun isExtensionStrengthAvailableReturnsCorrectFalseValue() =
+ runBlocking {
+ assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
+ ClientVersion.setCurrentVersion(ClientVersion("1.4.0"))
+
+ val advancedVendorExtender = AdvancedVendorExtender(FakeAdvancedExtenderImpl())
+
+ val advancedSessionProcessor = AdvancedSessionProcessor(
+ FakeSessionProcessImpl(), emptyList(), advancedVendorExtender, context
+ )
+
+ assertThat(advancedSessionProcessor.isExtensionStrengthAvailable).isFalse()
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun isExtensionStrengthAvailableReturnsCorrectTrueValue() =
+ runBlocking {
+ assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
+ ClientVersion.setCurrentVersion(ClientVersion("1.4.0"))
+
+ val advancedVendorExtender = AdvancedVendorExtender(
+ FakeAdvancedExtenderImpl(
+ testCaptureRequestKeys = mutableListOf(
+ CaptureRequest.EXTENSION_STRENGTH as CaptureRequest.Key<Any>
+ )
+ )
+ )
+
+ val advancedSessionProcessor = AdvancedSessionProcessor(
+ FakeSessionProcessImpl(), emptyList(), advancedVendorExtender, context
+ )
+
+ assertThat(advancedSessionProcessor.isExtensionStrengthAvailable).isTrue()
+ }
+
+ private class FakeAdvancedExtenderImpl(
+ val testCaptureRequestKeys: MutableList<CaptureRequest.Key<Any>> = mutableListOf(),
+ val testCaptureResultKeys: MutableList<CaptureResult.Key<Any>> = mutableListOf(),
+ ) : AdvancedExtenderImpl {
+ override fun isExtensionAvailable(
+ cameraId: String,
+ characteristicsMap: MutableMap<String, CameraCharacteristics>
+ ): Boolean = true
+
+ override fun init(
+ cameraId: String,
+ characteristicsMap: MutableMap<String, CameraCharacteristics>
+ ) {
+ }
+
+ override fun getEstimatedCaptureLatencyRange(
+ cameraId: String,
+ captureOutputSize: Size?,
+ imageFormat: Int
+ ): Range<Long>? = null
+
+ override fun getSupportedPreviewOutputResolutions(cameraId: String):
+ MutableMap<Int, MutableList<Size>> = mutableMapOf()
+
+ override fun getSupportedCaptureOutputResolutions(cameraId: String):
+ MutableMap<Int, MutableList<Size>> = mutableMapOf()
+
+ override fun getSupportedPostviewResolutions(captureSize: Size):
+ MutableMap<Int, MutableList<Size>> = mutableMapOf()
+
+ override fun getSupportedYuvAnalysisResolutions(cameraId: String): MutableList<Size>? = null
+ override fun createSessionProcessor(): SessionProcessorImpl = FakeSessionProcessImpl()
+ override fun getAvailableCaptureRequestKeys(): MutableList<CaptureRequest.Key<Any>> =
+ testCaptureRequestKeys
+
+ override fun getAvailableCaptureResultKeys(): MutableList<CaptureResult.Key<Any>> =
+ testCaptureResultKeys
+
+ override fun isCaptureProcessProgressAvailable(): Boolean = false
+ override fun isPostviewAvailable(): Boolean = false
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun getCurrentExtensionType_advancedSessionProcessorMonitorSessionProcessorImplResults(): Unit =
+ runBlocking {
+ assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
+ ClientVersion.setCurrentVersion(ClientVersion("1.4.0"))
+
+ val fakeSessionProcessImpl = object : SessionProcessorImpl by FakeSessionProcessImpl() {
+ private var repeatingCallback: SessionProcessorImpl.CaptureCallback? = null
+
+ override fun startRepeating(callback: SessionProcessorImpl.CaptureCallback): Int {
+ repeatingCallback = callback
+ return 0
+ }
+
+ fun updateCurrentExtensionType(extensionType: Int) {
+ repeatingCallback!!.onCaptureCompleted(
+ 0,
+ 0,
+ mapOf(CaptureResult.EXTENSION_CURRENT_TYPE to extensionType)
+ )
+ }
+ }
+ val advancedSessionProcessor = AdvancedSessionProcessor(
+ fakeSessionProcessImpl, emptyList(), object : VendorExtender {
+ override fun isCurrentExtensionTypeAvailable(): Boolean {
+ return true
+ }
+ }, context, ExtensionMode.AUTO
+ )
+
+ // Starts repeating first to let fakeSessionProcessImpl obtain the
+ // AdvancedSessionProcessor's SessionProcessorImplCaptureCallbackAdapter instance
+ advancedSessionProcessor.startRepeating(object : SessionProcessor.CaptureCallback {})
+ val receivedTypeList = mutableListOf<Int>()
+ // Sets the count as 2 for receiving the initial extension type and the updated
+ // FACE_RETOUCH type
+ val countDownLatch = CountDownLatch(2)
+ withContext(Dispatchers.Main) {
+ advancedSessionProcessor.currentExtensionType.observeForever {
+ receivedTypeList.add(it)
+ countDownLatch.countDown()
+ }
+ }
+ // Updates the current extension type capture result with Camera2 FACE_RETOUCH mode
+ fakeSessionProcessImpl.updateCurrentExtensionType(EXTENSION_FACE_RETOUCH)
+ // Verifies the new type value is updated to the type LiveData
+ assertThat(countDownLatch.await(200, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(receivedTypeList).containsExactlyElementsIn(
+ listOf(
+ ExtensionMode.AUTO,
+ ExtensionMode.FACE_RETOUCH
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun setExtensionStrength_advancedSessionProcessorInvokesSessionProcessorImpl() =
+ runBlocking {
+ assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
+ ClientVersion.setCurrentVersion(ClientVersion("1.4.0"))
+ val setParametersLatch = CountDownLatch(1)
+ val startRepeatingLatch = CountDownLatch(1)
+
+ val fakeSessionProcessImpl = object : SessionProcessorImpl by FakeSessionProcessImpl() {
+ private val UNKNOWN_STRENGTH = -1
+ private var strength = UNKNOWN_STRENGTH
+ override fun setParameters(parameters: MutableMap<CaptureRequest.Key<*>, Any>) {
+ if (parameters.containsKey(CaptureRequest.EXTENSION_STRENGTH)) {
+ strength = parameters[CaptureRequest.EXTENSION_STRENGTH] as Int
+ setParametersLatch.countDown()
+ }
+ }
+
+ override fun startRepeating(callback: SessionProcessorImpl.CaptureCallback): Int {
+ if (strength != UNKNOWN_STRENGTH) {
+ // Updates the new strength result value to onCaptureCompleted
+ callback.onCaptureCompleted(
+ 0,
+ 0,
+ mapOf(CaptureResult.EXTENSION_STRENGTH to strength)
+ )
+ }
+ startRepeatingLatch.countDown()
+ return 0
+ }
+ }
+ val advancedSessionProcessor = AdvancedSessionProcessor(
+ fakeSessionProcessImpl, emptyList(), object : VendorExtender {
+ override fun isExtensionStrengthAvailable(): Boolean {
+ return true
+ }
+ }, context
+ )
+
+ // Starts repeating first to verify that setExtensionStrength function will directly
+ // invoke the SessionProcessImpl#startRepeating function.
+ advancedSessionProcessor.startRepeating(object : SessionProcessor.CaptureCallback {})
+ val newExtensionStrength = 50
+ advancedSessionProcessor.setExtensionStrength(newExtensionStrength)
+ // Verifies that setExtensionStrength will invoke the SessionProcessImpl#setParameters
+ // function.
+ assertThat(setParametersLatch.await(200, TimeUnit.MILLISECONDS)).isTrue()
+ // Verifies that SessionProcessImpl#startRepeating function is invoked to apply the new
+ // strength value.
+ assertThat(startRepeatingLatch.await(200, TimeUnit.MILLISECONDS)).isTrue()
+ // Verifies the new strength value is updated to the strength LiveData
+ val expectedStrengthLatch = CountDownLatch(1)
+ withContext(Dispatchers.Main) {
+ advancedSessionProcessor.extensionStrength.observeForever {
+ if (it == newExtensionStrength) {
+ expectedStrengthLatch.countDown()
+ }
+ }
+ }
+ assertThat(expectedStrengthLatch.await(200, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
@RequiresApi(28)
private suspend fun assumeAllowsSharedSurface() = withContext(Dispatchers.Main) {
val imageReader = ImageReader.newInstance(640, 480, ImageFormat.YUV_420_888, 2)
@@ -588,7 +831,7 @@
analysisOutputConfig?.let { addOutputConfig(it) }
}
- if (ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)) {
+ if (ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4) && sessionType != -1) {
sessionBuilder.setSessionType(sessionType)
}
return sessionBuilder.build()
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/Camera2SessionConfigImplBuilder.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/Camera2SessionConfigImplBuilder.java
new file mode 100644
index 0000000..c2910a5
--- /dev/null
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/util/Camera2SessionConfigImplBuilder.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.extensions.util;
+
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImpl;
+import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A builder implementation to build the {@link Camera2SessionConfigImpl} instance.
+ */
+public class Camera2SessionConfigImplBuilder {
+ private int mSessionTemplateId = CameraDevice.TEMPLATE_PREVIEW;
+ private int mSessionType = SessionConfiguration.SESSION_REGULAR;
+ Map<CaptureRequest.Key<?>, Object> mSessionParameters = new HashMap<>();
+ List<Camera2OutputConfigImpl> mCamera2OutputConfigs = new ArrayList<>();
+
+ public Camera2SessionConfigImplBuilder() {
+ }
+
+ /**
+ * Adds a output config.
+ */
+ @NonNull
+ public Camera2SessionConfigImplBuilder addOutputConfig(
+ @NonNull Camera2OutputConfigImpl outputConfig) {
+ mCamera2OutputConfigs.add(outputConfig);
+ return this;
+ }
+
+ /**
+ * Sets session parameters.
+ */
+ @NonNull
+ public <T> Camera2SessionConfigImplBuilder addSessionParameter(
+ @NonNull CaptureRequest.Key<T> key, @NonNull T value) {
+ mSessionParameters.put(key, value);
+ return this;
+ }
+
+ /**
+ * Sets the template id for session parameters request.
+ */
+ @NonNull
+ public Camera2SessionConfigImplBuilder setSessionTemplateId(int templateId) {
+ mSessionTemplateId = templateId;
+ return this;
+ }
+
+ /**
+ * Sets the session type for the session.
+ */
+ @NonNull
+ public Camera2SessionConfigImplBuilder setSessionType(int sessionType) {
+ mSessionType = sessionType;
+ return this;
+ }
+
+ /**
+ * Gets the session template id.
+ */
+ public int getSessionTemplateId() {
+ return mSessionTemplateId;
+ }
+
+ /**
+ * Gets the session parameters.
+ */
+ @NonNull
+ public Map<CaptureRequest.Key<?>, Object> getSessionParameters() {
+ return mSessionParameters;
+ }
+
+ /**
+ * Gets all the output configs.
+ */
+ @NonNull
+ public List<Camera2OutputConfigImpl> getCamera2OutputConfigs() {
+ return mCamera2OutputConfigs;
+ }
+
+ /**
+ * Gets the camera capture session type.
+ */
+ public int getSessionType() {
+ return mSessionType;
+ }
+
+ /**
+ * Builds a {@link Camera2SessionConfigImpl} instance.
+ */
+ @NonNull
+ public Camera2SessionConfigImpl build() {
+ return new Camera2SessionConfigImplImpl(this);
+ }
+
+ private static class Camera2SessionConfigImplImpl implements
+ Camera2SessionConfigImpl {
+ private final int mSessionTemplateId;
+ private final int mSessionType;
+ private final Map<CaptureRequest.Key<?>, Object> mSessionParameters;
+ private final List<Camera2OutputConfigImpl> mCamera2OutputConfigs;
+
+ Camera2SessionConfigImplImpl(@NonNull Camera2SessionConfigImplBuilder builder) {
+ mSessionTemplateId = builder.getSessionTemplateId();
+ mSessionParameters = builder.getSessionParameters();
+ mCamera2OutputConfigs = builder.getCamera2OutputConfigs();
+ mSessionType = builder.getSessionType();
+ }
+
+ @Override
+ @NonNull
+ public List<Camera2OutputConfigImpl> getOutputConfigs() {
+ return mCamera2OutputConfigs;
+ }
+
+ @Override
+ @NonNull
+ public Map<CaptureRequest.Key<?>, Object> getSessionParameters() {
+ return mSessionParameters;
+ }
+
+ @Override
+ public int getSessionTemplateId() {
+ return mSessionTemplateId;
+ }
+
+ @Override
+ public int getSessionType() {
+ return mSessionType;
+ }
+ }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsControl.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsControl.java
new file mode 100644
index 0000000..c50037b
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsControl.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.extensions;
+
+import androidx.annotation.IntRange;
+import androidx.camera.core.CameraControl;
+
+/**
+ * A camera extensions control instance that allows customization of capture request settings for
+ * supported camera extensions.
+ *
+ * <p>Applications can leverage the
+ * {@link ExtensionsManager#getCameraExtensionsControl(CameraControl)} method to acquire a
+ * CameraExtensionsControl object to manage extension-related settings.
+ */
+public interface CameraExtensionsControl {
+ /**
+ * Sets the extension strength for the extension mode associated with the
+ * CameraExtensionsControl.
+ *
+ * <p>Strength equal to 0 means that the extension must not apply any post-processing and
+ * return a regular captured frame. Strength equal to 100 is the default level of
+ * post-processing applied when the control is not supported or not set by the client. Values
+ * between 0 and 100 will have different effect depending on the extension type as described
+ * below:
+ * <ul>
+ * <li>{@link ExtensionMode#BOKEH} - the strength will control the amount of blur.
+ * <li>{@link ExtensionMode#HDR} and {@link ExtensionMode#NIGHT} - the strength will
+ * control the amount of images fused and the brightness of the final image.
+ * <li>{@link ExtensionMode#FACE_RETOUCH} - the strength value will control the amount of
+ * cosmetic enhancement and skin smoothing.
+ * </ul>
+ *
+ * <p>This will be supported if the
+ * {@link CameraExtensionsInfo#isExtensionStrengthAvailable()} associated to the same
+ * extensions enabled camera returns {@code true}. Invoking this method will be no-op if
+ * extension strength is not supported.
+ *
+ * @param strength the new extension strength value
+ */
+ default void setExtensionStrength(@IntRange(from = 0, to = 100) int strength){
+ }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsControls.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsControls.java
new file mode 100644
index 0000000..9f91fb3
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsControls.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.extensions;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.CameraControl;
+import androidx.camera.core.impl.RestrictedCameraControl;
+import androidx.camera.core.impl.SessionProcessor;
+import androidx.core.util.Preconditions;
+
+/**
+ * Utility methods for operating on {@link CameraExtensionsControl} instances.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class CameraExtensionsControls {
+
+ /**
+ * Returns a {@link CameraExtensionsControl} instance converted from a {@link CameraControl}
+ * object when the {@link CameraControl} is retrieved from a extensions-enabled camera.
+ * Otherwise, returns {@code null}.
+ */
+ @Nullable
+ static CameraExtensionsControl from(@NonNull CameraControl cameraControl) {
+ Preconditions.checkArgument(cameraControl instanceof RestrictedCameraControl, "The input "
+ + "camera control must be an instance retrieved from the camera that is returned "
+ + "by invoking CameraProvider#bindToLifecycle() with an extension enabled camera "
+ + "selector.");
+
+ SessionProcessor sessionProcessor =
+ ((RestrictedCameraControl) cameraControl).getSessionProcessor();
+ if (sessionProcessor instanceof CameraExtensionsControl) {
+ return (CameraExtensionsControl) sessionProcessor;
+ } else {
+ return null;
+ }
+ }
+
+ private CameraExtensionsControls() {
+ }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsInfo.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsInfo.java
new file mode 100644
index 0000000..9c37769
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsInfo.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.extensions;
+
+import androidx.annotation.Nullable;
+import androidx.camera.core.CameraInfo;
+import androidx.lifecycle.LiveData;
+
+/**
+ * A camera extensions info instance that allows to observe or monitor capture request settings
+ * and results for supported camera extensions.
+ *
+ * <p>Applications can leverage the {@link ExtensionsManager#getCameraExtensionsInfo(CameraInfo)}
+ * method to acquire a CameraExtensionsInfo object for observing extension-specific settings and
+ * results.
+ */
+public interface CameraExtensionsInfo {
+
+ /**
+ * Returns whether extension strength is supported for the extensions-enabled camera that is
+ * associated with the CameraExtensionsInfo.
+ *
+ * <p>When extension strength is supported, applications can change the strength setting via
+ * {@link CameraExtensionsControl#setExtensionStrength(int)} and observe the strength value
+ * changes via the {@link LiveData} object returned by {@link #getExtensionStrength()}.
+ *
+ * @return {@code true} if extension strength is supported. Otherwise, returns {@code false}.
+ */
+ default boolean isExtensionStrengthAvailable() {
+ return false;
+ }
+
+ /**
+ * Returns a {@link LiveData} which observes the extension strength changes for the
+ * extensions-enabled camera that is associated with the CameraExtensionsInfo.
+ *
+ * <p>This is only available when {@link #isExtensionStrengthAvailable()} returns {@code true
+ * }. When this is supported, the extension strength value will range from 0 to 100 and will
+ * dynamically change based on the latest adjustments made within the current extension mode.
+ *
+ * @return a {@link LiveData} of {@link Integer} type to observe the extension strength
+ * changes when {@link #isExtensionStrengthAvailable()} returns {@code true}. Otherwise,
+ * returns {@code null}.
+ */
+ @Nullable
+ default LiveData<Integer> getExtensionStrength() {
+ return null;
+ }
+
+ /**
+ * Returns whether reporting the currently active extension type is supported for the
+ * extensions-enabled camera that is associated with the CameraExtensionsInfo.
+ *
+ * <p>When current extension type is supported, applications can observe the current extension
+ * value changes via the {@link LiveData} object returned by {@link #getCurrentExtensionType()}.
+ *
+ * @return {@code true} if current extension type is supported. Otherwise, returns {@code
+ * false}.
+ */
+ default boolean isCurrentExtensionTypeAvailable() {
+ return false;
+ }
+
+ /**
+ * Returns a {@link LiveData} which observes the extension type changes for the
+ * extensions-enabled camera that is associated with the CameraExtensionsInfo.
+ *
+ * <p>This is only available when {@link #isCurrentExtensionTypeAvailable()} returns {@code
+ * true}. When this is supported, the initial value will be equal to the extension type the
+ * session was started with. Then, the current extension type may change over time. For
+ * example, when the extension mode is {@link ExtensionMode#AUTO}, the current extension type
+ * may change to the {@link ExtensionMode#NIGHT} or {@link ExtensionMode#HDR} depending on
+ * the current lighting conditions or environment.
+ *
+ * @return a {@link LiveData} of {@link Integer} type to observe the extension type changes
+ * when {@link #isCurrentExtensionTypeAvailable()} returns {@code true}. Otherwise, returns
+ * {@code null}.
+ */
+ @Nullable
+ default LiveData<Integer> getCurrentExtensionType() {
+ return null;
+ }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsInfos.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsInfos.java
new file mode 100644
index 0000000..73b44af
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/CameraExtensionsInfos.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.extensions;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.CameraInfo;
+import androidx.camera.core.impl.RestrictedCameraInfo;
+import androidx.camera.core.impl.SessionProcessor;
+import androidx.core.util.Preconditions;
+
+/**
+ * Utility methods for operating on {@link CameraExtensionsInfo} instances.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class CameraExtensionsInfos {
+ private static final CameraExtensionsInfo NORMAL_MODE_CAMERA_EXTENSIONS_INFO =
+ new CameraExtensionsInfo() {
+ };
+
+ /**
+ * Returns a {@link CameraExtensionsInfo} instance converted from a {@link CameraInfo} object.
+ */
+ @NonNull
+ static CameraExtensionsInfo from(@NonNull CameraInfo cameraInfo) {
+ Preconditions.checkArgument(cameraInfo instanceof RestrictedCameraInfo, "The input camera"
+ + " info must be an instance retrieved from the camera that is returned "
+ + "by invoking CameraProvider#bindToLifecycle() with an extension enabled camera "
+ + "selector.");
+ SessionProcessor sessionProcessor =
+ ((RestrictedCameraInfo) cameraInfo).getSessionProcessor();
+ if (sessionProcessor instanceof CameraExtensionsInfo) {
+ return (CameraExtensionsInfo) sessionProcessor;
+ } else {
+ return NORMAL_MODE_CAMERA_EXTENSIONS_INFO;
+ }
+ }
+
+ private CameraExtensionsInfos() {
+ }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
index e3dffc2..1069325 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionsManager.java
@@ -28,6 +28,8 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.CameraControl;
+import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraProvider;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.DynamicRange;
@@ -512,6 +514,39 @@
return mExtensionsInfo.isImageAnalysisSupported(cameraSelector, mode);
}
+ /**
+ * Retrieves a {@link CameraExtensionsControl} object that allows customization of capture
+ * request settings for supported camera extensions.
+ *
+ * @param cameraControl the camera control for a camera with a specific extension mode turned
+ * on.
+ * @return a {@link CameraExtensionsControl} object to manage extension-related settings. Or
+ * returns {@code null} if the provided {@link CameraControl} doesn't represent a camera with
+ * enabled extensions.
+ */
+ @Nullable
+ public CameraExtensionsControl getCameraExtensionsControl(
+ @NonNull CameraControl cameraControl) {
+ return CameraExtensionsControls.from(cameraControl);
+ }
+
+ /**
+ * Retrieves a {@link CameraExtensionsInfo} object that allows to observe or monitor capture
+ * request settings and results for supported camera extensions.
+ *
+ * <p>If the provided {@link CameraInfo} doesn't represent a camera with enabled extensions, a
+ * placeholder {@link CameraExtensionsInfo} object will be returned, indicating no extension
+ * type and strength support.
+ *
+ * @param cameraInfo the camera info for a camera with a specific extension mode turned on.
+ * @return a {@link CameraExtensionsInfo} object for observing extension-specific capture
+ * request settings and results.
+ */
+ @NonNull
+ public CameraExtensionsInfo getCameraExtensionsInfo(@NonNull CameraInfo cameraInfo) {
+ return CameraExtensionsInfos.from(cameraInfo);
+ }
+
@VisibleForTesting
@NonNull
ExtensionsAvailability getExtensionsAvailability() {
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
index d63121d..2eb3fb04 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
@@ -20,6 +20,8 @@
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.os.Build;
import android.util.Pair;
import android.util.Range;
import android.util.Size;
@@ -40,7 +42,6 @@
import androidx.camera.extensions.impl.advanced.HdrAdvancedExtenderImpl;
import androidx.camera.extensions.impl.advanced.NightAdvancedExtenderImpl;
import androidx.camera.extensions.internal.compat.workaround.ExtensionDisabledValidator;
-import androidx.camera.extensions.internal.compat.workaround.ImageAnalysisAvailability;
import androidx.camera.extensions.internal.sessionprocessor.AdvancedSessionProcessor;
import androidx.core.util.Preconditions;
@@ -162,13 +163,8 @@
@Override
public Size[] getSupportedYuvAnalysisResolutions() {
Preconditions.checkNotNull(mCameraId, "VendorExtender#init() must be called first");
- ImageAnalysisAvailability imageAnalysisAvailability = new ImageAnalysisAvailability();
- if (!imageAnalysisAvailability.isAvailable(mCameraId, mMode)) {
- return new Size[0];
- }
-
- List<Size> yuvList = mAdvancedExtenderImpl.getSupportedYuvAnalysisResolutions(mCameraId);
- return yuvList == null ? new Size[0] : yuvList.toArray(new Size[0]);
+ // Disable ImageAnalysis
+ return new Size[0];
}
@NonNull
@@ -187,6 +183,21 @@
}
@NonNull
+ private List<CaptureResult.Key> getSupportedResultKeys() {
+ List<CaptureResult.Key> keys = Collections.emptyList();
+ if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_3) >= 0) {
+ try {
+ keys = Collections.unmodifiableList(
+ mAdvancedExtenderImpl.getAvailableCaptureResultKeys());
+ } catch (Exception e) {
+ Logger.e(TAG, "AdvancedExtenderImpl.getAvailableCaptureResultKeys "
+ + "throws exceptions", e);
+ }
+ }
+ return keys;
+ }
+
+ @NonNull
@Override
public Map<Integer, List<Size>> getSupportedPostviewResolutions(@NonNull Size captureSize) {
if (ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)
@@ -218,6 +229,30 @@
}
}
+ @Override
+ public boolean isExtensionStrengthAvailable() {
+ // EXTENSION_STRENGTH is supported since API level 34
+ if (ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)
+ && ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ return getSupportedParameterKeys().contains(CaptureRequest.EXTENSION_STRENGTH);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isCurrentExtensionTypeAvailable() {
+ // EXTENSION_CURRENT_TYPE is supported since API level 34
+ if (ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)
+ && ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ return getSupportedResultKeys().contains(CaptureResult.EXTENSION_CURRENT_TYPE);
+ } else {
+ return false;
+ }
+ }
+
@Nullable
@Override
public SessionProcessor createSessionProcessor(@NonNull Context context) {
@@ -226,6 +261,7 @@
mAdvancedExtenderImpl.createSessionProcessor(),
getSupportedParameterKeys(),
this,
- context);
+ context,
+ mMode);
}
}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
index 786e448..6928283 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
@@ -50,9 +50,7 @@
import androidx.camera.extensions.impl.NightPreviewExtenderImpl;
import androidx.camera.extensions.impl.PreviewExtenderImpl;
import androidx.camera.extensions.internal.compat.workaround.AvailableKeysRetriever;
-import androidx.camera.extensions.internal.compat.workaround.BasicExtenderSurfaceCombinationAvailability;
import androidx.camera.extensions.internal.compat.workaround.ExtensionDisabledValidator;
-import androidx.camera.extensions.internal.compat.workaround.ImageAnalysisAvailability;
import androidx.camera.extensions.internal.sessionprocessor.BasicExtenderSessionProcessor;
import androidx.core.util.Preconditions;
@@ -77,8 +75,6 @@
private String mCameraId;
private CameraCharacteristics mCameraCharacteristics;
private AvailableKeysRetriever mAvailableKeysRetriever = new AvailableKeysRetriever();
- @ExtensionMode.Mode
- private int mMode = ExtensionMode.NONE;
static final List<CaptureRequest.Key> sBaseSupportedKeys = new ArrayList<>(Arrays.asList(
CaptureRequest.SCALER_CROP_REGION,
@@ -100,7 +96,6 @@
public BasicVendorExtender(@ExtensionMode.Mode int mode) {
try {
- mMode = mode;
switch (mode) {
case ExtensionMode.BOKEH:
mPreviewExtenderImpl = new BokehPreviewExtenderImpl();
@@ -310,33 +305,8 @@
@Override
public Size[] getSupportedYuvAnalysisResolutions() {
Preconditions.checkNotNull(mCameraInfo, "VendorExtender#init() must be called first");
-
- // check if the ImageAnalysis is available.
- ImageAnalysisAvailability imageAnalysisAvailability = new ImageAnalysisAvailability();
- if (!imageAnalysisAvailability.isAvailable(mCameraId, mMode)) {
- return new Size[0];
- }
-
- // check if the surface combination supports the ImageAnalysis.
- BasicExtenderSurfaceCombinationAvailability
- surfaceCombinationAvailability = new BasicExtenderSurfaceCombinationAvailability();
- boolean hasPreviewProcessor = mPreviewExtenderImpl.getProcessorType()
- == PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_IMAGE_PROCESSOR;
- boolean hasImageCaptureProcessor = mImageCaptureExtenderImpl.getCaptureProcessor() != null;
- if (!surfaceCombinationAvailability.isImageAnalysisAvailable(
- getHardwareLevel(), hasPreviewProcessor, hasImageCaptureProcessor)) {
- return new Size[0];
- }
-
- return getOutputSizes(ImageFormat.YUV_420_888);
- }
-
- private int getHardwareLevel() {
- Integer hardwareLevel =
- mCameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
-
- return hardwareLevel != null ? hardwareLevel :
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
+ // Disable ImageAnalysis
+ return new Size[0];
}
@NonNull
@@ -422,6 +392,12 @@
}
}
+ @Override
+ public boolean isExtensionStrengthAvailable() {
+ // Extension strength function won't be supported by the basic extender mode.
+ return false;
+ }
+
@Nullable
@Override
public SessionProcessor createSessionProcessor(@NonNull Context context) {
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/VendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/VendorExtender.java
index eb68fae..e2819d8 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/VendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/VendorExtender.java
@@ -150,6 +150,20 @@
}
/**
+ * Returns if extension strength is supported or not.
+ */
+ default boolean isExtensionStrengthAvailable() {
+ return false;
+ }
+
+ /**
+ * Returns if dynamic extension type is supported or not.
+ */
+ default boolean isCurrentExtensionTypeAvailable() {
+ return false;
+ }
+
+ /**
* Creates a {@link SessionProcessor} that is responsible for (1) determining the stream
* configuration based on given output surfaces (2) Requesting OEM implementation to start
* repeating request and performing a still image capture.
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
index 74a8f42..532e1e8 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
@@ -52,14 +52,6 @@
quirks.add(new GetAvailableKeysNeedsOnInit());
}
- if (ImageAnalysisUnavailableQuirk.load()) {
- quirks.add(new ImageAnalysisUnavailableQuirk());
- }
-
- if (ExtraSupportedSurfaceCombinationsQuirk.load()) {
- quirks.add(new ExtraSupportedSurfaceCombinationsQuirk());
- }
-
return quirks;
}
}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java
deleted file mode 100644
index 0d080d5..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java
+++ /dev/null
@@ -1,218 +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.camera.extensions.internal.compat.quirk;
-
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
-import androidx.camera.core.impl.Quirk;
-import androidx.camera.extensions.internal.compat.workaround.BasicExtenderSurfaceCombinationAvailability;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * <p>QuirkSummary
- * Bug Id: b/194149215
- * Description: Quirk required to include extra supported surface combinations which are
- * additional to the guaranteed supported configurations. An example is the
- * Samsung A51's LIMITED-level camera device can support additional YUV/640x480
- * + PRIV/PREVIEW + YUV/MAXIMUM and YUV/640x480 + YUV/PREVIEW + YUV/MAXIMUM
- * configurations.
- * Device(s): Some Samsung devices
- * @see BasicExtenderSurfaceCombinationAvailability
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public class ExtraSupportedSurfaceCombinationsQuirk implements Quirk {
-
- /**
- * All devices in the list can support YUV/640x480 + PRIV/PREVIEW + YUV/MAXIMUM and
- * YUV/640x480 + YUV/PREVIEW + YUV/MAXIMUM configurations.
- */
- private static final Set<String> SUPPORT_EXTRA_FULL_CONFIGURATIONS_SAMSUNG_MODELS =
- new HashSet<>(Arrays.asList(
- "SM-A515F", // Galaxy A51
- "SM-A515U", // Galaxy A51
- "SM-A515U1", // Galaxy A51
- "SM-A515W", // Galaxy A51
- "SM-S515DL", // Galaxy A51
- "SC-54A", // Galaxy A51 5G
- "SCG07", // Galaxy A51 5G
- "SM-A5160", // Galaxy A51 5G
- "SM-A516B", // Galaxy A51 5G
- "SM-A516N", // Galaxy A51 5G
- "SM-A516U", // Galaxy A51 5G
- "SM-A516U1", // Galaxy A51 5G
- "SM-A516V", // Galaxy A51 5G
- "SM-A715F", // Galaxy A71
- "SM-A715W", // Galaxy A71
- "SM-A7160", // Galaxy A71 5G
- "SM-A716B", // Galaxy A71 5G
- "SM-A716U", // Galaxy A71 5G
- "SM-A716U1", // Galaxy A71 5G
- "SM-A716V", // Galaxy A71 5G
- "SM-A8050", // Galaxy A80
- "SM-A805F", // Galaxy A80
- "SM-A805N", // Galaxy A80
- "SCV44", // Galaxy Fold
- "SM-F9000", // Galaxy Fold
- "SM-F900F", // Galaxy Fold
- "SM-F900U", // Galaxy Fold
- "SM-F900U1", // Galaxy Fold
- "SM-F900W", // Galaxy Fold
- "SM-F907B", // Galaxy Fold 5G
- "SM-F907N", // Galaxy Fold 5G
- "SM-N970F", // Galaxy Note10
- "SM-N9700", // Galaxy Note10
- "SM-N970U", // Galaxy Note10
- "SM-N970U1", // Galaxy Note10
- "SM-N970W", // Galaxy Note10
- "SM-N971N", // Galaxy Note10 5G
- "SM-N770F", // Galaxy Note10 Lite
- "SC-01M", // Galaxy Note10+
- "SCV45", // Galaxy Note10+
- "SM-N9750", // Galaxy Note10+
- "SM-N975C", // Galaxy Note10+
- "SM-N975U", // Galaxy Note10+
- "SM-N975U1", // Galaxy Note10+
- "SM-N975W", // Galaxy Note10+
- "SM-N975F", // Galaxy Note10+
- "SM-N976B", // Galaxy Note10+ 5G
- "SM-N976N", // Galaxy Note10+ 5G
- "SM-N9760", // Galaxy Note10+ 5G
- "SM-N976Q", // Galaxy Note10+ 5G
- "SM-N976V", // Galaxy Note10+ 5G
- "SM-N976U", // Galaxy Note10+ 5G
- "SM-N9810", // Galaxy Note20 5G
- "SM-N981N", // Galaxy Note20 5G
- "SM-N981U", // Galaxy Note20 5G
- "SM-N981U1", // Galaxy Note20 5G
- "SM-N981W", // Galaxy Note20 5G
- "SM-N981B", // Galaxy Note20 5G
- "SC-53A", // Galaxy Note20 Ultra 5G
- "SCG06", // Galaxy Note20 Ultra 5G
- "SM-N9860", // Galaxy Note20 Ultra 5G
- "SM-N986N", // Galaxy Note20 Ultra 5G
- "SM-N986U", // Galaxy Note20 Ultra 5G
- "SM-N986U1", // Galaxy Note20 Ultra 5G
- "SM-N986W", // Galaxy Note20 Ultra 5G
- "SM-N986B", // Galaxy Note20 Ultra 5G
- "SC-03L", // Galaxy S10
- "SCV41", // Galaxy S10
- "SM-G973F", // Galaxy S10
- "SM-G973N", // Galaxy S10
- "SM-G9730", // Galaxy S10
- "SM-G9738", // Galaxy S10
- "SM-G973C", // Galaxy S10
- "SM-G973U", // Galaxy S10
- "SM-G973U1", // Galaxy S10
- "SM-G973W", // Galaxy S10
- "SM-G977B", // Galaxy S10 5G
- "SM-G977N", // Galaxy S10 5G
- "SM-G977P", // Galaxy S10 5G
- "SM-G977T", // Galaxy S10 5G
- "SM-G977U", // Galaxy S10 5G
- "SM-G770F", // Galaxy S10 Lite
- "SM-G770U1", // Galaxy S10 Lite
- "SC-04L", // Galaxy S10+
- "SCV42", // Galaxy S10+
- "SM-G975F", // Galaxy S10+
- "SM-G975N", // Galaxy S10+
- "SM-G9750", // Galaxy S10+
- "SM-G9758", // Galaxy S10+
- "SM-G975U", // Galaxy S10+
- "SM-G975U1", // Galaxy S10+
- "SM-G975W", // Galaxy S10+
- "SC-05L", // Galaxy S10+ Olympic Games Edition
- "SM-G970F", // Galaxy S10e
- "SM-G970N", // Galaxy S10e
- "SM-G9700", // Galaxy S10e
- "SM-G9708", // Galaxy S10e
- "SM-G970U", // Galaxy S10e
- "SM-G970U1", // Galaxy S10e
- "SM-G970W", // Galaxy S10e
- "SC-51A", // Galaxy S20 5G
- "SC51Aa", // Galaxy S20 5G
- "SCG01", // Galaxy S20 5G
- "SM-G9810", // Galaxy S20 5G
- "SM-G981N", // Galaxy S20 5G
- "SM-G981U", // Galaxy S20 5G
- "SM-G981U1", // Galaxy S20 5G
- "SM-G981V", // Galaxy S20 5G
- "SM-G981W", // Galaxy S20 5G
- "SM-G981B", // Galaxy S20 5G
- "SCG03", // Galaxy S20 Ultra 5G
- "SM-G9880", // Galaxy S20 Ultra 5G
- "SM-G988N", // Galaxy S20 Ultra 5G
- "SM-G988Q", // Galaxy S20 Ultra 5G
- "SM-G988U", // Galaxy S20 Ultra 5G
- "SM-G988U1", // Galaxy S20 Ultra 5G
- "SM-G988W", // Galaxy S20 Ultra 5G
- "SM-G988B", // Galaxy S20 Ultra 5G
- "SC-52A", // Galaxy S20+ 5G
- "SCG02", // Galaxy S20+ 5G
- "SM-G9860", // Galaxy S20+ 5G
- "SM-G986N", // Galaxy S20+ 5G
- "SM-G986U", // Galaxy S20+ 5G
- "SM-G986U1", // Galaxy S20+ 5G
- "SM-G986W", // Galaxy S20+ 5G
- "SM-G986B", // Galaxy S20+ 5G
- "SCV47", // Galaxy Z Flip
- "SM-F7000", // Galaxy Z Flip
- "SM-F700F", // Galaxy Z Flip
- "SM-F700N", // Galaxy Z Flip
- "SM-F700U", // Galaxy Z Flip
- "SM-F700U1", // Galaxy Z Flip
- "SM-F700W", // Galaxy Z Flip
- "SCG04", // Galaxy Z Flip 5G
- "SM-F7070", // Galaxy Z Flip 5G
- "SM-F707B", // Galaxy Z Flip 5G
- "SM-F707N", // Galaxy Z Flip 5G
- "SM-F707U", // Galaxy Z Flip 5G
- "SM-F707U1", // Galaxy Z Flip 5G
- "SM-F707W", // Galaxy Z Flip 5G
- "SM-F9160", // Galaxy Z Fold2 5G
- "SM-F916B", // Galaxy Z Fold2 5G
- "SM-F916N", // Galaxy Z Fold2 5G
- "SM-F916Q", // Galaxy Z Fold2 5G
- "SM-F916U", // Galaxy Z Fold2 5G
- "SM-F916U1", // Galaxy Z Fold2 5G
- "SM-F916W"// Galaxy Z Fold2 5G
- ));
-
- /**
- * Currently, once the quirk is loaded, it means that the device can support YUV/640x480 +
- * PRIV/PREVIEW + YUV/MAXIMUM and YUV/640x480 + YUV/PREVIEW + YUV/MAXIMUM configurations even
- * if it is LIMITED level device.
- */
- static boolean load() {
- return supportExtraFullConfigurationsSamsungDevice();
- }
-
- private static boolean supportExtraFullConfigurationsSamsungDevice() {
- if (!"samsung".equalsIgnoreCase(Build.BRAND)) {
- return false;
- }
-
- String capitalModelName = Build.MODEL.toUpperCase(Locale.US);
-
- return SUPPORT_EXTRA_FULL_CONFIGURATIONS_SAMSUNG_MODELS.contains(capitalModelName);
- }
-}
-
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ImageAnalysisUnavailableQuirk.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ImageAnalysisUnavailableQuirk.java
deleted file mode 100644
index 17db5c4..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ImageAnalysisUnavailableQuirk.java
+++ /dev/null
@@ -1,113 +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.camera.extensions.internal.compat.quirk;
-
-import android.os.Build;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.camera.core.ImageAnalysis;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.Preview;
-import androidx.camera.core.impl.Quirk;
-import androidx.camera.extensions.ExtensionMode;
-import androidx.camera.extensions.internal.compat.workaround.ImageAnalysisAvailability;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-/**
- * <p>QuirkSummary
- * Bug Id: b/290007642, b/308905323
- * Description: When enabling Extensions on devices that implement the Basic Extender,
- * ImageAnalysis is assumed to be supported always. But this might be false on
- * some devices like Samsung Galaxy S23 Ultra 5G, even if the device hardware
- * level is FULL or above that should be able to support the additional
- * ImageAnalysis no matter the Preview and ImageCapture have capture processor
- * or not. This might cause preview black screen or unable to capture image issues.
- * Device(s): Samsung Galaxy S23 Ultra 5G, Z Fold3 5G, A52s 5G or S22 Ultra devices, and Xiaomi
- * 13T Prod.
- * @see ImageAnalysisAvailability
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public class ImageAnalysisUnavailableQuirk implements Quirk {
- private static final Set<Pair<String, String>> KNOWN_DEVICES = new HashSet<>(
- Arrays.asList(
- Pair.create("samsung", "dm3q"), // Samsung Galaxy S23 Ultra 5G
- Pair.create("samsung", "q2q"), // Samsung Galaxy Z Fold3 5G
- Pair.create("samsung", "a52sxq"), // Samsung Galaxy A52s 5G
- Pair.create("samsung", "b0q"), // Samsung Galaxy S22 Ultra
-
- Pair.create("xiaomi", "corot") // Xiaomi 13T Pro
- ));
- private final Set<Pair<String, Integer>> mUnavailableCombinations = new HashSet<>();
- private boolean mDisableAll = false;
- ImageAnalysisUnavailableQuirk() {
- if (Build.BRAND.equalsIgnoreCase("SAMSUNG")) {
- if (Build.DEVICE.equalsIgnoreCase("dm3q")) { // Samsung Galaxy S23 Ultra 5G
- mUnavailableCombinations.addAll(Arrays.asList(
- Pair.create("0", ExtensionMode.BOKEH), // LEVEL_3
- Pair.create("0", ExtensionMode.FACE_RETOUCH),
- Pair.create("1", ExtensionMode.BOKEH), // LEVEL_FULL
- Pair.create("1", ExtensionMode.FACE_RETOUCH),
- Pair.create("3", ExtensionMode.BOKEH), // LEVEL_FULL
- Pair.create("3", ExtensionMode.FACE_RETOUCH)
- ));
- } else if (Build.DEVICE.equalsIgnoreCase("q2q")) { // Samsung Galaxy Z Fold3 5G
- mUnavailableCombinations.addAll(Arrays.asList(
- Pair.create("0", ExtensionMode.BOKEH), // LEVEL_3
- Pair.create("0", ExtensionMode.FACE_RETOUCH)
- ));
- } else if (Build.DEVICE.equalsIgnoreCase("a52sxq")) { // Samsung Galaxy A52s 5G
- mUnavailableCombinations.addAll(Arrays.asList(
- Pair.create("0", ExtensionMode.BOKEH), // LEVEL_3
- Pair.create("0", ExtensionMode.FACE_RETOUCH)
- ));
- } else if (Build.DEVICE.equalsIgnoreCase("b0q")) { // Samsung Galaxy A52s 5G
- mUnavailableCombinations.addAll(Arrays.asList(
- Pair.create("3", ExtensionMode.BOKEH), // FULL
- Pair.create("3", ExtensionMode.FACE_RETOUCH)
- ));
- }
- } else if (Build.BRAND.equalsIgnoreCase("xiaomi")) {
- if (Build.DEVICE.equalsIgnoreCase("corot")) { // Xiaomi 13T Pro
- mUnavailableCombinations.addAll(Arrays.asList(
- Pair.create("0", ExtensionMode.NIGHT), // LEVEL_3
- Pair.create("1", ExtensionMode.NIGHT) // LEVEL_3
- ));
- }
- } else if (Build.BRAND.equalsIgnoreCase("google")) {
- mDisableAll = true;
- }
- }
-
- static boolean load() {
- return KNOWN_DEVICES.contains(Pair.create(Build.BRAND.toLowerCase(Locale.US),
- Build.DEVICE.toLowerCase(Locale.US)))
- || Build.BRAND.equalsIgnoreCase("google");
- }
-
- /**
- * Returns whether {@link ImageAnalysis} is unavailable to be bound together with
- * {@link Preview} and {@link ImageCapture} for the specified camera id and extensions mode.
- */
- public boolean isUnavailable(@NonNull String cameraId, @ExtensionMode.Mode int mode) {
- return mDisableAll || mUnavailableCombinations.contains(Pair.create(cameraId, mode));
- }
-}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/BasicExtenderSurfaceCombinationAvailability.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/BasicExtenderSurfaceCombinationAvailability.java
deleted file mode 100644
index 85a93fa..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/BasicExtenderSurfaceCombinationAvailability.java
+++ /dev/null
@@ -1,81 +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.camera.extensions.internal.compat.workaround;
-
-import static android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3;
-import static android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL;
-import static android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;
-
-import androidx.annotation.RequiresApi;
-import androidx.camera.core.ImageAnalysis;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.Preview;
-import androidx.camera.extensions.internal.compat.quirk.DeviceQuirks;
-import androidx.camera.extensions.internal.compat.quirk.ExtraSupportedSurfaceCombinationsQuirk;
-
-/**
- * Workaround to check whether {@link ImageAnalysis} can be bound together with {@link Preview} and
- * {@link ImageCapture} when enabling extensions.
- *
- * <p>This is used by the BasicVendorExtender to check whether the device can support to bind the
- * additional ImageAnalysis UseCase.
- *
- * @see ExtraSupportedSurfaceCombinationsQuirk
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public class BasicExtenderSurfaceCombinationAvailability {
- ExtraSupportedSurfaceCombinationsQuirk mExtraSupportedSurfaceCombinationsQuirk =
- DeviceQuirks.get(ExtraSupportedSurfaceCombinationsQuirk.class);
-
- /**
- * Returns whether {@link ImageAnalysis} is available to be bound together with
- * {@link Preview} and {@link ImageCapture} for the specified camera id and extensions mode.
- *
- * @param hardwareLevel the camera device hardware level
- * @param hasPreviewProcessor whether PreviewExtenderImpl has processor
- * @param hasImageCaptureProcessor whether ImageCaptureExtenderImpl has processor
- * @return {@code true} if {@link ImageAnalysis} is available. Otherwise, returns {@code
- * false}.
- */
- public boolean isImageAnalysisAvailable(int hardwareLevel,
- boolean hasPreviewProcessor,
- boolean hasImageCaptureProcessor) {
- // No matter what the device hardware is and no matter the Preview and the ImageCapture
- // have processor or not, once ExtraSupportedSurfaceCombinationsQuirk can be loaded, the
- // device can support to bind ImageAnalysis when enabling the extensions mode.
- if (mExtraSupportedSurfaceCombinationsQuirk != null) {
- return true;
- }
-
- if (!hasPreviewProcessor && !hasImageCaptureProcessor) {
- // Required configuration: PRIV + JPEG + YUV
- // Required HW level: any
- return true;
- } else if (hasPreviewProcessor && !hasImageCaptureProcessor) {
- // Required configuration: YUV + JPEG + YUV
- // Required HW level: LIMITED level or above
- return hardwareLevel == INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
- || hardwareLevel == INFO_SUPPORTED_HARDWARE_LEVEL_FULL
- || hardwareLevel == INFO_SUPPORTED_HARDWARE_LEVEL_3;
- } else {
- // Required configuration: PRIV + YUV + YUV or YUV + YUV + YUV
- // Required HW level: FULL level or above
- return hardwareLevel == INFO_SUPPORTED_HARDWARE_LEVEL_FULL
- || hardwareLevel == INFO_SUPPORTED_HARDWARE_LEVEL_3;
- }
- }
-}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailability.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailability.java
deleted file mode 100644
index 246c800..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailability.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.extensions.internal.compat.workaround;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.camera.core.ImageAnalysis;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.Preview;
-import androidx.camera.extensions.ExtensionMode;
-import androidx.camera.extensions.internal.compat.quirk.DeviceQuirks;
-import androidx.camera.extensions.internal.compat.quirk.ImageAnalysisUnavailableQuirk;
-
-/**
- * Workaround to check whether {@link ImageAnalysis} can be bound together with {@link Preview} and
- * {@link ImageCapture} when enabling extensions.
- *
- * @see ImageAnalysisUnavailableQuirk
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public class ImageAnalysisAvailability {
- ImageAnalysisUnavailableQuirk mImageAnalysisUnavailableQuirk =
- DeviceQuirks.get(ImageAnalysisUnavailableQuirk.class);
-
- /**
- * Returns whether {@link ImageAnalysis} is available to be bound together with
- * {@link Preview} and {@link ImageCapture} for the specified camera id and extensions mode.
- *
- * @param cameraId the camera id to query
- * @param mode the extensions mode to query
- * @return {@code true} if {@link ImageAnalysis} is available. Otherwise, returns {@code
- * false}.
- */
- public boolean isAvailable(@NonNull String cameraId, @ExtensionMode.Mode int mode) {
- if (mImageAnalysisUnavailableQuirk != null
- && mImageAnalysisUnavailableQuirk.isUnavailable(cameraId, mode)) {
- return false;
- }
- return true;
- }
-}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
index 3ff2e1b..a80e685 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
@@ -16,6 +16,13 @@
package androidx.camera.extensions.internal.sessionprocessor;
+import static android.hardware.camera2.CameraExtensionCharacteristics.EXTENSION_AUTOMATIC;
+import static android.hardware.camera2.CameraExtensionCharacteristics.EXTENSION_BOKEH;
+import static android.hardware.camera2.CameraExtensionCharacteristics.EXTENSION_FACE_RETOUCH;
+import static android.hardware.camera2.CameraExtensionCharacteristics.EXTENSION_HDR;
+import static android.hardware.camera2.CameraExtensionCharacteristics.EXTENSION_NIGHT;
+
+import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureFailure;
@@ -24,10 +31,13 @@
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.SessionConfiguration;
import android.media.Image;
+import android.os.Build;
import android.util.Pair;
import android.util.Size;
import android.view.Surface;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -39,6 +49,7 @@
import androidx.camera.core.impl.OutputSurfaceConfiguration;
import androidx.camera.core.impl.RequestProcessor;
import androidx.camera.core.impl.SessionProcessor;
+import androidx.camera.extensions.ExtensionMode;
import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImpl;
import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl;
import androidx.camera.extensions.impl.advanced.ImageProcessorImpl;
@@ -53,11 +64,14 @@
import androidx.camera.extensions.internal.VendorExtender;
import androidx.camera.extensions.internal.Version;
import androidx.core.util.Preconditions;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* A {@link SessionProcessor} based on OEMs' {@link SessionProcessorImpl}.
@@ -71,16 +85,51 @@
private final VendorExtender mVendorExtender;
@NonNull
private final Context mContext;
+ @ExtensionMode.Mode
+ private final int mMode;
+ @Nullable
+ private final MutableLiveData<Integer> mCurrentExtensionTypeLiveData;
private boolean mIsPostviewConfigured = false;
+ // Caches the working capture config so that the new extension strength can be applied on top
+ // of the existing config.
+ @GuardedBy("mLock")
+ private HashMap<CaptureRequest.Key<?>, Object> mWorkingCaptureConfigMap = new HashMap<>();
+ // Caches the capture callback adapter so that repeating can be started again to apply the
+ // new extension strength setting.
+ @GuardedBy("mLock")
+ private SessionProcessorImplCaptureCallbackAdapter mRepeatingCaptureCallbackAdapter = null;
+ @Nullable
+ private final MutableLiveData<Integer> mExtensionStrengthLiveData;
+ @Nullable
+ private final ExtensionMetadataMonitor mExtensionMetadataMonitor;
public AdvancedSessionProcessor(@NonNull SessionProcessorImpl impl,
@NonNull List<CaptureRequest.Key> supportedKeys,
@NonNull VendorExtender vendorExtender,
@NonNull Context context) {
+ this(impl, supportedKeys, vendorExtender, context, ExtensionMode.NONE);
+ }
+
+ public AdvancedSessionProcessor(@NonNull SessionProcessorImpl impl,
+ @NonNull List<CaptureRequest.Key> supportedKeys,
+ @NonNull VendorExtender vendorExtender,
+ @NonNull Context context,
+ @ExtensionMode.Mode int mode) {
super(supportedKeys);
mImpl = impl;
mVendorExtender = vendorExtender;
mContext = context;
+ mMode = mode;
+ mCurrentExtensionTypeLiveData = isCurrentExtensionTypeAvailable() ? new MutableLiveData<>(
+ mMode) : null;
+ mExtensionStrengthLiveData = isExtensionStrengthAvailable() ? new MutableLiveData<>(100)
+ : null;
+ if (mCurrentExtensionTypeLiveData != null || mExtensionStrengthLiveData != null) {
+ mExtensionMetadataMonitor = new ExtensionMetadataMonitor(mCurrentExtensionTypeLiveData,
+ mExtensionStrengthLiveData);
+ } else {
+ mExtensionMetadataMonitor = null;
+ }
}
@NonNull
@@ -119,6 +168,13 @@
}
mIsPostviewConfigured = outputSurfaceConfig.getPostviewOutputSurface() != null;
+ // Resets the extension type and strength result when initializing the session
+ if (mCurrentExtensionTypeLiveData != null) {
+ mCurrentExtensionTypeLiveData.postValue(mMode);
+ }
+ if (mExtensionStrengthLiveData != null) {
+ mExtensionStrengthLiveData.postValue(100);
+ }
// Convert Camera2SessionConfigImpl(implemented in OEM) into Camera2SessionConfig
return convertToCamera2SessionConfig(sessionConfigImpl);
}
@@ -157,14 +213,83 @@
@Override
protected void deInitSessionInternal() {
+ synchronized (mLock) {
+ // Clears the working config map
+ mWorkingCaptureConfigMap = new HashMap<>();
+ mRepeatingCaptureCallbackAdapter = null;
+ }
mImpl.deInitSession();
}
@Override
+ public boolean isCurrentExtensionTypeAvailable() {
+ return mVendorExtender.isCurrentExtensionTypeAvailable();
+ }
+
+ @NonNull
+ @Override
+ public LiveData<Integer> getCurrentExtensionType() {
+ return mCurrentExtensionTypeLiveData;
+ }
+
+ @Override
+ public boolean isExtensionStrengthAvailable() {
+ return mVendorExtender.isExtensionStrengthAvailable();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void setExtensionStrength(@IntRange(from = 0, to = 100) int strength) {
+ if (!isExtensionStrengthAvailable()
+ || Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ return;
+ }
+
+ SessionProcessorImplCaptureCallbackAdapter captureCallbackAdapter;
+ HashMap<CaptureRequest.Key<?>, Object> captureConfigMap;
+
+ synchronized (mLock) {
+ mExtensionStrength = strength;
+ mWorkingCaptureConfigMap.put(CaptureRequest.EXTENSION_STRENGTH, mExtensionStrength);
+ captureCallbackAdapter = mRepeatingCaptureCallbackAdapter;
+ captureConfigMap =
+ (HashMap<CaptureRequest.Key<?>, Object>) mWorkingCaptureConfigMap.clone();
+ }
+
+ mImpl.setParameters(captureConfigMap);
+
+ // Starts the repeating again to apply the new strength setting if it has been started.
+ // Otherwise, the new strength setting will be applied when the capture session is
+ // configured and repeating is started.
+ if (captureCallbackAdapter != null) {
+ mImpl.startRepeating(captureCallbackAdapter);
+ }
+ }
+
+ @SuppressLint("KotlinPropertyAccess")
+ @NonNull
+ @Override
+ public LiveData<Integer> getExtensionStrength() {
+ return mExtensionStrengthLiveData;
+ }
+
+ @Override
public void setParameters(
@NonNull Config parameters) {
- HashMap<CaptureRequest.Key<?>, Object> map = convertConfigToMap(parameters);
- mImpl.setParameters(map);
+ HashMap<CaptureRequest.Key<?>, Object> captureConfigMap;
+
+ synchronized (mLock) {
+ captureConfigMap = convertConfigToMap(parameters);
+ // Applies extension strength setting if it is set via
+ // CameraExtensionsControl#setExtensionStrength() API.
+ if (mExtensionStrength != EXTENSION_STRENGTH_UNKNOWN
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ captureConfigMap.put(CaptureRequest.EXTENSION_STRENGTH, mExtensionStrength);
+ }
+ mWorkingCaptureConfigMap = captureConfigMap;
+ }
+
+ mImpl.setParameters(captureConfigMap);
}
@NonNull
@@ -215,7 +340,13 @@
@Override
public int startRepeating(@NonNull SessionProcessor.CaptureCallback callback) {
- return mImpl.startRepeating(new SessionProcessorImplCaptureCallbackAdapter(callback));
+ SessionProcessorImplCaptureCallbackAdapter captureCallbackAdapter;
+ synchronized (mLock) {
+ captureCallbackAdapter = new SessionProcessorImplCaptureCallbackAdapter(callback,
+ mExtensionMetadataMonitor);
+ mRepeatingCaptureCallbackAdapter = captureCallbackAdapter;
+ }
+ return mImpl.startRepeating(captureCallbackAdapter);
}
@Override
@@ -232,6 +363,9 @@
@Override
public void stopRepeating() {
mImpl.stopRepeating();
+ synchronized (mLock) {
+ mRepeatingCaptureCallbackAdapter = null;
+ }
}
@Override
@@ -300,11 +434,11 @@
mAnalysisOutputSurface =
outputSurfaceConfig.getImageAnalysisOutputSurface() != null
? new OutputSurfaceImplAdapter(
- outputSurfaceConfig.getImageAnalysisOutputSurface()) : null;
+ outputSurfaceConfig.getImageAnalysisOutputSurface()) : null;
mPostviewOutputSurface =
outputSurfaceConfig.getPostviewOutputSurface() != null
? new OutputSurfaceImplAdapter(
- outputSurfaceConfig.getPostviewOutputSurface()) : null;
+ outputSurfaceConfig.getPostviewOutputSurface()) : null;
}
@NonNull
@@ -494,7 +628,7 @@
private final RequestProcessorImpl.Callback mCallback;
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
- CallbackAdapter(RequestProcessorImpl.Callback callback) {
+ CallbackAdapter(@NonNull RequestProcessorImpl.Callback callback) {
mCallback = callback;
}
@@ -571,11 +705,20 @@
private static class SessionProcessorImplCaptureCallbackAdapter implements
SessionProcessorImpl.CaptureCallback {
private final SessionProcessor.CaptureCallback mCaptureCallback;
+ @Nullable
+ private final ExtensionMetadataMonitor mExtensionMetadataMonitor;
+
+ SessionProcessorImplCaptureCallbackAdapter(
+ @NonNull SessionProcessor.CaptureCallback callback) {
+ this(callback, null);
+ }
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
SessionProcessorImplCaptureCallbackAdapter(
- @NonNull SessionProcessor.CaptureCallback callback) {
+ @NonNull SessionProcessor.CaptureCallback callback,
+ @Nullable ExtensionMetadataMonitor extensionMetadataMonitor) {
mCaptureCallback = callback;
+ mExtensionMetadataMonitor = extensionMetadataMonitor;
}
@Override
@@ -609,6 +752,9 @@
@Override
public void onCaptureCompleted(long timestamp, int captureSequenceId,
Map<CaptureResult.Key, Object> result) {
+ if (mExtensionMetadataMonitor != null) {
+ mExtensionMetadataMonitor.checkExtensionMetadata(result);
+ }
mCaptureCallback.onCaptureCompleted(timestamp, captureSequenceId, result);
}
@@ -617,4 +763,67 @@
mCaptureCallback.onCaptureProcessProgressed(progress);
}
}
+
+ /**
+ * Monitors the extension metadata (extension strength, type) changes from the capture results.
+ */
+ private static class ExtensionMetadataMonitor {
+ @Nullable
+ private final MutableLiveData<Integer> mCurrentExtensionTypeLiveData;
+ @Nullable
+ private final MutableLiveData<Integer> mExtensionStrengthLiveData;
+
+ ExtensionMetadataMonitor(
+ @Nullable MutableLiveData<Integer> currentExtensionTypeLiveData,
+ @Nullable MutableLiveData<Integer> extensionStrengthLiveData) {
+ mCurrentExtensionTypeLiveData = currentExtensionTypeLiveData;
+ mExtensionStrengthLiveData = extensionStrengthLiveData;
+ }
+
+ void checkExtensionMetadata(Map<CaptureResult.Key, Object> captureResult) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+
+ if (mCurrentExtensionTypeLiveData != null) {
+ // Monitors and update current extension type
+ Object extensionType = captureResult.get(CaptureResult.EXTENSION_CURRENT_TYPE);
+ // The returned type should be the value defined by the Camera2 API.
+ // Needs to
+ // convert it to the value defined by CameraX.
+ if (extensionType != null && !Objects.equals(
+ mCurrentExtensionTypeLiveData.getValue(),
+ convertExtensionMode((int) extensionType))) {
+ mCurrentExtensionTypeLiveData.postValue(
+ convertExtensionMode((int) extensionType));
+ }
+ }
+
+ if (mExtensionStrengthLiveData != null) {
+ // Monitors and update current extension strength
+ Object extensionStrength = captureResult.get(CaptureResult.EXTENSION_STRENGTH);
+ if (extensionStrength != null && !Objects.equals(
+ mExtensionStrengthLiveData.getValue(), extensionStrength)) {
+ mExtensionStrengthLiveData.postValue((Integer) extensionStrength);
+ }
+ }
+ }
+ }
+
+ @ExtensionMode.Mode
+ private int convertExtensionMode(int camera2ExtensionMode) {
+ switch (camera2ExtensionMode) {
+ case EXTENSION_AUTOMATIC:
+ return ExtensionMode.AUTO;
+ case EXTENSION_FACE_RETOUCH:
+ return ExtensionMode.FACE_RETOUCH;
+ case EXTENSION_BOKEH:
+ return ExtensionMode.BOKEH;
+ case EXTENSION_HDR:
+ return ExtensionMode.HDR;
+ case EXTENSION_NIGHT:
+ return ExtensionMode.NIGHT;
+ default:
+ return ExtensionMode.NONE;
+ }
+ }
+ }
}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
index 858f76e..edcf69a 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
@@ -78,7 +78,6 @@
@NonNull
private final ImageCaptureExtenderImpl mImageCaptureExtenderImpl;
- final Object mLock = new Object();
volatile StillCaptureProcessor mStillCaptureProcessor = null;
volatile PreviewProcessor mPreviewProcessor = null;
volatile RequestUpdateProcessorImpl mRequestUpdateProcessor = null;
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
index c2a9bbb..40f4aac 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
@@ -34,12 +34,14 @@
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.DeferrableSurface;
import androidx.camera.core.impl.OutputSurfaceConfiguration;
-import androidx.camera.core.impl.RestrictedCameraControl;
-import androidx.camera.core.impl.RestrictedCameraControl.CameraOperation;
+import androidx.camera.core.impl.RestrictedCameraInfo;
+import androidx.camera.core.impl.RestrictedCameraInfo.CameraOperation;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.SessionProcessor;
import androidx.camera.core.impl.SessionProcessorSurface;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.extensions.CameraExtensionsControl;
+import androidx.camera.extensions.CameraExtensionsInfo;
import androidx.camera.extensions.internal.ExtensionsUtils;
import androidx.camera.extensions.internal.RequestOptionConfig;
@@ -56,8 +58,13 @@
* maintaining the {@link ImageProcessor} associated with the image reader.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-abstract class SessionProcessorBase implements SessionProcessor {
+abstract class SessionProcessorBase implements SessionProcessor, CameraExtensionsInfo,
+ CameraExtensionsControl {
private static final String TAG = "SessionProcessorBase";
+ /**
+ * Unknown extension strength.
+ */
+ protected static final int EXTENSION_STRENGTH_UNKNOWN = -1;
@NonNull
@GuardedBy("mLock")
private final Map<Integer, ImageReader> mImageReaderMap = new HashMap<>();
@@ -68,11 +75,13 @@
private HandlerThread mImageReaderHandlerThread;
@GuardedBy("mLock")
private final List<DeferrableSurface> mSurfacesList = new ArrayList<>();
- private final Object mLock = new Object();
+ protected final Object mLock = new Object();
private String mCameraId;
@NonNull
private final @CameraOperation Set<Integer> mSupportedCameraOperations;
+ @GuardedBy("mLock")
+ protected int mExtensionStrength = EXTENSION_STRENGTH_UNKNOWN;
SessionProcessorBase(@NonNull List<CaptureRequest.Key> supportedParameterKeys) {
mSupportedCameraOperations = getSupportedCameraOperations(supportedParameterKeys);
@@ -85,49 +94,55 @@
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (supportedParameterKeys.contains(CaptureRequest.CONTROL_ZOOM_RATIO)
|| supportedParameterKeys.contains(CaptureRequest.SCALER_CROP_REGION)) {
- operations.add(RestrictedCameraControl.ZOOM);
+ operations.add(RestrictedCameraInfo.CAMERA_OPERATION_ZOOM);
}
} else {
if (supportedParameterKeys.contains(CaptureRequest.SCALER_CROP_REGION)) {
- operations.add(RestrictedCameraControl.ZOOM);
+ operations.add(RestrictedCameraInfo.CAMERA_OPERATION_ZOOM);
}
}
if (supportedParameterKeys.containsAll(
Arrays.asList(
CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_MODE))) {
- operations.add(RestrictedCameraControl.AUTO_FOCUS);
+ operations.add(RestrictedCameraInfo.CAMERA_OPERATION_AUTO_FOCUS);
}
if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AF_REGIONS)) {
- operations.add(RestrictedCameraControl.AF_REGION);
+ operations.add(RestrictedCameraInfo.CAMERA_OPERATION_AF_REGION);
}
if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AE_REGIONS)) {
- operations.add(RestrictedCameraControl.AE_REGION);
+ operations.add(RestrictedCameraInfo.CAMERA_OPERATION_AE_REGION);
}
if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AWB_REGIONS)) {
- operations.add(RestrictedCameraControl.AWB_REGION);
+ operations.add(RestrictedCameraInfo.CAMERA_OPERATION_AWB_REGION);
}
if (supportedParameterKeys.containsAll(
Arrays.asList(
CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER))) {
- operations.add(RestrictedCameraControl.FLASH);
+ operations.add(RestrictedCameraInfo.CAMERA_OPERATION_FLASH);
}
if (supportedParameterKeys.containsAll(
Arrays.asList(
CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.FLASH_MODE))) {
- operations.add(RestrictedCameraControl.TORCH);
+ operations.add(RestrictedCameraInfo.CAMERA_OPERATION_TORCH);
}
if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION)) {
- operations.add(RestrictedCameraControl.EXPOSURE_COMPENSATION);
+ operations.add(RestrictedCameraInfo.CAMERA_OPERATION_EXPOSURE_COMPENSATION);
}
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ && supportedParameterKeys.contains(CaptureRequest.EXTENSION_STRENGTH)) {
+ operations.add(RestrictedCameraInfo.CAMERA_OPERATION_EXTENSION_STRENGTH);
+ }
+
return operations;
}
@@ -270,6 +285,7 @@
mSurfacesList.clear();
mImageReaderMap.clear();
mOutputConfigMap.clear();
+ mExtensionStrength = EXTENSION_STRENGTH_UNKNOWN;
}
if (mImageReaderHandlerThread != null) {
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt
index 942f4ee8..2e71de0 100644
--- a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt
+++ b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt
@@ -26,7 +26,7 @@
import android.util.Pair
import android.util.Range
import android.util.Size
-import androidx.camera.core.impl.RestrictedCameraControl
+import androidx.camera.core.impl.RestrictedCameraInfo
import androidx.camera.extensions.impl.CaptureStageImpl
import androidx.camera.extensions.impl.ImageCaptureExtenderImpl
import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl
@@ -111,7 +111,7 @@
private fun testSupportedCameraOperation(
supportedCaptureRequestKeys: List<CaptureRequest.Key<out Any>>,
- @RestrictedCameraControl.CameraOperation expectSupportedOperations: Set<Int>
+ @RestrictedCameraInfo.CameraOperation expectSupportedOperations: Set<Int>
) {
var vendorExtender: VendorExtender? = null
if (extenderType == "basic") {
@@ -141,7 +141,7 @@
CaptureRequest.CONTROL_ZOOM_RATIO
),
expectSupportedOperations = setOf(
- RestrictedCameraControl.ZOOM
+ RestrictedCameraInfo.CAMERA_OPERATION_ZOOM
)
)
}
@@ -154,7 +154,7 @@
CaptureRequest.SCALER_CROP_REGION
),
expectSupportedOperations = setOf(
- RestrictedCameraControl.ZOOM
+ RestrictedCameraInfo.CAMERA_OPERATION_ZOOM
)
)
}
@@ -167,7 +167,7 @@
CaptureRequest.SCALER_CROP_REGION
),
expectSupportedOperations = setOf(
- RestrictedCameraControl.ZOOM
+ RestrictedCameraInfo.CAMERA_OPERATION_ZOOM
)
)
}
@@ -180,7 +180,7 @@
CaptureRequest.CONTROL_AF_TRIGGER
),
expectSupportedOperations = setOf(
- RestrictedCameraControl.AUTO_FOCUS
+ RestrictedCameraInfo.CAMERA_OPERATION_AUTO_FOCUS
)
)
}
@@ -192,7 +192,7 @@
CaptureRequest.CONTROL_AF_REGIONS,
),
expectSupportedOperations = setOf(
- RestrictedCameraControl.AF_REGION
+ RestrictedCameraInfo.CAMERA_OPERATION_AF_REGION
)
)
}
@@ -204,7 +204,7 @@
CaptureRequest.CONTROL_AE_REGIONS,
),
expectSupportedOperations = setOf(
- RestrictedCameraControl.AE_REGION
+ RestrictedCameraInfo.CAMERA_OPERATION_AE_REGION
)
)
}
@@ -216,7 +216,7 @@
CaptureRequest.CONTROL_AWB_REGIONS,
),
expectSupportedOperations = setOf(
- RestrictedCameraControl.AWB_REGION
+ RestrictedCameraInfo.CAMERA_OPERATION_AWB_REGION
)
)
}
@@ -229,7 +229,7 @@
CaptureRequest.FLASH_MODE
),
expectSupportedOperations = setOf(
- RestrictedCameraControl.TORCH
+ RestrictedCameraInfo.CAMERA_OPERATION_TORCH
)
)
}
@@ -242,7 +242,7 @@
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER
),
expectSupportedOperations = setOf(
- RestrictedCameraControl.FLASH
+ RestrictedCameraInfo.CAMERA_OPERATION_FLASH
)
)
}
@@ -254,7 +254,20 @@
CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION,
),
expectSupportedOperations = setOf(
- RestrictedCameraControl.EXPOSURE_COMPENSATION
+ RestrictedCameraInfo.CAMERA_OPERATION_EXPOSURE_COMPENSATION
+ )
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun supportedCameraOperations_extensionStrengthIsEnabled() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.EXTENSION_STRENGTH,
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraInfo.CAMERA_OPERATION_EXTENSION_STRENGTH
)
)
}
@@ -268,14 +281,14 @@
testSupportedCameraOperation(
supportedCaptureRequestKeys = emptyList(),
expectSupportedOperations = setOf(
- RestrictedCameraControl.ZOOM,
- RestrictedCameraControl.AUTO_FOCUS,
- RestrictedCameraControl.TORCH,
- RestrictedCameraControl.AF_REGION,
- RestrictedCameraControl.AE_REGION,
- RestrictedCameraControl.AWB_REGION,
- RestrictedCameraControl.EXPOSURE_COMPENSATION,
- RestrictedCameraControl.FLASH,
+ RestrictedCameraInfo.CAMERA_OPERATION_ZOOM,
+ RestrictedCameraInfo.CAMERA_OPERATION_AUTO_FOCUS,
+ RestrictedCameraInfo.CAMERA_OPERATION_TORCH,
+ RestrictedCameraInfo.CAMERA_OPERATION_AF_REGION,
+ RestrictedCameraInfo.CAMERA_OPERATION_AE_REGION,
+ RestrictedCameraInfo.CAMERA_OPERATION_AWB_REGION,
+ RestrictedCameraInfo.CAMERA_OPERATION_EXPOSURE_COMPENSATION,
+ RestrictedCameraInfo.CAMERA_OPERATION_FLASH
)
)
}
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/BasicExtenderSurfaceCombinationAvailabilityTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/BasicExtenderSurfaceCombinationAvailabilityTest.kt
deleted file mode 100644
index 40f43cb..0000000
--- a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/BasicExtenderSurfaceCombinationAvailabilityTest.kt
+++ /dev/null
@@ -1,92 +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.camera.extensions.internal.compat.workaround
-
-import android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3
-import android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
-import android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
-import android.os.Build
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.ParameterizedRobolectricTestRunner
-import org.robolectric.annotation.Config
-import org.robolectric.annotation.internal.DoNotInstrument
-import org.robolectric.util.ReflectionHelpers
-
-@RunWith(ParameterizedRobolectricTestRunner::class)
-@DoNotInstrument
-@Config(
- minSdk = Build.VERSION_CODES.Q
-)
-class BasicExtenderSurfaceCombinationAvailabilityTest(private val config: Config) {
-
- @Test
- fun checkImageAnalysisAvailability() {
- // Set up device properties
- ReflectionHelpers.setStaticField(Build::class.java, "BRAND", config.brand)
- ReflectionHelpers.setStaticField(Build::class.java, "DEVICE", config.device)
- ReflectionHelpers.setStaticField(Build::class.java, "MODEL", config.model)
- val basicExtenderImageAnalysisAvailability =
- BasicExtenderSurfaceCombinationAvailability()
- val isAvailable = basicExtenderImageAnalysisAvailability.isImageAnalysisAvailable(
- config.hardwareLevel,
- config.hasPreviewProcessor,
- config.hasImageCaptureProcessor
- )
- assertThat(isAvailable).isEqualTo(config.isAvailable)
- }
-
- class Config(
- val brand: String,
- val device: String,
- val model: String,
- val hardwareLevel: Int,
- val hasPreviewProcessor: Boolean,
- val hasImageCaptureProcessor: Boolean,
- val isAvailable: Boolean
- )
-
- companion object {
- @JvmStatic
- @ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
- fun createTestSet(): List<Config> {
- val levelLimited = INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
- val levelFull = INFO_SUPPORTED_HARDWARE_LEVEL_FULL
- val level3 = INFO_SUPPORTED_HARDWARE_LEVEL_3
- return listOf(
- // Samsung Galaxy A51 (support extra full level surface combinations)
- Config("Samsung", "", "SM-A515F", levelLimited, true, true, true),
- Config("Samsung", "", "SM-A515F", levelLimited, true, false, true),
- Config("Samsung", "", "SM-A515U", levelLimited, true, true, true),
- Config("Samsung", "", "SM-A515U", levelLimited, true, false, true),
- Config("Samsung", "", "SM-A515W", levelLimited, true, true, true),
- Config("Samsung", "", "SM-A516U1", levelLimited, true, true, true),
- Config("Samsung", "", "SM-A8050", levelLimited, true, true, true),
- Config("Samsung", "", "SM-F907B", levelLimited, true, true, true),
-
- // Other cases should be determined by hardware level and processors.
- Config("", "", "", level3, true, true, true),
- Config("", "", "", levelFull, true, true, true),
- Config("", "", "", levelFull, false, true, true),
- Config("", "", "", levelLimited, true, true, false),
- Config("", "", "", levelLimited, true, false, true),
- Config("", "", "", levelLimited, false, true, false),
- )
- }
- }
-}
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailabilityTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailabilityTest.kt
deleted file mode 100644
index 42c95b7..0000000
--- a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailabilityTest.kt
+++ /dev/null
@@ -1,101 +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.camera.extensions.internal.compat.workaround
-
-import android.os.Build
-import androidx.camera.extensions.ExtensionMode.BOKEH
-import androidx.camera.extensions.ExtensionMode.FACE_RETOUCH
-import androidx.camera.extensions.ExtensionMode.NIGHT
-import com.google.common.truth.Truth
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.ParameterizedRobolectricTestRunner
-import org.robolectric.annotation.Config
-import org.robolectric.annotation.internal.DoNotInstrument
-import org.robolectric.util.ReflectionHelpers
-
-@RunWith(ParameterizedRobolectricTestRunner::class)
-@DoNotInstrument
-@Config(
- minSdk = Build.VERSION_CODES.Q
-)
-class ImageAnalysisAvailabilityTest(private val config: Config) {
- @Test
- fun checkImageAnalysisAvailability() {
- // Set up device properties
- ReflectionHelpers.setStaticField(Build::class.java, "BRAND", config.brand)
- ReflectionHelpers.setStaticField(Build::class.java, "DEVICE", config.device)
- ReflectionHelpers.setStaticField(Build::class.java, "MODEL", config.model)
- val imageAnalysisAvailability = ImageAnalysisAvailability()
- var isAvailable = imageAnalysisAvailability.isAvailable(config.cameraId, config.mode)
- Truth.assertThat(isAvailable).isEqualTo(config.isAvailable)
- }
-
- class Config(
- val brand: String,
- val device: String,
- val model: String,
- val cameraId: String,
- val mode: Int,
- val isAvailable: Boolean
- )
-
- companion object {
- @JvmStatic
- @ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
- fun createTestSet(): List<Config> {
- return listOf(
- // Samsung Galaxy S23 Ultra 5G tests
- Config("Samsung", "dm3q", "", "0", BOKEH, false),
- Config("Samsung", "dm3q", "", "0", FACE_RETOUCH, false),
- Config("Samsung", "dm3q", "", "1", BOKEH, false),
- Config("Samsung", "dm3q", "", "1", FACE_RETOUCH, false),
- Config("Samsung", "dm3q", "", "3", BOKEH, false),
- Config("Samsung", "dm3q", "", "3", FACE_RETOUCH, false),
- Config("Samsung", "dm3q", "", "2", BOKEH, true),
- Config("Samsung", "dm3q", "", "0", NIGHT, true),
-
- // Samsung Galaxy Z Fold3 5G
- Config("Samsung", "q2q", "", "0", BOKEH, false),
- Config("Samsung", "q2q", "", "0", FACE_RETOUCH, false),
- Config("Samsung", "q2q", "", "1", BOKEH, true),
- Config("Samsung", "q2q", "", "0", NIGHT, true),
-
- // Samsung Galaxy A52s 5G
- Config("Samsung", "a52sxq", "", "0", BOKEH, false),
- Config("Samsung", "a52sxq", "", "0", FACE_RETOUCH, false),
- Config("Samsung", "a52sxq", "", "1", BOKEH, true),
- Config("Samsung", "a52sxq", "", "1", FACE_RETOUCH, true),
-
- // Samsung Galaxy S22 Ultra tests
- Config("Samsung", "b0q", "", "3", BOKEH, false),
- Config("Samsung", "b0q", "", "3", FACE_RETOUCH, false),
- Config("Samsung", "b0q", "", "0", BOKEH, true),
- Config("Samsung", "b0q", "", "0", FACE_RETOUCH, true),
-
- // Google Pixel doesn't support ImageAnalysis.
- Config("google", "", "redfin", "0", BOKEH, false),
- Config("google", "", "oriole", "1", NIGHT, false),
- Config("google", "", "pixel unknown", "0", BOKEH, false),
-
- // Xiaomi 13T Pro doesn't support ImageAnalysis.
- Config("xiaomi", "corot", "", "0", NIGHT, false),
- Config("xiaomi", "corot", "", "1", NIGHT, false),
- )
- }
- }
-}
diff --git a/camera/camera-lifecycle/api/1.4.0-beta01.txt b/camera/camera-lifecycle/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..5dd135e
--- /dev/null
+++ b/camera/camera-lifecycle/api/1.4.0-beta01.txt
@@ -0,0 +1,24 @@
+// Signature format: 4.0
+package androidx.camera.lifecycle {
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraProviderConfiguration {
+ }
+
+ @RequiresApi(21) public final class ProcessCameraProvider implements androidx.camera.core.CameraProvider {
+ method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+ method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup);
+ method @MainThread public androidx.camera.core.ConcurrentCamera bindToLifecycle(java.util.List<androidx.camera.core.ConcurrentCamera.SingleCameraConfig!>);
+ method @SuppressCompatibility @androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration public static void configureInstance(androidx.camera.core.CameraXConfig);
+ method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
+ method public java.util.List<java.util.List<androidx.camera.core.CameraInfo!>!> getAvailableConcurrentCameraInfos();
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
+ method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+ method public boolean isBound(androidx.camera.core.UseCase);
+ method @MainThread public boolean isConcurrentCameraModeOn();
+ method @VisibleForTesting public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> shutdownAsync();
+ method @MainThread public void unbind(androidx.camera.core.UseCase!...);
+ method @MainThread public void unbindAll();
+ }
+
+}
+
diff --git a/camera/camera-lifecycle/api/res-1.4.0-beta01.txt b/camera/camera-lifecycle/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-lifecycle/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-lifecycle/api/restricted_1.4.0-beta01.txt b/camera/camera-lifecycle/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..5dd135e
--- /dev/null
+++ b/camera/camera-lifecycle/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,24 @@
+// Signature format: 4.0
+package androidx.camera.lifecycle {
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraProviderConfiguration {
+ }
+
+ @RequiresApi(21) public final class ProcessCameraProvider implements androidx.camera.core.CameraProvider {
+ method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+ method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup);
+ method @MainThread public androidx.camera.core.ConcurrentCamera bindToLifecycle(java.util.List<androidx.camera.core.ConcurrentCamera.SingleCameraConfig!>);
+ method @SuppressCompatibility @androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration public static void configureInstance(androidx.camera.core.CameraXConfig);
+ method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
+ method public java.util.List<java.util.List<androidx.camera.core.CameraInfo!>!> getAvailableConcurrentCameraInfos();
+ method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
+ method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+ method public boolean isBound(androidx.camera.core.UseCase);
+ method @MainThread public boolean isConcurrentCameraModeOn();
+ method @VisibleForTesting public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> shutdownAsync();
+ method @MainThread public void unbind(androidx.camera.core.UseCase!...);
+ method @MainThread public void unbindAll();
+ }
+
+}
+
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
index 726b243..e1df029 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
@@ -25,7 +25,9 @@
import androidx.camera.core.concurrent.CameraCoordinator;
import androidx.camera.core.impl.CameraConfig;
import androidx.camera.core.impl.CameraConfigs;
+import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.CameraInternal;
+import androidx.camera.core.impl.RestrictedCameraInfo;
import androidx.camera.core.internal.CameraUseCaseAdapter;
import androidx.camera.testing.fakes.FakeCamera;
import androidx.camera.testing.impl.fakes.FakeCameraConfig;
@@ -444,8 +446,8 @@
LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(
mLifecycle, mCameraUseCaseAdapter);
LifecycleCamera retrieved = mRepository.getLifecycleCamera(mLifecycle,
- mCamera.getCameraInfoInternal().getCameraId(),
- mCameraUseCaseAdapter.getExtendedConfig());
+ CameraUseCaseAdapter.CameraId.create(mCamera.getCameraInfoInternal().getCameraId(),
+ CameraConfigs.defaultConfig().getCompatibilityId()));
assertThat(lifecycleCamera).isSameInstanceAs(retrieved);
}
@@ -461,12 +463,10 @@
newCameraUseCaseAdapter);
LifecycleCamera retrieved1 = mRepository.getLifecycleCamera(mLifecycle,
- mCameraUseCaseAdapter.getCameraId(),
- mCameraUseCaseAdapter.getExtendedConfig());
+ mCameraUseCaseAdapter.getCameraId());
LifecycleCamera retrieved2 = mRepository.getLifecycleCamera(mLifecycle,
- newCameraUseCaseAdapter.getCameraId(),
- newCameraUseCaseAdapter.getExtendedConfig());
+ newCameraUseCaseAdapter.getCameraId());
assertThat(lifecycleCamera1).isSameInstanceAs(retrieved1);
assertThat(lifecycleCamera2).isSameInstanceAs(retrieved2);
@@ -476,11 +476,10 @@
@Test
public void keys() {
LifecycleCameraRepository.Key key0 = LifecycleCameraRepository.Key.create(mLifecycle,
- mCameraUseCaseAdapter.getCameraId(),
- CameraConfigs.defaultConfig().getCompatibilityId());
+ mCameraUseCaseAdapter.getCameraId());
LifecycleCameraRepository.Key key1 = LifecycleCameraRepository.Key.create(mLifecycle,
- mCamera.getCameraInfoInternal().getCameraId(),
- CameraConfigs.defaultConfig().getCompatibilityId());
+ CameraUseCaseAdapter.CameraId.create(mCamera.getCameraInfoInternal().getCameraId(),
+ CameraConfigs.defaultConfig().getCompatibilityId()));
Map<LifecycleCameraRepository.Key, LifecycleOwner> map = new HashMap<>();
map.put(key0, mLifecycle);
@@ -629,9 +628,10 @@
private CameraUseCaseAdapter createCameraUseCaseAdapterWithNewCameraConfig() {
CameraConfig cameraConfig = new FakeCameraConfig();
return new CameraUseCaseAdapter(mCamera,
+ new RestrictedCameraInfo((CameraInfoInternal) mCamera.getCameraInfo(),
+ cameraConfig),
mCameraCoordinator,
new FakeCameraDeviceSurfaceManager(),
- new FakeUseCaseConfigFactory(),
- cameraConfig);
+ new FakeUseCaseConfigFactory());
}
}
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 ec280ea2..b39c18a 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
@@ -35,18 +35,22 @@
import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
import androidx.camera.core.impl.CameraConfig
import androidx.camera.core.impl.CameraFactory
+import androidx.camera.core.impl.CameraInfoInternal
import androidx.camera.core.impl.Config
import androidx.camera.core.impl.ExtendedCameraConfigProviderStore
import androidx.camera.core.impl.Identifier
import androidx.camera.core.impl.MutableOptionsBundle
+import androidx.camera.core.impl.RestrictedCameraInfo
import androidx.camera.core.impl.SessionProcessor
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
import androidx.camera.testing.fakes.FakeAppConfig
import androidx.camera.testing.fakes.FakeCamera
import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.camera.testing.impl.fakes.FakeCameraConfig
import androidx.camera.testing.impl.fakes.FakeCameraCoordinator
import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager
import androidx.camera.testing.impl.fakes.FakeCameraFactory
+import androidx.camera.testing.impl.fakes.FakeCameraFilter
import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
import androidx.camera.testing.impl.fakes.FakeSessionProcessor
import androidx.camera.testing.impl.fakes.FakeSurfaceEffect
@@ -641,7 +645,86 @@
assertThat(cameraInfos.size).isEqualTo(1)
val cameraInfo = cameraInfos.first() as FakeCameraInfoInternal
- assertThat(cameraInfo.lensFacing).isEqualTo(CameraSelector.LENS_FACING_BACK)
+ assertThat(cameraInfo.lensFacing).isEqualTo(LENS_FACING_BACK)
+ }
+ }
+
+ @Test
+ fun getCameraInfo_sameCameraInfoWithBindToLifecycle_afterBinding() {
+ // Arrange.
+ ProcessCameraProvider.configureInstance(FakeAppConfig.create())
+
+ runBlocking(MainScope().coroutineContext) {
+ provider = ProcessCameraProvider.getInstance(context).await()
+ val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
+
+ // Act: getting the camera info after bindToLifecycle.
+ val camera = provider.bindToLifecycle(lifecycleOwner0, cameraSelector)
+ val cameraInfoInternal1: CameraInfoInternal =
+ provider.getCameraInfo(cameraSelector) as CameraInfoInternal
+ val cameraInfoInternal2: CameraInfoInternal = camera.cameraInfo as CameraInfoInternal
+
+ // Assert.
+ assertThat(cameraInfoInternal1).isSameInstanceAs(cameraInfoInternal2)
+ }
+ }
+
+ @Test
+ fun getCameraInfo_sameCameraInfoWithBindToLifecycle_beforeBinding() {
+ // Arrange.
+ ProcessCameraProvider.configureInstance(FakeAppConfig.create())
+ runBlocking(MainScope().coroutineContext) {
+ provider = ProcessCameraProvider.getInstance(context).await()
+ val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
+
+ // Act: getting the camera info before bindToLifecycle.
+ val cameraInfoInternal1: CameraInfoInternal =
+ provider.getCameraInfo(cameraSelector) as CameraInfoInternal
+ val camera = provider.bindToLifecycle(lifecycleOwner0, cameraSelector)
+ val cameraInfoInternal2: CameraInfoInternal = camera.cameraInfo as CameraInfoInternal
+
+ // Assert.
+ assertThat(cameraInfoInternal1).isSameInstanceAs(cameraInfoInternal2)
+ }
+ }
+
+ @Test
+ fun getCameraInfo_containExtendedCameraConfig() {
+ // Arrange.
+ ProcessCameraProvider.configureInstance(FakeAppConfig.create())
+ runBlocking {
+ provider = ProcessCameraProvider.getInstance(context).await()
+ val id = Identifier.create("FakeId")
+ val cameraConfig = FakeCameraConfig(postviewSupported = true)
+ ExtendedCameraConfigProviderStore.addConfig(id) { _, _ ->
+ cameraConfig
+ }
+ val cameraSelector =
+ CameraSelector.Builder().addCameraFilter(FakeCameraFilter(id)).build()
+
+ // Act.
+ val restrictedCameraInfo =
+ provider.getCameraInfo(cameraSelector) as RestrictedCameraInfo
+
+ // Assert.
+ assertThat(restrictedCameraInfo.isPostviewSupported).isTrue()
+ }
+ }
+
+ @Test
+ fun getCameraInfo_exceptionWhenCameraSelectorInvalid() {
+ // Arrange.
+ ProcessCameraProvider.configureInstance(FakeAppConfig.create())
+ runBlocking(MainScope().coroutineContext) {
+ provider = ProcessCameraProvider.getInstance(context).await()
+ // Intentionally create a camera selector that doesn't result in a camera.
+ val cameraSelector =
+ CameraSelector.Builder().addCameraFilter { ArrayList<CameraInfo>() }.build()
+
+ // Act & Assert.
+ assertThrows(IllegalArgumentException::class.java) {
+ provider.getCameraInfo(cameraSelector)
+ }
}
}
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java
index b34bbe5..1723857 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java
@@ -24,9 +24,8 @@
import androidx.camera.core.UseCase;
import androidx.camera.core.ViewPort;
import androidx.camera.core.concurrent.CameraCoordinator;
-import androidx.camera.core.impl.CameraConfig;
import androidx.camera.core.impl.CameraInternal;
-import androidx.camera.core.impl.Identifier;
+import androidx.camera.core.impl.RestrictedCameraInfo;
import androidx.camera.core.internal.CameraUseCaseAdapter;
import androidx.core.util.Preconditions;
import androidx.lifecycle.Lifecycle;
@@ -105,10 +104,7 @@
@NonNull CameraUseCaseAdapter cameraUseCaseAdaptor) {
LifecycleCamera lifecycleCamera;
synchronized (mLock) {
- Key key = Key.create(lifecycleOwner,
- cameraUseCaseAdaptor.getCameraId(),
- cameraUseCaseAdaptor.getExtendedConfig().getCompatibilityId()
- );
+ Key key = Key.create(lifecycleOwner, cameraUseCaseAdaptor.getCameraId());
Preconditions.checkArgument(mCameraMap.get(key) == null, "LifecycleCamera already "
+ "exists for the given LifecycleOwner and set of cameras");
@@ -137,11 +133,10 @@
*/
@Nullable
LifecycleCamera getLifecycleCamera(LifecycleOwner lifecycleOwner,
- @NonNull String cameraId,
- @NonNull CameraConfig cameraConfig) {
+ @NonNull CameraUseCaseAdapter.CameraId cameraId
+ ) {
synchronized (mLock) {
- return mCameraMap.get(Key.create(lifecycleOwner, cameraId,
- cameraConfig.getCompatibilityId()));
+ return mCameraMap.get(Key.create(lifecycleOwner, cameraId));
}
}
@@ -181,9 +176,8 @@
synchronized (mLock) {
LifecycleOwner lifecycleOwner = lifecycleCamera.getLifecycleOwner();
Key key = Key.create(lifecycleOwner,
- lifecycleCamera.getCameraUseCaseAdapter().getCameraId(),
- lifecycleCamera.getCameraUseCaseAdapter()
- .getExtendedConfig().getCompatibilityId());
+ CameraUseCaseAdapter.generateCameraId(
+ (RestrictedCameraInfo) lifecycleCamera.getCameraInfo()));
LifecycleCameraRepositoryObserver observer =
getLifecycleCameraRepositoryObserver(lifecycleOwner);
@@ -505,26 +499,22 @@
}
/**
- * A key for mapping a {@link LifecycleOwner} and set of {@link CameraInternal} to a
+ * A key for mapping a {@link LifecycleOwner} and a {@link CameraUseCaseAdapter.CameraId} to a
* {@link LifecycleCamera}.
*/
@AutoValue
abstract static class Key {
static Key create(@NonNull LifecycleOwner lifecycleOwner,
- @NonNull String cameraId,
- @NonNull Identifier cameraConfigId) {
+ @NonNull CameraUseCaseAdapter.CameraId cameraId) {
return new AutoValue_LifecycleCameraRepository_Key(
- lifecycleOwner, cameraId, cameraConfigId);
+ lifecycleOwner, cameraId);
}
@NonNull
public abstract LifecycleOwner getLifecycleOwner();
@NonNull
- public abstract String getCameraId();
-
- @NonNull
- public abstract Identifier getCameraConfigId();
+ public abstract CameraUseCaseAdapter.CameraId getCameraId();
}
private static class LifecycleCameraRepositoryObserver implements LifecycleObserver {
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
index 5f5deeb..536684e 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
@@ -36,6 +36,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraEffect;
@@ -60,6 +61,7 @@
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
+import androidx.camera.core.impl.RestrictedCameraInfo;
import androidx.camera.core.impl.utils.ContextUtil;
import androidx.camera.core.impl.utils.Threads;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
@@ -77,7 +79,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -113,6 +117,9 @@
new LifecycleCameraRepository();
private CameraX mCameraX;
private Context mContext;
+ @GuardedBy("mLock")
+ private Map<CameraUseCaseAdapter.CameraId, RestrictedCameraInfo> mCameraInfoMap =
+ new HashMap<>();
/**
* Retrieves the {@link ProcessCameraProvider} associated with the current process.
@@ -301,6 +308,7 @@
mCameraXConfigProvider = null;
mCameraXInitializeFuture = null;
mCameraXShutdownFuture = shutdownFuture;
+ mCameraInfoMap.clear();
}
mCameraX = null;
mContext = null;
@@ -465,14 +473,14 @@
}
List<CameraInfo> cameraInfosToBind = new ArrayList<>();
- List<CameraInfo> availableCameraInfos = getAvailableCameraInfos();
- CameraInfo firstCameraInfo = getCameraInfoFromCameraSelector(
- singleCameraConfigs.get(0).getCameraSelector(),
- availableCameraInfos);
- CameraInfo secondCameraInfo = getCameraInfoFromCameraSelector(
- singleCameraConfigs.get(1).getCameraSelector(),
- availableCameraInfos);
- if (firstCameraInfo == null || secondCameraInfo == null) {
+ CameraInfo firstCameraInfo;
+ CameraInfo secondCameraInfo;
+ try {
+ firstCameraInfo = getCameraInfo(
+ singleCameraConfigs.get(0).getCameraSelector());
+ secondCameraInfo = getCameraInfo(
+ singleCameraConfigs.get(1).getCameraSelector());
+ } catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid camera selectors in camera configs");
}
cameraInfosToBind.add(firstCameraInfo);
@@ -570,11 +578,13 @@
// Get the LifecycleCamera if existed.
CameraInternal cameraInternal =
cameraSelector.select(mCameraX.getCameraRepository().getCameras());
- CameraInfoInternal cameraInfoInternal = cameraInternal.getCameraInfoInternal();
- CameraConfig cameraConfig = getCameraConfig(cameraSelector, cameraInfoInternal);
+ RestrictedCameraInfo restrictedCameraInfo =
+ (RestrictedCameraInfo) getCameraInfo(cameraSelector);
+
LifecycleCamera lifecycleCameraToBind =
mLifecycleCameraRepository.getLifecycleCamera(
- lifecycleOwner, cameraInfoInternal.getCameraId(), cameraConfig);
+ lifecycleOwner,
+ CameraUseCaseAdapter.generateCameraId(restrictedCameraInfo));
// Check if there's another camera that has already been bound.
Collection<LifecycleCamera> lifecycleCameras =
@@ -596,10 +606,10 @@
lifecycleCameraToBind =
mLifecycleCameraRepository.createLifecycleCamera(lifecycleOwner,
new CameraUseCaseAdapter(cameraInternal,
+ restrictedCameraInfo,
mCameraX.getCameraFactory().getCameraCoordinator(),
mCameraX.getCameraDeviceSurfaceManager(),
- mCameraX.getDefaultConfigFactory(),
- cameraConfig));
+ mCameraX.getDefaultConfigFactory()));
}
if (useCases.length == 0) {
@@ -752,7 +762,7 @@
* operate concurrently on the device. Each list maps to one combination of these camera's
* {@link CameraInfo}.
*
- * For example, to select a front camera and a back camera and bind to {@link LifecycleOwner}
+ * <p>For example, to select a front camera and a back camera and bind to {@link LifecycleOwner}
* with preview {@link UseCase}, this function could be used with
* {@link #bindToLifecycle(List)}.
* <pre><code>
@@ -792,7 +802,6 @@
* </code></pre>
*
* @return list of combinations of {@link CameraInfo}.
- *
*/
@NonNull
public List<List<CameraInfo>> getAvailableConcurrentCameraInfos() {
@@ -800,17 +809,18 @@
requireNonNull(mCameraX.getCameraFactory().getCameraCoordinator());
List<List<CameraSelector>> concurrentCameraSelectorLists =
mCameraX.getCameraFactory().getCameraCoordinator().getConcurrentCameraSelectors();
- List<CameraInfo> availableCameraInfos = getAvailableCameraInfos();
List<List<CameraInfo>> availableConcurrentCameraInfos = new ArrayList<>();
for (final List<CameraSelector> cameraSelectors : concurrentCameraSelectorLists) {
List<CameraInfo> cameraInfos = new ArrayList<>();
for (CameraSelector cameraSelector : cameraSelectors) {
- CameraInfo cameraInfo = getCameraInfoFromCameraSelector(cameraSelector,
- availableCameraInfos);
- if (cameraInfo != null) {
- cameraInfos.add(cameraInfo);
+ CameraInfo cameraInfo;
+ try {
+ cameraInfo = getCameraInfo(cameraSelector);
+ } catch (IllegalArgumentException e) {
+ continue;
}
+ cameraInfos.add(cameraInfo);
}
availableConcurrentCameraInfos.add(cameraInfos);
}
@@ -818,6 +828,42 @@
}
/**
+ * Returns the {@link CameraInfo} instance of the camera resulted from the
+ * specified {@link CameraSelector}.
+ *
+ * <p>The returned {@link CameraInfo} is corresponded to the camera that will be bound
+ * when calling {@link #bindToLifecycle} with the specified {@link CameraSelector}.
+ *
+ * @param cameraSelector the {@link CameraSelector} to get the {@link CameraInfo} that is
+ * corresponded to.
+ * @return the corresponding {@link CameraInfo}.
+ * @throws UnsupportedOperationException if the camera provider is not implemented properly.
+ * @throws IllegalArgumentException if the given {@link CameraSelector} can't result in a
+ * valid camera to provide the {@link CameraInfo}.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @Override
+ @NonNull
+ public CameraInfo getCameraInfo(@NonNull CameraSelector cameraSelector) {
+ CameraInfoInternal cameraInfoInternal = cameraSelector.select(
+ mCameraX.getCameraRepository().getCameras()).getCameraInfoInternal();
+ CameraConfig cameraConfig = getCameraConfig(cameraSelector, cameraInfoInternal);
+
+ CameraUseCaseAdapter.CameraId key = CameraUseCaseAdapter.CameraId.create(
+ cameraInfoInternal.getCameraId(), cameraConfig.getCompatibilityId());
+ RestrictedCameraInfo restrictedCameraInfo;
+ synchronized (mLock) {
+ restrictedCameraInfo = mCameraInfoMap.get(key);
+ if (restrictedCameraInfo == null) {
+ restrictedCameraInfo = new RestrictedCameraInfo(cameraInfoInternal, cameraConfig);
+ mCameraInfoMap.put(key, restrictedCameraInfo);
+ }
+ }
+
+ return restrictedCameraInfo;
+ }
+
+ /**
* Returns whether there is a {@link ConcurrentCamera} bound.
*
* @return true if there is a {@link ConcurrentCamera} bound, otherwise false.
@@ -861,14 +907,6 @@
.setActiveConcurrentCameraInfos(cameraInfos);
}
- @Nullable
- private CameraInfo getCameraInfoFromCameraSelector(
- @NonNull CameraSelector cameraSelector,
- @NonNull List<CameraInfo> availableCameraInfos) {
- List<CameraInfo> cameraInfos = cameraSelector.filter(availableCameraInfos);
- return cameraInfos.isEmpty() ? null : cameraInfos.get(0);
- }
-
private ProcessCameraProvider() {
}
}
diff --git a/camera/camera-mlkit-vision/api/1.4.0-beta01.txt b/camera/camera-mlkit-vision/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..0599c25
--- /dev/null
+++ b/camera/camera-mlkit-vision/api/1.4.0-beta01.txt
@@ -0,0 +1,20 @@
+// Signature format: 4.0
+package androidx.camera.mlkit.vision {
+
+ @RequiresApi(21) public class MlKitAnalyzer implements androidx.camera.core.ImageAnalysis.Analyzer {
+ ctor public MlKitAnalyzer(java.util.List<com.google.mlkit.vision.interfaces.Detector<?>!>, int, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.mlkit.vision.MlKitAnalyzer.Result!>);
+ method public final void analyze(androidx.camera.core.ImageProxy);
+ method public final android.util.Size getDefaultTargetResolution();
+ method public final int getTargetCoordinateSystem();
+ method public final void updateTransform(android.graphics.Matrix?);
+ }
+
+ public static final class MlKitAnalyzer.Result {
+ ctor public MlKitAnalyzer.Result(java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Object!>, long, java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Throwable!>);
+ method public Throwable? getThrowable(com.google.mlkit.vision.interfaces.Detector<?>);
+ method public long getTimestamp();
+ method public <T> T? getValue(com.google.mlkit.vision.interfaces.Detector<T!>);
+ }
+
+}
+
diff --git a/camera/camera-mlkit-vision/api/res-1.4.0-beta01.txt b/camera/camera-mlkit-vision/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-mlkit-vision/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-mlkit-vision/api/restricted_1.4.0-beta01.txt b/camera/camera-mlkit-vision/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..0599c25
--- /dev/null
+++ b/camera/camera-mlkit-vision/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,20 @@
+// Signature format: 4.0
+package androidx.camera.mlkit.vision {
+
+ @RequiresApi(21) public class MlKitAnalyzer implements androidx.camera.core.ImageAnalysis.Analyzer {
+ ctor public MlKitAnalyzer(java.util.List<com.google.mlkit.vision.interfaces.Detector<?>!>, int, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.mlkit.vision.MlKitAnalyzer.Result!>);
+ method public final void analyze(androidx.camera.core.ImageProxy);
+ method public final android.util.Size getDefaultTargetResolution();
+ method public final int getTargetCoordinateSystem();
+ method public final void updateTransform(android.graphics.Matrix?);
+ }
+
+ public static final class MlKitAnalyzer.Result {
+ ctor public MlKitAnalyzer.Result(java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Object!>, long, java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Throwable!>);
+ method public Throwable? getThrowable(com.google.mlkit.vision.interfaces.Detector<?>);
+ method public long getTimestamp();
+ method public <T> T? getValue(com.google.mlkit.vision.interfaces.Detector<T!>);
+ }
+
+}
+
diff --git a/camera/camera-mlkit-vision/src/main/java/androidx/camera/mlkit/vision/MlKitAnalyzer.java b/camera/camera-mlkit-vision/src/main/java/androidx/camera/mlkit/vision/MlKitAnalyzer.java
index 45385fb..c858910 100644
--- a/camera/camera-mlkit-vision/src/main/java/androidx/camera/mlkit/vision/MlKitAnalyzer.java
+++ b/camera/camera-mlkit-vision/src/main/java/androidx/camera/mlkit/vision/MlKitAnalyzer.java
@@ -151,7 +151,15 @@
}
/**
- * {@inheritDoc}
+ * Analyzes the image with the ML Kit {@code Detector}s.
+ *
+ * <p>This method forwards the image and the transformation {@link Matrix} to the {@code
+ * Detector}s. The {@code Matrix} is calculated based on the target coordinate system set in
+ * the constructor.
+ *
+ * <p>Usually this method is invoked by {@link ImageAnalysis} when a new frame is available.
+ *
+ * @see ImageAnalysis.Analyzer#analyze
*/
@Override
@OptIn(markerClass = TransformExperimental.class)
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
index 1c13602..d7a50ec 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
@@ -16,10 +16,7 @@
package androidx.camera.testing.fakes;
-import android.graphics.Canvas;
-import android.graphics.Rect;
import android.text.TextUtils;
-import android.util.Size;
import android.view.Surface;
import androidx.annotation.IntRange;
@@ -45,6 +42,7 @@
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
+import androidx.camera.testing.impl.CaptureSimulationKt;
import androidx.core.util.Preconditions;
import com.google.common.util.concurrent.ListenableFuture;
@@ -56,6 +54,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -178,6 +177,8 @@
checkNotReleased();
switch (mState) {
case OPEN:
+ // fall through
+ case CONFIGURED:
mSessionConfig = null;
reconfigure();
// fall through
@@ -559,39 +560,36 @@
* Simulates a capture frame being drawn on the session config surfaces to imitate a real
* camera.
*/
+ @NonNull
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public void simulateCaptureFrame() {
+ public ListenableFuture<Void> simulateCaptureFrameAsync() {
+ return simulateCaptureFrameAsync(null);
+ }
+
+ /**
+ * Simulates a capture frame being drawn on the session config surfaces to imitate a real
+ * camera.
+ *
+ * <p> This method uses the provided {@link Executor} for the asynchronous operations in case
+ * of specific thread requirements.
+ */
+ @NonNull
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public ListenableFuture<Void> simulateCaptureFrameAsync(@Nullable Executor executor) {
// Since capture session is not configured synchronously and may be dependent on when a
// surface can be obtained from DeferrableSurface, we should wait for the session
// configuration here just-in-case.
awaitSessionConfiguration(1000);
if (mSessionConfig == null || mState != State.CONFIGURED) {
- Logger.e(TAG, "Session config not successfully configured yet.");
- return;
+ return Futures.immediateFailedFuture(
+ new IllegalStateException("Session config not successfully configured yet."));
}
- for (DeferrableSurface deferrableSurface : mSessionConfig.getSurfaces()) {
- Size surfaceSize = deferrableSurface.getPrescribedSize();
- Futures.addCallback(deferrableSurface.getSurface(), new FutureCallback<Surface>() {
- @Override
- public void onSuccess(@Nullable Surface surface) {
- if (surface == null) {
- Logger.e(TAG, "Null surface obtained from " + deferrableSurface);
- return;
- }
- Canvas canvas = surface.lockCanvas(
- new Rect(0, 0, surfaceSize.getWidth(), surfaceSize.getHeight()));
- // TODO: Draw something on the canvas (e.g. fake image bitmap or
- // alternating color).
- surface.unlockCanvasAndPost(canvas);
- }
-
- @Override
- public void onFailure(@NonNull Throwable t) {
- Logger.e(TAG, "Could not obtain surface from " + deferrableSurface, t);
- }
- }, CameraXExecutors.directExecutor());
+ if (executor == null) {
+ return CaptureSimulationKt.simulateCaptureFrameAsync(mSessionConfig.getSurfaces());
}
+ return CaptureSimulationKt.simulateCaptureFrameAsync(mSessionConfig.getSurfaces(),
+ executor);
}
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
index e546484..9d9a543 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
@@ -59,6 +59,7 @@
import androidx.camera.core.impl.CameraConfig;
import androidx.camera.core.impl.CameraConfigs;
import androidx.camera.core.impl.CameraInternal;
+import androidx.camera.core.impl.RestrictedCameraInfo;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.camera.core.internal.CameraUseCaseAdapter;
import androidx.camera.testing.impl.fakes.FakeCameraCoordinator;
@@ -630,10 +631,10 @@
CameraInternal camera =
cameraSelector.select(cameraX.getCameraRepository().getCameras());
return new CameraUseCaseAdapter(camera,
+ new RestrictedCameraInfo(camera.getCameraInfoInternal(), cameraConfig),
cameraCoordinator,
cameraX.getCameraDeviceSurfaceManager(),
- cameraX.getDefaultConfigFactory(),
- cameraConfig);
+ cameraX.getDefaultConfigFactory());
} catch (ExecutionException | InterruptedException | TimeoutException e) {
throw new RuntimeException("Unable to retrieve CameraX instance");
}
@@ -1182,7 +1183,8 @@
if (deviceHolder.get() == null) {
ret = false;
}
- if (Build.MODEL.equalsIgnoreCase("sm-g920v")) {
+ if (Build.HARDWARE.equalsIgnoreCase("universal7420")
+ || Build.HARDWARE.equalsIgnoreCase("samsungexynos7420")) {
// Please see b/305835396
TimeUnit.SECONDS.sleep(1);
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CaptureSimulation.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CaptureSimulation.kt
new file mode 100644
index 0000000..e44dbe8
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CaptureSimulation.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+
+package androidx.camera.testing.impl
+
+import android.graphics.Rect
+import android.view.Surface
+import androidx.annotation.RequiresApi
+import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.core.impl.utils.futures.FutureCallback
+import androidx.camera.core.impl.utils.futures.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.async
+
+private const val TAG = "CaptureSimulation"
+
+/** Simulates a capture frame being drawn on all of the provided surfaces. */
+suspend fun List<DeferrableSurface>.simulateCaptureFrame() =
+ forEach { it.simulateCaptureFrame() }
+
+/**
+ * Simulates a capture frame being drawn on the provided surface.
+ *
+ * @throws IllegalStateException If [DeferrableSurface.getSurface] provides a null surface.
+ */
+suspend fun DeferrableSurface.simulateCaptureFrame() {
+ val deferred = CompletableDeferred<Unit>()
+
+ Futures.addCallback(surface, object : FutureCallback<Surface?> {
+ override fun onSuccess(surface: Surface?) {
+ if (surface == null) {
+ deferred.completeExceptionally(
+ IllegalStateException("Null surface obtained from ${this@simulateCaptureFrame}")
+ )
+ return
+ }
+ val canvas = surface.lockCanvas(
+ Rect(0, 0, prescribedSize.width, prescribedSize.height)
+ )
+ // TODO: Draw something on the canvas (e.g. fake image bitmap or alternating color).
+ surface.unlockCanvasAndPost(canvas)
+ deferred.complete(Unit)
+ }
+
+ override fun onFailure(t: Throwable) {
+ deferred.completeExceptionally(t)
+ }
+ }, CameraXExecutors.directExecutor())
+
+ deferred.await()
+}
+
+// The following methods are adapters for Java invocations.
+
+/**
+ * Simulates a capture frame being drawn on all of the provided surfaces.
+ *
+ * This method uses the provided [Executor] for the asynchronous operations.
+ *
+ * @param executor The [Executor] to use for the asynchronous operations.
+ * @return A [ListenableFuture] representing when the operation has been completed.
+ */
+@JvmOverloads
+fun List<DeferrableSurface>.simulateCaptureFrameAsync(
+ executor: Executor = Dispatchers.Default.asExecutor()
+): ListenableFuture<Void> {
+ val scope = CoroutineScope(SupervisorJob() + executor.asCoroutineDispatcher())
+ return (scope.async { simulateCaptureFrame() } as Job).asListenableFuture()
+}
+
+/**
+ * Simulates a capture frame being drawn on the provided surfaces.
+ *
+ * This method uses the provided [Executor] for the asynchronous operations.
+ *
+ * @param executor The [Executor] to use for the asynchronous operations.
+ * @return A [ListenableFuture] representing when the operation has been completed.
+ */
+@JvmOverloads
+fun DeferrableSurface.simulateCaptureFrameAsync(
+ executor: Executor = Dispatchers.Default.asExecutor()
+): ListenableFuture<Void> {
+ val scope = CoroutineScope(SupervisorJob() + executor.asCoroutineDispatcher())
+ return (scope.async { simulateCaptureFrame() } as Job).asListenableFuture()
+}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CoroutineAdapters.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CoroutineAdapters.kt
new file mode 100644
index 0000000..a011ce8
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CoroutineAdapters.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+
+package androidx.camera.testing.impl
+
+import androidx.annotation.RequiresApi
+import androidx.concurrent.futures.CallbackToFutureAdapter
+import com.google.common.util.concurrent.ListenableFuture
+import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+
+// TODO: This file is copied from camera-pipe-integration, probably best to move somewhere common
+// if more modules require these in future
+
+/**
+ * Convert a job into a ListenableFuture<Void>.
+ *
+ * The return value of the Future is null, and canceling the future will not cancel the Job.
+ * The tag field may be used to help debug futures.
+ */
+fun Job.asListenableFuture(tag: Any? = "Job.asListenableFuture"): ListenableFuture<Void> {
+ val resolver: CallbackToFutureAdapter.Resolver<Void> =
+ CallbackToFutureAdapter.Resolver<Void> { completer ->
+ this.invokeOnCompletion {
+ if (it != null) {
+ if (it is CancellationException) {
+ completer.setCancelled()
+ } else {
+ completer.setException(it)
+ }
+ } else {
+ completer.set(null)
+ }
+ }
+ tag
+ }
+ return CallbackToFutureAdapter.getFuture(resolver)
+}
+
+/**
+ * Convert a job into a ListenableFuture<T>.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T> Deferred<T>.asListenableFuture(
+ tag: Any? = "Deferred.asListenableFuture"
+): ListenableFuture<T> {
+ val resolver: CallbackToFutureAdapter.Resolver<T> =
+ CallbackToFutureAdapter.Resolver<T> { completer ->
+ this.invokeOnCompletion {
+ if (it != null) {
+ if (it is CancellationException) {
+ completer.setCancelled()
+ } else {
+ completer.setException(it)
+ }
+ } else {
+ // Ignore exceptions - This should never throw in this situation.
+ completer.set(this.getCompleted())
+ }
+ }
+ tag
+ }
+ return CallbackToFutureAdapter.getFuture(resolver)
+}
+
+fun <T> Deferred<T>.propagateTo(destination: CompletableDeferred<T>) {
+ invokeOnCompletion {
+ propagateOnceTo(destination, it)
+ }
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T> Deferred<T>.propagateOnceTo(
+ destination: CompletableDeferred<T>,
+ throwable: Throwable?,
+) {
+ if (throwable != null) {
+ if (throwable is CancellationException) {
+ destination.cancel(throwable)
+ } else {
+ destination.completeExceptionally(throwable)
+ }
+ } else {
+ // Ignore exceptions - This should never throw in this situation.
+ destination.complete(getCompleted())
+ }
+}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraConfig.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraConfig.kt
index d63a1f8..ff626ab 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraConfig.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraConfig.kt
@@ -32,12 +32,12 @@
private val postviewSupported: Boolean = false,
private val captureProcessProgressSupported: Boolean = false
) : CameraConfig {
- private val mUseCaseConfigFactory =
+ private val useCaseConfigFactory =
UseCaseConfigFactory { _, _ -> null }
- private val mIdentifier = Identifier.create(Any())
+ private val identifier = Identifier.create(Any())
override fun getUseCaseConfigFactory(): UseCaseConfigFactory {
- return mUseCaseConfigFactory
+ return useCaseConfigFactory
}
override fun isPostviewSupported(): Boolean {
@@ -49,7 +49,7 @@
}
override fun getCompatibilityId(): Identifier {
- return mIdentifier
+ return identifier
}
override fun getConfig(): Config {
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraFilter.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraFilter.kt
new file mode 100644
index 0000000..dc38dac
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraFilter.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.camera.testing.impl.fakes
+
+import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+import androidx.camera.core.CameraFilter
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.impl.Identifier
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class FakeCameraFilter(val id: Identifier = CameraFilter.DEFAULT_ID) : CameraFilter {
+
+ override fun filter(cameraInfos: List<CameraInfo>): List<CameraInfo> {
+ return ArrayList(cameraInfos)
+ }
+
+ override fun getIdentifier(): Identifier {
+ return id
+ }
+}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeSessionProcessor.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeSessionProcessor.kt
index 85a2b0f..82bc4bc 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeSessionProcessor.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeSessionProcessor.kt
@@ -34,7 +34,7 @@
import androidx.camera.core.impl.OptionsBundle
import androidx.camera.core.impl.OutputSurfaceConfiguration
import androidx.camera.core.impl.RequestProcessor
-import androidx.camera.core.impl.RestrictedCameraControl
+import androidx.camera.core.impl.RestrictedCameraInfo
import androidx.camera.core.impl.SessionConfig
import androidx.camera.core.impl.SessionProcessor
import androidx.camera.core.impl.SessionProcessorSurface
@@ -51,7 +51,7 @@
class FakeSessionProcessor(
val inputFormatPreview: Int? = null,
val inputFormatCapture: Int? = null,
- val postviewSupportedSizes: Map<Int, List<Size>>? = null
+ val postviewSupportedSizes: Map<Int, List<Size>>? = null,
) : SessionProcessor {
private lateinit var previewProcessorSurface: DeferrableSurface
private lateinit var captureProcessorSurface: DeferrableSurface
@@ -85,7 +85,7 @@
private var rotationDegrees = 0
private var jpegQuality = 100
- @RestrictedCameraControl.CameraOperation
+ @RestrictedCameraInfo.CameraOperation
var restrictedCameraOperations: Set<Int> = emptySet()
fun releaseSurfaces() {
@@ -233,7 +233,7 @@
return latestParameters
}
- @RestrictedCameraControl.CameraOperation
+ @RestrictedCameraInfo.CameraOperation
override fun getSupportedCameraOperations(): Set<Int> {
return restrictedCameraOperations
}
diff --git a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraTest.java b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraTest.java
index f187590..66afbce 100644
--- a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraTest.java
+++ b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraTest.java
@@ -21,16 +21,22 @@
import static java.util.Collections.singletonList;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraDevice;
import android.os.Build;
+import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.UseCase;
import androidx.camera.core.impl.CameraConfig;
import androidx.camera.core.impl.CameraInternal;
+import androidx.camera.core.impl.DeferrableSurface;
import androidx.camera.core.impl.Identifier;
+import androidx.camera.core.impl.ImmediateSurface;
import androidx.camera.core.impl.MutableOptionsBundle;
import androidx.camera.core.impl.Observable;
+import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.testing.impl.fakes.FakeUseCase;
@@ -83,6 +89,33 @@
}
@Test
+ public void closeCameraInReconfiguredState_deferrableSurfaceTerminated() {
+ // Arrange: create UseCase with ImmediateSurface
+ SurfaceTexture surfaceTexture = new SurfaceTexture(1);
+ Surface surface = new Surface(surfaceTexture);
+ DeferrableSurface immediateSurface = new ImmediateSurface(surface);
+ FakeUseCase fakeUseCase = new FakeUseCase();
+ SessionConfig sessionConfig = new SessionConfig.Builder()
+ .setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
+ .addSurface(immediateSurface)
+ .build();
+ fakeUseCase.updateSessionConfigForTesting(sessionConfig);
+
+ // Act: attach/detach UseCase
+ mCamera.attachUseCases(singletonList(fakeUseCase));
+ mCamera.onUseCaseActive(fakeUseCase);
+ mCamera.detachUseCases(singletonList(fakeUseCase));
+
+ // Assert: immediateSurface is terminated
+ immediateSurface.close();
+ assertThat(immediateSurface.getTerminationFuture().isDone()).isTrue();
+
+ // Cleanup surface
+ surface.release();
+ surfaceTexture.release();
+ }
+
+ @Test
public void cameraIsInClosedState_whenInitialized() {
assertThat(mLatestState).isEqualTo(CameraInternal.State.CLOSED);
}
diff --git a/camera/camera-video/api/1.4.0-beta01.txt b/camera/camera-video/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..df297e5
--- /dev/null
+++ b/camera/camera-video/api/1.4.0-beta01.txt
@@ -0,0 +1,220 @@
+// Signature format: 4.0
+package androidx.camera.video {
+
+ @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class AudioStats {
+ method public double getAudioAmplitude();
+ method public abstract int getAudioState();
+ method public abstract Throwable? getErrorCause();
+ method public boolean hasAudio();
+ method public boolean hasError();
+ field public static final double AUDIO_AMPLITUDE_NONE = 0.0;
+ field public static final int AUDIO_STATE_ACTIVE = 0; // 0x0
+ field public static final int AUDIO_STATE_DISABLED = 1; // 0x1
+ field public static final int AUDIO_STATE_ENCODER_ERROR = 3; // 0x3
+ field public static final int AUDIO_STATE_MUTED = 5; // 0x5
+ field public static final int AUDIO_STATE_SOURCE_ERROR = 4; // 0x4
+ field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAudioApi {
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
+ }
+
+ @RequiresApi(21) public class FallbackStrategy {
+ method public static androidx.camera.video.FallbackStrategy higherQualityOrLowerThan(androidx.camera.video.Quality);
+ method public static androidx.camera.video.FallbackStrategy higherQualityThan(androidx.camera.video.Quality);
+ method public static androidx.camera.video.FallbackStrategy lowerQualityOrHigherThan(androidx.camera.video.Quality);
+ method public static androidx.camera.video.FallbackStrategy lowerQualityThan(androidx.camera.video.Quality);
+ }
+
+ @RequiresApi(21) public final class FileDescriptorOutputOptions extends androidx.camera.video.OutputOptions {
+ method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+ }
+
+ @RequiresApi(21) public static final class FileDescriptorOutputOptions.Builder {
+ ctor public FileDescriptorOutputOptions.Builder(android.os.ParcelFileDescriptor);
+ method public androidx.camera.video.FileDescriptorOutputOptions build();
+ method public androidx.camera.video.FileDescriptorOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+ method public androidx.camera.video.FileDescriptorOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+ method public androidx.camera.video.FileDescriptorOutputOptions.Builder setLocation(android.location.Location?);
+ }
+
+ @RequiresApi(21) public final class FileOutputOptions extends androidx.camera.video.OutputOptions {
+ method public java.io.File getFile();
+ }
+
+ @RequiresApi(21) public static final class FileOutputOptions.Builder {
+ ctor public FileOutputOptions.Builder(java.io.File);
+ method public androidx.camera.video.FileOutputOptions build();
+ method public androidx.camera.video.FileOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+ method public androidx.camera.video.FileOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+ method public androidx.camera.video.FileOutputOptions.Builder setLocation(android.location.Location?);
+ }
+
+ @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
+ method public android.net.Uri getCollectionUri();
+ method public android.content.ContentResolver getContentResolver();
+ method public android.content.ContentValues getContentValues();
+ field public static final android.content.ContentValues EMPTY_CONTENT_VALUES;
+ }
+
+ public static final class MediaStoreOutputOptions.Builder {
+ ctor public MediaStoreOutputOptions.Builder(android.content.ContentResolver, android.net.Uri);
+ method public androidx.camera.video.MediaStoreOutputOptions build();
+ method public androidx.camera.video.MediaStoreOutputOptions.Builder setContentValues(android.content.ContentValues);
+ method public androidx.camera.video.MediaStoreOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+ method public androidx.camera.video.MediaStoreOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+ method public androidx.camera.video.MediaStoreOutputOptions.Builder setLocation(android.location.Location?);
+ }
+
+ @RequiresApi(21) public abstract class OutputOptions {
+ method @IntRange(from=0) public long getDurationLimitMillis();
+ method @IntRange(from=0) public long getFileSizeLimit();
+ method public android.location.Location? getLocation();
+ field public static final int DURATION_UNLIMITED = 0; // 0x0
+ field public static final int FILE_SIZE_UNLIMITED = 0; // 0x0
+ }
+
+ @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class OutputResults {
+ ctor public OutputResults();
+ method public abstract android.net.Uri getOutputUri();
+ }
+
+ @RequiresApi(21) public final class PendingRecording {
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public androidx.camera.video.PendingRecording asPersistentRecording();
+ method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
+ }
+
+ @RequiresApi(21) public class Quality {
+ field public static final androidx.camera.video.Quality FHD;
+ field public static final androidx.camera.video.Quality HD;
+ field public static final androidx.camera.video.Quality HIGHEST;
+ field public static final androidx.camera.video.Quality LOWEST;
+ field public static final androidx.camera.video.Quality SD;
+ field public static final androidx.camera.video.Quality UHD;
+ }
+
+ @RequiresApi(21) public final class QualitySelector {
+ method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality);
+ method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality, androidx.camera.video.FallbackStrategy);
+ method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>);
+ method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>, androidx.camera.video.FallbackStrategy);
+ method public static android.util.Size? getResolution(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
+ method @Deprecated public static java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.CameraInfo);
+ method @Deprecated public static boolean isQualitySupported(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
+ }
+
+ @RequiresApi(21) public final class Recorder implements androidx.camera.video.VideoOutput {
+ method public int getAspectRatio();
+ method public java.util.concurrent.Executor? getExecutor();
+ method public androidx.camera.video.QualitySelector getQualitySelector();
+ method public int getTargetVideoEncodingBitRate();
+ method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo);
+ method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo, int);
+ method public int getVideoCapabilitiesSource();
+ method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+ method @RequiresApi(26) public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileDescriptorOutputOptions);
+ method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileOutputOptions);
+ method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.MediaStoreOutputOptions);
+ field public static final androidx.camera.video.QualitySelector DEFAULT_QUALITY_SELECTOR;
+ field public static final int VIDEO_CAPABILITIES_SOURCE_CAMCORDER_PROFILE = 0; // 0x0
+ field public static final int VIDEO_CAPABILITIES_SOURCE_CODEC_CAPABILITIES = 1; // 0x1
+ }
+
+ @RequiresApi(21) public static final class Recorder.Builder {
+ ctor public Recorder.Builder();
+ method public androidx.camera.video.Recorder build();
+ method public androidx.camera.video.Recorder.Builder setAspectRatio(int);
+ method public androidx.camera.video.Recorder.Builder setExecutor(java.util.concurrent.Executor);
+ method public androidx.camera.video.Recorder.Builder setQualitySelector(androidx.camera.video.QualitySelector);
+ method public androidx.camera.video.Recorder.Builder setTargetVideoEncodingBitRate(@IntRange(from=1) int);
+ method public androidx.camera.video.Recorder.Builder setVideoCapabilitiesSource(int);
+ }
+
+ @RequiresApi(21) public final class Recording implements java.lang.AutoCloseable {
+ method public void close();
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public boolean isPersistent();
+ method public void mute(boolean);
+ method public void pause();
+ method public void resume();
+ method public void stop();
+ }
+
+ @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class RecordingStats {
+ method public abstract androidx.camera.video.AudioStats getAudioStats();
+ method public abstract long getNumBytesRecorded();
+ method public abstract long getRecordedDurationNanos();
+ }
+
+ @RequiresApi(21) public interface VideoCapabilities {
+ method public java.util.Set<androidx.camera.core.DynamicRange!> getSupportedDynamicRanges();
+ method public java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.DynamicRange);
+ method public boolean isQualitySupported(androidx.camera.video.Quality, androidx.camera.core.DynamicRange);
+ method public default boolean isStabilizationSupported();
+ }
+
+ @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
+ method public androidx.camera.core.DynamicRange getDynamicRange();
+ method public int getMirrorMode();
+ method public T getOutput();
+ method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
+ method public int getTargetRotation();
+ method public boolean isVideoStabilizationEnabled();
+ method public void setTargetRotation(int);
+ method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
+ }
+
+ @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
+ ctor public VideoCapture.Builder(T);
+ method public androidx.camera.video.VideoCapture<T!> build();
+ method public androidx.camera.video.VideoCapture.Builder<T!> setDynamicRange(androidx.camera.core.DynamicRange);
+ method public androidx.camera.video.VideoCapture.Builder<T!> setMirrorMode(int);
+ method public androidx.camera.video.VideoCapture.Builder<T!> setTargetFrameRate(android.util.Range<java.lang.Integer!>);
+ method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
+ method public androidx.camera.video.VideoCapture.Builder<T!> setVideoStabilizationEnabled(boolean);
+ }
+
+ @RequiresApi(21) public interface VideoOutput {
+ method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+ }
+
+ @RequiresApi(21) public abstract class VideoRecordEvent {
+ method public androidx.camera.video.OutputOptions getOutputOptions();
+ method public androidx.camera.video.RecordingStats getRecordingStats();
+ }
+
+ @RequiresApi(21) public static final class VideoRecordEvent.Finalize extends androidx.camera.video.VideoRecordEvent {
+ method public Throwable? getCause();
+ method public int getError();
+ method public androidx.camera.video.OutputResults getOutputResults();
+ method public boolean hasError();
+ field public static final int ERROR_DURATION_LIMIT_REACHED = 9; // 0x9
+ field public static final int ERROR_ENCODING_FAILED = 6; // 0x6
+ field public static final int ERROR_FILE_SIZE_LIMIT_REACHED = 2; // 0x2
+ field public static final int ERROR_INSUFFICIENT_STORAGE = 3; // 0x3
+ field public static final int ERROR_INVALID_OUTPUT_OPTIONS = 5; // 0x5
+ field public static final int ERROR_NONE = 0; // 0x0
+ field public static final int ERROR_NO_VALID_DATA = 8; // 0x8
+ field public static final int ERROR_RECORDER_ERROR = 7; // 0x7
+ field public static final int ERROR_RECORDING_GARBAGE_COLLECTED = 10; // 0xa
+ field public static final int ERROR_SOURCE_INACTIVE = 4; // 0x4
+ field public static final int ERROR_UNKNOWN = 1; // 0x1
+ }
+
+ @RequiresApi(21) public static final class VideoRecordEvent.Pause extends androidx.camera.video.VideoRecordEvent {
+ }
+
+ @RequiresApi(21) public static final class VideoRecordEvent.Resume extends androidx.camera.video.VideoRecordEvent {
+ }
+
+ @RequiresApi(21) public static final class VideoRecordEvent.Start extends androidx.camera.video.VideoRecordEvent {
+ }
+
+ @RequiresApi(21) public static final class VideoRecordEvent.Status extends androidx.camera.video.VideoRecordEvent {
+ }
+
+}
+
diff --git a/camera/camera-video/api/res-1.4.0-beta01.txt b/camera/camera-video/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-video/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-video/api/restricted_1.4.0-beta01.txt b/camera/camera-video/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..df297e5
--- /dev/null
+++ b/camera/camera-video/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,220 @@
+// Signature format: 4.0
+package androidx.camera.video {
+
+ @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class AudioStats {
+ method public double getAudioAmplitude();
+ method public abstract int getAudioState();
+ method public abstract Throwable? getErrorCause();
+ method public boolean hasAudio();
+ method public boolean hasError();
+ field public static final double AUDIO_AMPLITUDE_NONE = 0.0;
+ field public static final int AUDIO_STATE_ACTIVE = 0; // 0x0
+ field public static final int AUDIO_STATE_DISABLED = 1; // 0x1
+ field public static final int AUDIO_STATE_ENCODER_ERROR = 3; // 0x3
+ field public static final int AUDIO_STATE_MUTED = 5; // 0x5
+ field public static final int AUDIO_STATE_SOURCE_ERROR = 4; // 0x4
+ field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAudioApi {
+ }
+
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
+ }
+
+ @RequiresApi(21) public class FallbackStrategy {
+ method public static androidx.camera.video.FallbackStrategy higherQualityOrLowerThan(androidx.camera.video.Quality);
+ method public static androidx.camera.video.FallbackStrategy higherQualityThan(androidx.camera.video.Quality);
+ method public static androidx.camera.video.FallbackStrategy lowerQualityOrHigherThan(androidx.camera.video.Quality);
+ method public static androidx.camera.video.FallbackStrategy lowerQualityThan(androidx.camera.video.Quality);
+ }
+
+ @RequiresApi(21) public final class FileDescriptorOutputOptions extends androidx.camera.video.OutputOptions {
+ method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+ }
+
+ @RequiresApi(21) public static final class FileDescriptorOutputOptions.Builder {
+ ctor public FileDescriptorOutputOptions.Builder(android.os.ParcelFileDescriptor);
+ method public androidx.camera.video.FileDescriptorOutputOptions build();
+ method public androidx.camera.video.FileDescriptorOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+ method public androidx.camera.video.FileDescriptorOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+ method public androidx.camera.video.FileDescriptorOutputOptions.Builder setLocation(android.location.Location?);
+ }
+
+ @RequiresApi(21) public final class FileOutputOptions extends androidx.camera.video.OutputOptions {
+ method public java.io.File getFile();
+ }
+
+ @RequiresApi(21) public static final class FileOutputOptions.Builder {
+ ctor public FileOutputOptions.Builder(java.io.File);
+ method public androidx.camera.video.FileOutputOptions build();
+ method public androidx.camera.video.FileOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+ method public androidx.camera.video.FileOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+ method public androidx.camera.video.FileOutputOptions.Builder setLocation(android.location.Location?);
+ }
+
+ @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
+ method public android.net.Uri getCollectionUri();
+ method public android.content.ContentResolver getContentResolver();
+ method public android.content.ContentValues getContentValues();
+ field public static final android.content.ContentValues EMPTY_CONTENT_VALUES;
+ }
+
+ public static final class MediaStoreOutputOptions.Builder {
+ ctor public MediaStoreOutputOptions.Builder(android.content.ContentResolver, android.net.Uri);
+ method public androidx.camera.video.MediaStoreOutputOptions build();
+ method public androidx.camera.video.MediaStoreOutputOptions.Builder setContentValues(android.content.ContentValues);
+ method public androidx.camera.video.MediaStoreOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+ method public androidx.camera.video.MediaStoreOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+ method public androidx.camera.video.MediaStoreOutputOptions.Builder setLocation(android.location.Location?);
+ }
+
+ @RequiresApi(21) public abstract class OutputOptions {
+ method @IntRange(from=0) public long getDurationLimitMillis();
+ method @IntRange(from=0) public long getFileSizeLimit();
+ method public android.location.Location? getLocation();
+ field public static final int DURATION_UNLIMITED = 0; // 0x0
+ field public static final int FILE_SIZE_UNLIMITED = 0; // 0x0
+ }
+
+ @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class OutputResults {
+ ctor public OutputResults();
+ method public abstract android.net.Uri getOutputUri();
+ }
+
+ @RequiresApi(21) public final class PendingRecording {
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public androidx.camera.video.PendingRecording asPersistentRecording();
+ method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
+ }
+
+ @RequiresApi(21) public class Quality {
+ field public static final androidx.camera.video.Quality FHD;
+ field public static final androidx.camera.video.Quality HD;
+ field public static final androidx.camera.video.Quality HIGHEST;
+ field public static final androidx.camera.video.Quality LOWEST;
+ field public static final androidx.camera.video.Quality SD;
+ field public static final androidx.camera.video.Quality UHD;
+ }
+
+ @RequiresApi(21) public final class QualitySelector {
+ method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality);
+ method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality, androidx.camera.video.FallbackStrategy);
+ method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>);
+ method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>, androidx.camera.video.FallbackStrategy);
+ method public static android.util.Size? getResolution(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
+ method @Deprecated public static java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.CameraInfo);
+ method @Deprecated public static boolean isQualitySupported(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
+ }
+
+ @RequiresApi(21) public final class Recorder implements androidx.camera.video.VideoOutput {
+ method public int getAspectRatio();
+ method public java.util.concurrent.Executor? getExecutor();
+ method public androidx.camera.video.QualitySelector getQualitySelector();
+ method public int getTargetVideoEncodingBitRate();
+ method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo);
+ method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo, int);
+ method public int getVideoCapabilitiesSource();
+ method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+ method @RequiresApi(26) public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileDescriptorOutputOptions);
+ method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileOutputOptions);
+ method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.MediaStoreOutputOptions);
+ field public static final androidx.camera.video.QualitySelector DEFAULT_QUALITY_SELECTOR;
+ field public static final int VIDEO_CAPABILITIES_SOURCE_CAMCORDER_PROFILE = 0; // 0x0
+ field public static final int VIDEO_CAPABILITIES_SOURCE_CODEC_CAPABILITIES = 1; // 0x1
+ }
+
+ @RequiresApi(21) public static final class Recorder.Builder {
+ ctor public Recorder.Builder();
+ method public androidx.camera.video.Recorder build();
+ method public androidx.camera.video.Recorder.Builder setAspectRatio(int);
+ method public androidx.camera.video.Recorder.Builder setExecutor(java.util.concurrent.Executor);
+ method public androidx.camera.video.Recorder.Builder setQualitySelector(androidx.camera.video.QualitySelector);
+ method public androidx.camera.video.Recorder.Builder setTargetVideoEncodingBitRate(@IntRange(from=1) int);
+ method public androidx.camera.video.Recorder.Builder setVideoCapabilitiesSource(int);
+ }
+
+ @RequiresApi(21) public final class Recording implements java.lang.AutoCloseable {
+ method public void close();
+ method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public boolean isPersistent();
+ method public void mute(boolean);
+ method public void pause();
+ method public void resume();
+ method public void stop();
+ }
+
+ @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class RecordingStats {
+ method public abstract androidx.camera.video.AudioStats getAudioStats();
+ method public abstract long getNumBytesRecorded();
+ method public abstract long getRecordedDurationNanos();
+ }
+
+ @RequiresApi(21) public interface VideoCapabilities {
+ method public java.util.Set<androidx.camera.core.DynamicRange!> getSupportedDynamicRanges();
+ method public java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.DynamicRange);
+ method public boolean isQualitySupported(androidx.camera.video.Quality, androidx.camera.core.DynamicRange);
+ method public default boolean isStabilizationSupported();
+ }
+
+ @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
+ method public androidx.camera.core.DynamicRange getDynamicRange();
+ method public int getMirrorMode();
+ method public T getOutput();
+ method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
+ method public int getTargetRotation();
+ method public boolean isVideoStabilizationEnabled();
+ method public void setTargetRotation(int);
+ method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
+ }
+
+ @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
+ ctor public VideoCapture.Builder(T);
+ method public androidx.camera.video.VideoCapture<T!> build();
+ method public androidx.camera.video.VideoCapture.Builder<T!> setDynamicRange(androidx.camera.core.DynamicRange);
+ method public androidx.camera.video.VideoCapture.Builder<T!> setMirrorMode(int);
+ method public androidx.camera.video.VideoCapture.Builder<T!> setTargetFrameRate(android.util.Range<java.lang.Integer!>);
+ method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
+ method public androidx.camera.video.VideoCapture.Builder<T!> setVideoStabilizationEnabled(boolean);
+ }
+
+ @RequiresApi(21) public interface VideoOutput {
+ method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+ }
+
+ @RequiresApi(21) public abstract class VideoRecordEvent {
+ method public androidx.camera.video.OutputOptions getOutputOptions();
+ method public androidx.camera.video.RecordingStats getRecordingStats();
+ }
+
+ @RequiresApi(21) public static final class VideoRecordEvent.Finalize extends androidx.camera.video.VideoRecordEvent {
+ method public Throwable? getCause();
+ method public int getError();
+ method public androidx.camera.video.OutputResults getOutputResults();
+ method public boolean hasError();
+ field public static final int ERROR_DURATION_LIMIT_REACHED = 9; // 0x9
+ field public static final int ERROR_ENCODING_FAILED = 6; // 0x6
+ field public static final int ERROR_FILE_SIZE_LIMIT_REACHED = 2; // 0x2
+ field public static final int ERROR_INSUFFICIENT_STORAGE = 3; // 0x3
+ field public static final int ERROR_INVALID_OUTPUT_OPTIONS = 5; // 0x5
+ field public static final int ERROR_NONE = 0; // 0x0
+ field public static final int ERROR_NO_VALID_DATA = 8; // 0x8
+ field public static final int ERROR_RECORDER_ERROR = 7; // 0x7
+ field public static final int ERROR_RECORDING_GARBAGE_COLLECTED = 10; // 0xa
+ field public static final int ERROR_SOURCE_INACTIVE = 4; // 0x4
+ field public static final int ERROR_UNKNOWN = 1; // 0x1
+ }
+
+ @RequiresApi(21) public static final class VideoRecordEvent.Pause extends androidx.camera.video.VideoRecordEvent {
+ }
+
+ @RequiresApi(21) public static final class VideoRecordEvent.Resume extends androidx.camera.video.VideoRecordEvent {
+ }
+
+ @RequiresApi(21) public static final class VideoRecordEvent.Start extends androidx.camera.video.VideoRecordEvent {
+ }
+
+ @RequiresApi(21) public static final class VideoRecordEvent.Status extends androidx.camera.video.VideoRecordEvent {
+ }
+
+}
+
diff --git a/camera/camera-view/api/1.4.0-beta01.txt b/camera/camera-view/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..3d62383
--- /dev/null
+++ b/camera/camera-view/api/1.4.0-beta01.txt
@@ -0,0 +1,198 @@
+// Signature format: 4.0
+package androidx.camera.view {
+
+ @RequiresApi(21) public abstract class CameraController {
+ method @MainThread public void clearEffects();
+ method @MainThread public void clearImageAnalysisAnalyzer();
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
+ method @MainThread public androidx.camera.core.CameraControl? getCameraControl();
+ method @MainThread public androidx.camera.core.CameraInfo? getCameraInfo();
+ method @MainThread public androidx.camera.core.CameraSelector getCameraSelector();
+ method @MainThread public java.util.concurrent.Executor? getImageAnalysisBackgroundExecutor();
+ method @MainThread public int getImageAnalysisBackpressureStrategy();
+ method @MainThread public int getImageAnalysisImageQueueDepth();
+ method @MainThread public int getImageAnalysisOutputImageFormat();
+ method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getImageAnalysisResolutionSelector();
+ method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getImageAnalysisTargetSize();
+ method @MainThread public int getImageCaptureFlashMode();
+ method @MainThread public java.util.concurrent.Executor? getImageCaptureIoExecutor();
+ method @MainThread public int getImageCaptureMode();
+ method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getImageCaptureResolutionSelector();
+ method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getImageCaptureTargetSize();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> getInitializationFuture();
+ method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getPreviewResolutionSelector();
+ method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getPreviewTargetSize();
+ method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTapToFocusState();
+ method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
+ method @MainThread public androidx.camera.core.DynamicRange getVideoCaptureDynamicRange();
+ method @MainThread public int getVideoCaptureMirrorMode();
+ method @MainThread public androidx.camera.video.QualitySelector getVideoCaptureQualitySelector();
+ method @MainThread public android.util.Range<java.lang.Integer!> getVideoCaptureTargetFrameRate();
+ method @MainThread public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
+ method @MainThread public boolean hasCamera(androidx.camera.core.CameraSelector);
+ method @MainThread public boolean isImageAnalysisEnabled();
+ method @MainThread public boolean isImageCaptureEnabled();
+ method @MainThread public boolean isPinchToZoomEnabled();
+ method @MainThread public boolean isRecording();
+ method @MainThread public boolean isTapToFocusEnabled();
+ method @MainThread public boolean isVideoCaptureEnabled();
+ method @MainThread public void setCameraSelector(androidx.camera.core.CameraSelector);
+ method @MainThread public void setEffects(java.util.Set<androidx.camera.core.CameraEffect!>);
+ method @MainThread public void setEnabledUseCases(int);
+ method @MainThread public void setImageAnalysisAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
+ method @MainThread public void setImageAnalysisBackgroundExecutor(java.util.concurrent.Executor?);
+ method @MainThread public void setImageAnalysisBackpressureStrategy(int);
+ method @MainThread public void setImageAnalysisImageQueueDepth(int);
+ method @MainThread public void setImageAnalysisOutputImageFormat(int);
+ method @MainThread public void setImageAnalysisResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+ method @Deprecated @MainThread public void setImageAnalysisTargetSize(androidx.camera.view.CameraController.OutputSize?);
+ method @MainThread public void setImageCaptureFlashMode(int);
+ method @MainThread public void setImageCaptureIoExecutor(java.util.concurrent.Executor?);
+ method @MainThread public void setImageCaptureMode(int);
+ method @MainThread public void setImageCaptureResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+ method @Deprecated @MainThread public void setImageCaptureTargetSize(androidx.camera.view.CameraController.OutputSize?);
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
+ method @MainThread public void setPinchToZoomEnabled(boolean);
+ method @MainThread public void setPreviewResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+ method @Deprecated @MainThread public void setPreviewTargetSize(androidx.camera.view.CameraController.OutputSize?);
+ method @MainThread public void setTapToFocusEnabled(boolean);
+ method @MainThread public void setVideoCaptureDynamicRange(androidx.camera.core.DynamicRange);
+ method @MainThread public void setVideoCaptureMirrorMode(int);
+ method @MainThread public void setVideoCaptureQualitySelector(androidx.camera.video.QualitySelector);
+ method @MainThread public void setVideoCaptureTargetFrameRate(android.util.Range<java.lang.Integer!>);
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
+ method @MainThread @RequiresApi(26) public androidx.camera.video.Recording startRecording(androidx.camera.video.FileDescriptorOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+ method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.FileOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+ method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.MediaStoreOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+ method @MainThread public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
+ method @MainThread public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
+ field public static final int COORDINATE_SYSTEM_VIEW_REFERENCED = 1; // 0x1
+ field public static final int IMAGE_ANALYSIS = 2; // 0x2
+ field public static final int IMAGE_CAPTURE = 1; // 0x1
+ field public static final int TAP_TO_FOCUS_FAILED = 4; // 0x4
+ field public static final int TAP_TO_FOCUS_FOCUSED = 2; // 0x2
+ field public static final int TAP_TO_FOCUS_NOT_FOCUSED = 3; // 0x3
+ field public static final int TAP_TO_FOCUS_NOT_STARTED = 0; // 0x0
+ field public static final int TAP_TO_FOCUS_STARTED = 1; // 0x1
+ field public static final int VIDEO_CAPTURE = 4; // 0x4
+ }
+
+ @Deprecated @RequiresApi(21) public static final class CameraController.OutputSize {
+ ctor @Deprecated public CameraController.OutputSize(android.util.Size);
+ ctor @Deprecated public CameraController.OutputSize(int);
+ method @Deprecated public int getAspectRatio();
+ method @Deprecated public android.util.Size? getResolution();
+ field @Deprecated public static final int UNASSIGNED_ASPECT_RATIO = -1; // 0xffffffff
+ }
+
+ @RequiresApi(21) public final class LifecycleCameraController extends androidx.camera.view.CameraController {
+ ctor public LifecycleCameraController(android.content.Context);
+ method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
+ method @MainThread public void unbind();
+ }
+
+ @RequiresApi(21) public final class PreviewView extends android.widget.FrameLayout {
+ ctor @UiThread public PreviewView(android.content.Context);
+ ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?);
+ ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int);
+ ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int, int);
+ method @UiThread public android.graphics.Bitmap? getBitmap();
+ method @UiThread public androidx.camera.view.CameraController? getController();
+ method @UiThread public androidx.camera.view.PreviewView.ImplementationMode getImplementationMode();
+ method @UiThread public androidx.camera.core.MeteringPointFactory getMeteringPointFactory();
+ method @SuppressCompatibility public androidx.camera.view.transform.OutputTransform? getOutputTransform();
+ method public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
+ method @UiThread public androidx.camera.view.PreviewView.ScaleType getScaleType();
+ method @UiThread public androidx.camera.core.Preview.SurfaceProvider getSurfaceProvider();
+ method @UiThread public androidx.camera.core.ViewPort? getViewPort();
+ method @UiThread public androidx.camera.core.ViewPort? getViewPort(int);
+ method @UiThread public void setController(androidx.camera.view.CameraController?);
+ method @UiThread public void setImplementationMode(androidx.camera.view.PreviewView.ImplementationMode);
+ method @UiThread public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
+ method @UiThread public void setScreenFlashWindow(android.view.Window?);
+ }
+
+ @RequiresApi(21) public enum PreviewView.ImplementationMode {
+ enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode COMPATIBLE;
+ enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode PERFORMANCE;
+ }
+
+ @RequiresApi(21) public enum PreviewView.ScaleType {
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_CENTER;
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_END;
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_START;
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_CENTER;
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_END;
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_START;
+ }
+
+ public enum PreviewView.StreamState {
+ enum_constant public static final androidx.camera.view.PreviewView.StreamState IDLE;
+ enum_constant public static final androidx.camera.view.PreviewView.StreamState STREAMING;
+ }
+
+ @RequiresApi(21) public final class RotationProvider {
+ ctor public RotationProvider(android.content.Context);
+ method @CheckResult public boolean addListener(java.util.concurrent.Executor, androidx.camera.view.RotationProvider.Listener);
+ method public void removeListener(androidx.camera.view.RotationProvider.Listener);
+ }
+
+ public static interface RotationProvider.Listener {
+ method public void onRotationChanged(int);
+ }
+
+ @RequiresApi(21) public final class ScreenFlashView extends android.view.View {
+ ctor @UiThread public ScreenFlashView(android.content.Context);
+ ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?);
+ ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?, int);
+ ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?, int, int);
+ method @UiThread public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
+ method @UiThread public void setController(androidx.camera.view.CameraController?);
+ method @UiThread public void setScreenFlashWindow(android.view.Window?);
+ }
+
+}
+
+package androidx.camera.view.transform {
+
+ @SuppressCompatibility @RequiresApi(21) public final class CoordinateTransform {
+ ctor public CoordinateTransform(androidx.camera.view.transform.OutputTransform, androidx.camera.view.transform.OutputTransform);
+ method public void mapPoint(android.graphics.PointF);
+ method public void mapPoints(float[]);
+ method public void mapRect(android.graphics.RectF);
+ method public void transform(android.graphics.Matrix);
+ }
+
+ @SuppressCompatibility @RequiresApi(21) public final class FileTransformFactory {
+ ctor public FileTransformFactory();
+ method public androidx.camera.view.transform.OutputTransform getOutputTransform(android.content.ContentResolver, android.net.Uri) throws java.io.IOException;
+ method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.File) throws java.io.IOException;
+ method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.InputStream) throws java.io.IOException;
+ method public boolean isUsingExifOrientation();
+ method public void setUsingExifOrientation(boolean);
+ }
+
+ @SuppressCompatibility @RequiresApi(21) public final class ImageProxyTransformFactory {
+ ctor public ImageProxyTransformFactory();
+ method public androidx.camera.view.transform.OutputTransform getOutputTransform(androidx.camera.core.ImageProxy);
+ method public boolean isUsingCropRect();
+ method public boolean isUsingRotationDegrees();
+ method public void setUsingCropRect(boolean);
+ method public void setUsingRotationDegrees(boolean);
+ }
+
+ @SuppressCompatibility @RequiresApi(21) public final class OutputTransform {
+ }
+
+}
+
+package androidx.camera.view.video {
+
+ @RequiresApi(21) public class AudioConfig {
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public static androidx.camera.view.video.AudioConfig create(boolean);
+ method public boolean getAudioEnabled();
+ field public static final androidx.camera.view.video.AudioConfig AUDIO_DISABLED;
+ }
+
+}
+
diff --git a/camera/camera-view/api/res-1.4.0-beta01.txt b/camera/camera-view/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-view/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-view/api/restricted_1.4.0-beta01.txt b/camera/camera-view/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..3d62383
--- /dev/null
+++ b/camera/camera-view/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,198 @@
+// Signature format: 4.0
+package androidx.camera.view {
+
+ @RequiresApi(21) public abstract class CameraController {
+ method @MainThread public void clearEffects();
+ method @MainThread public void clearImageAnalysisAnalyzer();
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
+ method @MainThread public androidx.camera.core.CameraControl? getCameraControl();
+ method @MainThread public androidx.camera.core.CameraInfo? getCameraInfo();
+ method @MainThread public androidx.camera.core.CameraSelector getCameraSelector();
+ method @MainThread public java.util.concurrent.Executor? getImageAnalysisBackgroundExecutor();
+ method @MainThread public int getImageAnalysisBackpressureStrategy();
+ method @MainThread public int getImageAnalysisImageQueueDepth();
+ method @MainThread public int getImageAnalysisOutputImageFormat();
+ method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getImageAnalysisResolutionSelector();
+ method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getImageAnalysisTargetSize();
+ method @MainThread public int getImageCaptureFlashMode();
+ method @MainThread public java.util.concurrent.Executor? getImageCaptureIoExecutor();
+ method @MainThread public int getImageCaptureMode();
+ method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getImageCaptureResolutionSelector();
+ method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getImageCaptureTargetSize();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> getInitializationFuture();
+ method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getPreviewResolutionSelector();
+ method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getPreviewTargetSize();
+ method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTapToFocusState();
+ method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
+ method @MainThread public androidx.camera.core.DynamicRange getVideoCaptureDynamicRange();
+ method @MainThread public int getVideoCaptureMirrorMode();
+ method @MainThread public androidx.camera.video.QualitySelector getVideoCaptureQualitySelector();
+ method @MainThread public android.util.Range<java.lang.Integer!> getVideoCaptureTargetFrameRate();
+ method @MainThread public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
+ method @MainThread public boolean hasCamera(androidx.camera.core.CameraSelector);
+ method @MainThread public boolean isImageAnalysisEnabled();
+ method @MainThread public boolean isImageCaptureEnabled();
+ method @MainThread public boolean isPinchToZoomEnabled();
+ method @MainThread public boolean isRecording();
+ method @MainThread public boolean isTapToFocusEnabled();
+ method @MainThread public boolean isVideoCaptureEnabled();
+ method @MainThread public void setCameraSelector(androidx.camera.core.CameraSelector);
+ method @MainThread public void setEffects(java.util.Set<androidx.camera.core.CameraEffect!>);
+ method @MainThread public void setEnabledUseCases(int);
+ method @MainThread public void setImageAnalysisAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
+ method @MainThread public void setImageAnalysisBackgroundExecutor(java.util.concurrent.Executor?);
+ method @MainThread public void setImageAnalysisBackpressureStrategy(int);
+ method @MainThread public void setImageAnalysisImageQueueDepth(int);
+ method @MainThread public void setImageAnalysisOutputImageFormat(int);
+ method @MainThread public void setImageAnalysisResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+ method @Deprecated @MainThread public void setImageAnalysisTargetSize(androidx.camera.view.CameraController.OutputSize?);
+ method @MainThread public void setImageCaptureFlashMode(int);
+ method @MainThread public void setImageCaptureIoExecutor(java.util.concurrent.Executor?);
+ method @MainThread public void setImageCaptureMode(int);
+ method @MainThread public void setImageCaptureResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+ method @Deprecated @MainThread public void setImageCaptureTargetSize(androidx.camera.view.CameraController.OutputSize?);
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
+ method @MainThread public void setPinchToZoomEnabled(boolean);
+ method @MainThread public void setPreviewResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+ method @Deprecated @MainThread public void setPreviewTargetSize(androidx.camera.view.CameraController.OutputSize?);
+ method @MainThread public void setTapToFocusEnabled(boolean);
+ method @MainThread public void setVideoCaptureDynamicRange(androidx.camera.core.DynamicRange);
+ method @MainThread public void setVideoCaptureMirrorMode(int);
+ method @MainThread public void setVideoCaptureQualitySelector(androidx.camera.video.QualitySelector);
+ method @MainThread public void setVideoCaptureTargetFrameRate(android.util.Range<java.lang.Integer!>);
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
+ method @MainThread @RequiresApi(26) public androidx.camera.video.Recording startRecording(androidx.camera.video.FileDescriptorOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+ method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.FileOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+ method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.MediaStoreOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+ method @MainThread public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
+ method @MainThread public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
+ field public static final int COORDINATE_SYSTEM_VIEW_REFERENCED = 1; // 0x1
+ field public static final int IMAGE_ANALYSIS = 2; // 0x2
+ field public static final int IMAGE_CAPTURE = 1; // 0x1
+ field public static final int TAP_TO_FOCUS_FAILED = 4; // 0x4
+ field public static final int TAP_TO_FOCUS_FOCUSED = 2; // 0x2
+ field public static final int TAP_TO_FOCUS_NOT_FOCUSED = 3; // 0x3
+ field public static final int TAP_TO_FOCUS_NOT_STARTED = 0; // 0x0
+ field public static final int TAP_TO_FOCUS_STARTED = 1; // 0x1
+ field public static final int VIDEO_CAPTURE = 4; // 0x4
+ }
+
+ @Deprecated @RequiresApi(21) public static final class CameraController.OutputSize {
+ ctor @Deprecated public CameraController.OutputSize(android.util.Size);
+ ctor @Deprecated public CameraController.OutputSize(int);
+ method @Deprecated public int getAspectRatio();
+ method @Deprecated public android.util.Size? getResolution();
+ field @Deprecated public static final int UNASSIGNED_ASPECT_RATIO = -1; // 0xffffffff
+ }
+
+ @RequiresApi(21) public final class LifecycleCameraController extends androidx.camera.view.CameraController {
+ ctor public LifecycleCameraController(android.content.Context);
+ method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
+ method @MainThread public void unbind();
+ }
+
+ @RequiresApi(21) public final class PreviewView extends android.widget.FrameLayout {
+ ctor @UiThread public PreviewView(android.content.Context);
+ ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?);
+ ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int);
+ ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int, int);
+ method @UiThread public android.graphics.Bitmap? getBitmap();
+ method @UiThread public androidx.camera.view.CameraController? getController();
+ method @UiThread public androidx.camera.view.PreviewView.ImplementationMode getImplementationMode();
+ method @UiThread public androidx.camera.core.MeteringPointFactory getMeteringPointFactory();
+ method @SuppressCompatibility public androidx.camera.view.transform.OutputTransform? getOutputTransform();
+ method public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
+ method @UiThread public androidx.camera.view.PreviewView.ScaleType getScaleType();
+ method @UiThread public androidx.camera.core.Preview.SurfaceProvider getSurfaceProvider();
+ method @UiThread public androidx.camera.core.ViewPort? getViewPort();
+ method @UiThread public androidx.camera.core.ViewPort? getViewPort(int);
+ method @UiThread public void setController(androidx.camera.view.CameraController?);
+ method @UiThread public void setImplementationMode(androidx.camera.view.PreviewView.ImplementationMode);
+ method @UiThread public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
+ method @UiThread public void setScreenFlashWindow(android.view.Window?);
+ }
+
+ @RequiresApi(21) public enum PreviewView.ImplementationMode {
+ enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode COMPATIBLE;
+ enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode PERFORMANCE;
+ }
+
+ @RequiresApi(21) public enum PreviewView.ScaleType {
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_CENTER;
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_END;
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_START;
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_CENTER;
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_END;
+ enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_START;
+ }
+
+ public enum PreviewView.StreamState {
+ enum_constant public static final androidx.camera.view.PreviewView.StreamState IDLE;
+ enum_constant public static final androidx.camera.view.PreviewView.StreamState STREAMING;
+ }
+
+ @RequiresApi(21) public final class RotationProvider {
+ ctor public RotationProvider(android.content.Context);
+ method @CheckResult public boolean addListener(java.util.concurrent.Executor, androidx.camera.view.RotationProvider.Listener);
+ method public void removeListener(androidx.camera.view.RotationProvider.Listener);
+ }
+
+ public static interface RotationProvider.Listener {
+ method public void onRotationChanged(int);
+ }
+
+ @RequiresApi(21) public final class ScreenFlashView extends android.view.View {
+ ctor @UiThread public ScreenFlashView(android.content.Context);
+ ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?);
+ ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?, int);
+ ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?, int, int);
+ method @UiThread public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
+ method @UiThread public void setController(androidx.camera.view.CameraController?);
+ method @UiThread public void setScreenFlashWindow(android.view.Window?);
+ }
+
+}
+
+package androidx.camera.view.transform {
+
+ @SuppressCompatibility @RequiresApi(21) public final class CoordinateTransform {
+ ctor public CoordinateTransform(androidx.camera.view.transform.OutputTransform, androidx.camera.view.transform.OutputTransform);
+ method public void mapPoint(android.graphics.PointF);
+ method public void mapPoints(float[]);
+ method public void mapRect(android.graphics.RectF);
+ method public void transform(android.graphics.Matrix);
+ }
+
+ @SuppressCompatibility @RequiresApi(21) public final class FileTransformFactory {
+ ctor public FileTransformFactory();
+ method public androidx.camera.view.transform.OutputTransform getOutputTransform(android.content.ContentResolver, android.net.Uri) throws java.io.IOException;
+ method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.File) throws java.io.IOException;
+ method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.InputStream) throws java.io.IOException;
+ method public boolean isUsingExifOrientation();
+ method public void setUsingExifOrientation(boolean);
+ }
+
+ @SuppressCompatibility @RequiresApi(21) public final class ImageProxyTransformFactory {
+ ctor public ImageProxyTransformFactory();
+ method public androidx.camera.view.transform.OutputTransform getOutputTransform(androidx.camera.core.ImageProxy);
+ method public boolean isUsingCropRect();
+ method public boolean isUsingRotationDegrees();
+ method public void setUsingCropRect(boolean);
+ method public void setUsingRotationDegrees(boolean);
+ }
+
+ @SuppressCompatibility @RequiresApi(21) public final class OutputTransform {
+ }
+
+}
+
+package androidx.camera.view.video {
+
+ @RequiresApi(21) public class AudioConfig {
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public static androidx.camera.view.video.AudioConfig create(boolean);
+ method public boolean getAudioEnabled();
+ field public static final androidx.camera.view.video.AudioConfig AUDIO_DISABLED;
+ }
+
+}
+
diff --git a/camera/camera-view/build.gradle b/camera/camera-view/build.gradle
index 3ffab6d..1bae697 100644
--- a/camera/camera-view/build.gradle
+++ b/camera/camera-view/build.gradle
@@ -35,6 +35,7 @@
api(project(":camera:camera-core"))
api(project(":camera:camera-video"))
implementation(project(":camera:camera-lifecycle"))
+ implementation(project(":camera:camera-viewfinder-core"))
implementation("androidx.annotation:annotation-experimental:1.4.0")
implementation(libs.guavaListenableFuture)
implementation("androidx.core:core:1.3.2")
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
index 10f4461..2890858 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
@@ -523,8 +523,10 @@
}
private fun initialLifecycleOwner() {
- lifecycleOwner = FakeLifecycleOwner()
- lifecycleOwner.startAndResume()
+ instrumentation.runOnMainSync {
+ lifecycleOwner = FakeLifecycleOwner()
+ lifecycleOwner.startAndResume()
+ }
}
private fun initialPreviewView() {
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
index 076669e..c9ca792 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
@@ -39,7 +39,6 @@
import android.util.Size;
import android.view.Display;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.TextureView;
@@ -84,6 +83,7 @@
import androidx.camera.view.internal.compat.quirk.SurfaceViewStretchedQuirk;
import androidx.camera.view.transform.CoordinateTransform;
import androidx.camera.view.transform.OutputTransform;
+import androidx.camera.viewfinder.core.ZoomGestureDetector;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
@@ -174,7 +174,7 @@
// Detector for zoom-to-scale.
@NonNull
- private final ScaleGestureDetector mScaleGestureDetector;
+ private final ZoomGestureDetector mZoomGestureDetector;
// Synthetic access
@SuppressWarnings("WeakerAccess")
@@ -323,8 +323,14 @@
attributes.recycle();
}
- mScaleGestureDetector = new ScaleGestureDetector(
- context, new PinchToZoomOnScaleGestureListener());
+ mZoomGestureDetector = new ZoomGestureDetector(context,
+ (type, detector) -> {
+ if (type == ZoomGestureDetector.ZOOM_GESTURE_MOVE
+ && mCameraController != null) {
+ mCameraController.onPinchToZoom(detector.getScaleFactor());
+ }
+ return true;
+ });
// Set background only if it wasn't already set. A default background prevents the content
// behind the PreviewView from being visible before the preview starts streaming.
@@ -381,7 +387,7 @@
// invoked twice.
return true;
}
- return mScaleGestureDetector.onTouchEvent(event) || super.onTouchEvent(event);
+ return mZoomGestureDetector.onTouchEvent(event) || super.onTouchEvent(event);
}
@Override
@@ -931,20 +937,6 @@
}
/**
- * GestureListener that speeds up scale factor and sends it to controller.
- */
- class PinchToZoomOnScaleGestureListener extends
- ScaleGestureDetector.SimpleOnScaleGestureListener {
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- if (mCameraController != null) {
- mCameraController.onPinchToZoom(detector.getScaleFactor());
- }
- return true;
- }
- }
-
- /**
* Sets the {@link CameraController}.
*
* <p> Once set, the controller will use {@link PreviewView} to display camera preview feed.
diff --git a/camera/camera-viewfinder-compose/build.gradle b/camera/camera-viewfinder-compose/build.gradle
index 2bc8ab8..91f4586 100644
--- a/camera/camera-viewfinder-compose/build.gradle
+++ b/camera/camera-viewfinder-compose/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -51,7 +51,7 @@
androidx {
name = "androidx.camera:camera-viewfinder-compose"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2023"
description = "Composable ViewFinder implementation for CameraX"
metalavaK2UastEnabled = true
diff --git a/camera/camera-viewfinder-core/api/current.txt b/camera/camera-viewfinder-core/api/current.txt
index 5076d998..7f858a3 100644
--- a/camera/camera-viewfinder-core/api/current.txt
+++ b/camera/camera-viewfinder-core/api/current.txt
@@ -1,9 +1,47 @@
// Signature format: 4.0
+package androidx.camera.viewfinder.core {
+
+ @RequiresApi(21) public final class ZoomGestureDetector {
+ ctor public ZoomGestureDetector(android.content.Context context, androidx.camera.viewfinder.core.ZoomGestureDetector.OnZoomGestureListener listener);
+ ctor public ZoomGestureDetector(android.content.Context context, optional int spanSlop, androidx.camera.viewfinder.core.ZoomGestureDetector.OnZoomGestureListener listener);
+ ctor public ZoomGestureDetector(android.content.Context context, optional int spanSlop, optional int minSpan, androidx.camera.viewfinder.core.ZoomGestureDetector.OnZoomGestureListener listener);
+ method public long getEventTime();
+ method public float getFocusX();
+ method public float getFocusY();
+ method public float getScaleFactor();
+ method public long getTimeDelta();
+ method public boolean isInProgress();
+ method public boolean isQuickZoomEnabled();
+ method public boolean isStylusZoomEnabled();
+ method @UiThread public boolean onTouchEvent(android.view.MotionEvent event);
+ method public void setQuickZoomEnabled(boolean);
+ method public void setStylusZoomEnabled(boolean);
+ property public final long eventTime;
+ property public final float focusX;
+ property public final float focusY;
+ property public final boolean isInProgress;
+ property public final boolean isQuickZoomEnabled;
+ property public final boolean isStylusZoomEnabled;
+ property public final float scaleFactor;
+ property public final long timeDelta;
+ field public static final androidx.camera.viewfinder.core.ZoomGestureDetector.Companion Companion;
+ field public static final int ZOOM_GESTURE_BEGIN = 1; // 0x1
+ field public static final int ZOOM_GESTURE_END = 2; // 0x2
+ field public static final int ZOOM_GESTURE_MOVE = 0; // 0x0
+ }
+
+ public static final class ZoomGestureDetector.Companion {
+ }
+
+ public static fun interface ZoomGestureDetector.OnZoomGestureListener {
+ method @UiThread public boolean onZoomEvent(int type, androidx.camera.viewfinder.core.ZoomGestureDetector detector);
+ }
+
+}
+
package @RequiresApi(21) androidx.camera.viewfinder.surface {
public enum ImplementationMode {
- method public static androidx.camera.viewfinder.surface.ImplementationMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.camera.viewfinder.surface.ImplementationMode[] values();
enum_constant public static final androidx.camera.viewfinder.surface.ImplementationMode COMPATIBLE;
enum_constant public static final androidx.camera.viewfinder.surface.ImplementationMode PERFORMANCE;
field public static final androidx.camera.viewfinder.surface.ImplementationMode.Companion Companion;
@@ -34,19 +72,21 @@
public final class ViewfinderSurfaceRequest {
method public androidx.camera.viewfinder.surface.ImplementationMode? getImplementationMode();
- method public int getLensFacing();
+ method public int getOutputMirrorMode();
method public android.util.Size getResolution();
- method public int getSensorOrientation();
+ method public int getSourceOrientation();
method public suspend Object? getSurface(kotlin.coroutines.Continuation<? super android.view.Surface>);
method public com.google.common.util.concurrent.ListenableFuture<android.view.Surface> getSurfaceAsync();
method public void markSurfaceSafeToRelease();
method public void provideSurface(android.view.Surface surface, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Result?> resultListener);
method public boolean willNotProvideSurface();
property public final androidx.camera.viewfinder.surface.ImplementationMode? implementationMode;
- property public final int lensFacing;
+ property public final int outputMirrorMode;
property public final android.util.Size resolution;
- property public final int sensorOrientation;
+ property public final int sourceOrientation;
field public static final androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Companion Companion;
+ field public static final int MIRROR_MODE_HORIZONTAL = 1; // 0x1
+ field public static final int MIRROR_MODE_NONE = 0; // 0x0
}
public static final class ViewfinderSurfaceRequest.Builder {
@@ -55,8 +95,8 @@
ctor public ViewfinderSurfaceRequest.Builder(androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder builder);
method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest build();
method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder setImplementationMode(androidx.camera.viewfinder.surface.ImplementationMode? implementationMode);
- method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder setLensFacing(int lensFacing);
- method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder setSensorOrientation(int sensorOrientation);
+ method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder setOutputMirrorMode(int outputMirrorMode);
+ method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder setSourceOrientation(int sourceOrientation);
}
public static final class ViewfinderSurfaceRequest.Companion {
diff --git a/camera/camera-viewfinder-core/api/restricted_current.txt b/camera/camera-viewfinder-core/api/restricted_current.txt
index 5076d998..7f858a3 100644
--- a/camera/camera-viewfinder-core/api/restricted_current.txt
+++ b/camera/camera-viewfinder-core/api/restricted_current.txt
@@ -1,9 +1,47 @@
// Signature format: 4.0
+package androidx.camera.viewfinder.core {
+
+ @RequiresApi(21) public final class ZoomGestureDetector {
+ ctor public ZoomGestureDetector(android.content.Context context, androidx.camera.viewfinder.core.ZoomGestureDetector.OnZoomGestureListener listener);
+ ctor public ZoomGestureDetector(android.content.Context context, optional int spanSlop, androidx.camera.viewfinder.core.ZoomGestureDetector.OnZoomGestureListener listener);
+ ctor public ZoomGestureDetector(android.content.Context context, optional int spanSlop, optional int minSpan, androidx.camera.viewfinder.core.ZoomGestureDetector.OnZoomGestureListener listener);
+ method public long getEventTime();
+ method public float getFocusX();
+ method public float getFocusY();
+ method public float getScaleFactor();
+ method public long getTimeDelta();
+ method public boolean isInProgress();
+ method public boolean isQuickZoomEnabled();
+ method public boolean isStylusZoomEnabled();
+ method @UiThread public boolean onTouchEvent(android.view.MotionEvent event);
+ method public void setQuickZoomEnabled(boolean);
+ method public void setStylusZoomEnabled(boolean);
+ property public final long eventTime;
+ property public final float focusX;
+ property public final float focusY;
+ property public final boolean isInProgress;
+ property public final boolean isQuickZoomEnabled;
+ property public final boolean isStylusZoomEnabled;
+ property public final float scaleFactor;
+ property public final long timeDelta;
+ field public static final androidx.camera.viewfinder.core.ZoomGestureDetector.Companion Companion;
+ field public static final int ZOOM_GESTURE_BEGIN = 1; // 0x1
+ field public static final int ZOOM_GESTURE_END = 2; // 0x2
+ field public static final int ZOOM_GESTURE_MOVE = 0; // 0x0
+ }
+
+ public static final class ZoomGestureDetector.Companion {
+ }
+
+ public static fun interface ZoomGestureDetector.OnZoomGestureListener {
+ method @UiThread public boolean onZoomEvent(int type, androidx.camera.viewfinder.core.ZoomGestureDetector detector);
+ }
+
+}
+
package @RequiresApi(21) androidx.camera.viewfinder.surface {
public enum ImplementationMode {
- method public static androidx.camera.viewfinder.surface.ImplementationMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.camera.viewfinder.surface.ImplementationMode[] values();
enum_constant public static final androidx.camera.viewfinder.surface.ImplementationMode COMPATIBLE;
enum_constant public static final androidx.camera.viewfinder.surface.ImplementationMode PERFORMANCE;
field public static final androidx.camera.viewfinder.surface.ImplementationMode.Companion Companion;
@@ -34,19 +72,21 @@
public final class ViewfinderSurfaceRequest {
method public androidx.camera.viewfinder.surface.ImplementationMode? getImplementationMode();
- method public int getLensFacing();
+ method public int getOutputMirrorMode();
method public android.util.Size getResolution();
- method public int getSensorOrientation();
+ method public int getSourceOrientation();
method public suspend Object? getSurface(kotlin.coroutines.Continuation<? super android.view.Surface>);
method public com.google.common.util.concurrent.ListenableFuture<android.view.Surface> getSurfaceAsync();
method public void markSurfaceSafeToRelease();
method public void provideSurface(android.view.Surface surface, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Result?> resultListener);
method public boolean willNotProvideSurface();
property public final androidx.camera.viewfinder.surface.ImplementationMode? implementationMode;
- property public final int lensFacing;
+ property public final int outputMirrorMode;
property public final android.util.Size resolution;
- property public final int sensorOrientation;
+ property public final int sourceOrientation;
field public static final androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Companion Companion;
+ field public static final int MIRROR_MODE_HORIZONTAL = 1; // 0x1
+ field public static final int MIRROR_MODE_NONE = 0; // 0x0
}
public static final class ViewfinderSurfaceRequest.Builder {
@@ -55,8 +95,8 @@
ctor public ViewfinderSurfaceRequest.Builder(androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder builder);
method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest build();
method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder setImplementationMode(androidx.camera.viewfinder.surface.ImplementationMode? implementationMode);
- method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder setLensFacing(int lensFacing);
- method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder setSensorOrientation(int sensorOrientation);
+ method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder setOutputMirrorMode(int outputMirrorMode);
+ method public androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder setSourceOrientation(int sourceOrientation);
}
public static final class ViewfinderSurfaceRequest.Companion {
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/HandlerScheduledExecutorService.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/HandlerScheduledExecutorService.kt
index 1f8cf56..52c5c836 100644
--- a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/HandlerScheduledExecutorService.kt
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/HandlerScheduledExecutorService.kt
@@ -152,7 +152,7 @@
handler.removeCallbacks(this@HandlerScheduledFuture)
}
},
- CameraExecutors.directExecutor()
+ ViewfinderExecutors.directExecutor()
)
mCompleter.set(completer)
"HandlerScheduledFuture-$mTask"
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/CameraExecutors.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/ViewfinderExecutors.kt
similarity index 97%
rename from camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/CameraExecutors.kt
rename to camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/ViewfinderExecutors.kt
index acd04ef..14a9fd3 100644
--- a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/CameraExecutors.kt
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/ViewfinderExecutors.kt
@@ -22,7 +22,7 @@
/**
* Utility class for generating specific implementations of [Executor].
*/
-object CameraExecutors {
+object ViewfinderExecutors {
/** Returns a cached [ScheduledExecutorService] which posts to the main thread. */
@JvmStatic
fun mainThreadExecutor(): ScheduledExecutorService {
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ChainingListenableFuture.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ChainingListenableFuture.kt
index 8c68dff..b126920 100644
--- a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ChainingListenableFuture.kt
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ChainingListenableFuture.kt
@@ -16,7 +16,7 @@
package androidx.camera.impl.utils.futures
-import androidx.camera.impl.utils.executor.CameraExecutors
+import androidx.camera.impl.utils.executor.ViewfinderExecutors
import androidx.core.util.Preconditions
import com.google.common.util.concurrent.ListenableFuture
import java.lang.reflect.UndeclaredThrowableException
@@ -220,7 +220,7 @@
// Don't pin inputs beyond completion
mOutputFuture = null
}
- }, CameraExecutors.directExecutor())
+ }, ViewfinderExecutors.directExecutor())
} catch (e: UndeclaredThrowableException) {
// Set the cause of the exception as this future's exception
e.cause?.let { setException(it) }
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/Futures.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/Futures.kt
index 5d6a6c4..a4ad534 100644
--- a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/Futures.kt
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/Futures.kt
@@ -17,7 +17,7 @@
package androidx.camera.impl.utils.futures
import androidx.arch.core.util.Function
-import androidx.camera.impl.utils.executor.CameraExecutors
+import androidx.camera.impl.utils.executor.ViewfinderExecutors
import androidx.concurrent.futures.CallbackToFutureAdapter
import androidx.core.util.Preconditions
import com.google.common.util.concurrent.ListenableFuture
@@ -130,7 +130,7 @@
input,
{ functionInput -> functionInput },
completer,
- CameraExecutors.directExecutor()
+ ViewfinderExecutors.directExecutor()
)
}
@@ -204,7 +204,7 @@
// Propagate cancellation from completer to input future
completer.addCancellationListener(
{ input.cancel(true) },
- CameraExecutors.directExecutor()
+ ViewfinderExecutors.directExecutor()
)
}
}
@@ -230,7 +230,7 @@
// Input of function is same as output
propagateTransform(
false, future, { input -> input }, it,
- CameraExecutors.directExecutor()
+ ViewfinderExecutors.directExecutor()
)
"nonCancellationPropagating[$future]"
}
@@ -253,7 +253,7 @@
): ListenableFuture<List<V?>?> {
return ListFuture(
futures.toList(), false,
- CameraExecutors.directExecutor()
+ ViewfinderExecutors.directExecutor()
)
}
@@ -277,7 +277,7 @@
return ListFuture(
futures.toList(),
true,
- CameraExecutors.directExecutor()
+ ViewfinderExecutors.directExecutor()
)
}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ListFuture.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ListFuture.kt
index cc83d93..783f24d 100644
--- a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ListFuture.kt
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ListFuture.kt
@@ -16,7 +16,7 @@
package androidx.camera.impl.utils.futures
-import androidx.camera.impl.utils.executor.CameraExecutors
+import androidx.camera.impl.utils.executor.ViewfinderExecutors
import androidx.concurrent.futures.CallbackToFutureAdapter
import androidx.core.util.Preconditions
import com.google.common.util.concurrent.ListenableFuture
@@ -84,7 +84,7 @@
// Let go of the memory held by other futuresInternal
futuresInternal = null
- }, CameraExecutors.directExecutor())
+ }, ViewfinderExecutors.directExecutor())
// Now begin the "real" initialization.
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/core/ZoomGestureDetector.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/core/ZoomGestureDetector.kt
index 1796e95..fd6c42f7 100644
--- a/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/core/ZoomGestureDetector.kt
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/core/ZoomGestureDetector.kt
@@ -22,34 +22,36 @@
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
+import androidx.annotation.IntDef
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
+import androidx.annotation.UiThread
import androidx.camera.viewfinder.core.ZoomGestureDetector.OnZoomGestureListener
import kotlin.math.abs
import kotlin.math.hypot
/**
- * Detects scaling transformation gestures that interprets zooming events using the supplied
- * [MotionEvent]s.
- *
- * The [OnZoomGestureListener] callback will notify users when a particular
- * gesture event has occurred.
- *
- * This class should only be used with [MotionEvent]s reported via touch.
+ * Detector that interprets [MotionEvent]s and notify users when a zooming gesture has occurred.
*
* To use this class to do pinch-to-zoom on the viewfinder:
- * - In the [OnZoomGestureListener.onZoom], get the [scaleFactor] and set it to
+ * - In the [OnZoomGestureListener.onZoomEvent], get the [scaleFactor] and set it to
* `CameraControl.setZoomRatio` if the factor is in the range of `ZoomState.getMinZoomRatio` and
* `ZoomState.getMaxZoomRatio`. Then create an instance of the `ZoomGestureDetector` with the
* [OnZoomGestureListener].
- * - In the [View.onTouchEvent], call [onTouchEvent] and pass the [MotionEvent] to the
+ * - In the [View.onTouchEvent], call [onTouchEvent] and pass the [MotionEvent]s to the
* `ZoomGestureDetector`.
*
+ * @constructor Creates a ZoomGestureDetector for detecting zooming gesture.
+ * @param context The application context.
+ * @param spanSlop The distance in pixels touches can wander before a gesture to be interpreted
+ * as zooming.
+ * @param minSpan The distance in pixels between touches that must be reached for a gesture to be
+ * interpreted as zooming.
+ * @param listener The listener to receive the callback.
* @sample androidx.camera.viewfinder.core.samples.onTouchEventSample
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-class ZoomGestureDetector @SuppressLint("ExecutorRegistration") constructor(
+class ZoomGestureDetector @SuppressLint("ExecutorRegistration") @JvmOverloads constructor(
private val context: Context,
private val spanSlop: Int = ViewConfiguration.get(context).scaledTouchSlop * 2,
private val minSpan: Int = DEFAULT_MIN_SPAN,
@@ -81,13 +83,14 @@
* Once receiving [ZOOM_GESTURE_END] event, [focusX] and [focusY] will return focal point of
* the pointers remaining on the screen.
*
- * @type The type of the event. Possible values include [ZOOM_GESTURE_MOVE],
+ * @param type The type of the event. Possible values include [ZOOM_GESTURE_MOVE],
* [ZOOM_GESTURE_BEGIN] and [ZOOM_GESTURE_END].
* @param detector The detector reporting the event - use this to retrieve extended info
* about event state.
* @return Whether or not the detector should consider this event as handled.
*/
- fun onZoom(type: Int, detector: ZoomGestureDetector): Boolean
+ @UiThread
+ fun onZoomEvent(@ZoomGesture type: Int, detector: ZoomGestureDetector): Boolean
}
/**
@@ -128,43 +131,37 @@
* The average distance in pixels between each of the pointers forming the gesture in progress
* through the focal point.
*/
- var currentSpan = 0f
- private set
+ private var currentSpan = 0f
/**
* The previous average distance in pixels between each of the pointers forming the gesture in
* progress through the focal point.
*/
- var previousSpan = 0f
- private set
+ private var previousSpan = 0f
/**
* The average X distance in pixels between each of the pointers forming the gesture in progress
* through the focal point.
*/
- var currentSpanX = 0f
- private set
+ private var currentSpanX = 0f
/**
* The average Y distance in pixels between each of the pointers forming the gesture in progress
* through the focal point.
*/
- var currentSpanY = 0f
- private set
+ private var currentSpanY = 0f
/**
* The previous average X distance in pixels between each of the pointers forming the gesture in
* progress through the focal point.
*/
- var previousSpanX = 0f
- private set
+ private var previousSpanX = 0f
/**
* The previous average Y distance in pixels between each of the pointers forming the gesture in
* progress through the focal point.
*/
- var previousSpanY = 0f
- private set
+ private var previousSpanY = 0f
/**
* The event time in milliseconds of the current event being processed.
@@ -206,8 +203,10 @@
*
* @param event The event to process.
* @return `true` if the event was processed and the detector wants to receive the
- * rest of the MotionEvents in this event stream.
+ * rest of the [MotionEvent]s in this event stream. Return it in the [View.onTouchEvent] for a
+ * normal use case.
*/
+ @UiThread
fun onTouchEvent(event: MotionEvent): Boolean {
eventTime = event.eventTime
@@ -230,9 +229,9 @@
if (action == MotionEvent.ACTION_DOWN || streamComplete) {
// Reset any scale in progress with the listener.
// If it's an ACTION_DOWN we're beginning a new event stream.
- // This means the app probably didn't give us all the events. Shame on it.
+ // This means the app probably didn't give us all the events.
if (isInProgress) {
- listener.onZoom(ZOOM_GESTURE_END, this)
+ listener.onZoomEvent(ZOOM_GESTURE_END, this)
isInProgress = false
initialSpan = 0f
anchoredZoomMode = ANCHORED_ZOOM_MODE_NONE
@@ -299,8 +298,8 @@
if (skipIndex == i) continue
// Convert the resulting diameter into a radius.
- devSumX += abs((event.getX(i) - focusX))
- devSumY += abs((event.getY(i) - focusY))
+ devSumX += abs(event.getX(i) - focusX)
+ devSumY += abs(event.getY(i) - focusY)
}
val devX = devSumX / div
val devY = devSumY / div
@@ -323,7 +322,7 @@
this.focusX = focusX
this.focusY = focusY
if (!inAnchoredZoomMode() && isInProgress && (span < minSpan || configChanged)) {
- listener.onZoom(ZOOM_GESTURE_END, this)
+ listener.onZoomEvent(ZOOM_GESTURE_END, this)
isInProgress = false
initialSpan = span
}
@@ -339,7 +338,7 @@
val minSpan = if (inAnchoredZoomMode()) spanSlop else minSpan
if (!isInProgress &&
span >= minSpan &&
- (wasInProgress || abs((span - initialSpan)) > spanSlop)) {
+ (wasInProgress || abs(span - initialSpan) > spanSlop)) {
currentSpanX = spanX
previousSpanX = currentSpanX
currentSpanY = spanY
@@ -347,7 +346,7 @@
currentSpan = span
previousSpan = currentSpan
prevTime = eventTime
- isInProgress = listener.onZoom(ZOOM_GESTURE_BEGIN, this)
+ isInProgress = listener.onZoomEvent(ZOOM_GESTURE_BEGIN, this)
}
// Handle motion; focal point and span/scale factor are changing.
@@ -359,7 +358,7 @@
var updatePrev = true
if (isInProgress) {
- updatePrev = listener.onZoom(ZOOM_GESTURE_MOVE, this)
+ updatePrev = listener.onZoomEvent(ZOOM_GESTURE_MOVE, this)
}
if (updatePrev) {
@@ -391,7 +390,7 @@
currentSpan < previousSpan ||
!eventBeforeOrAboveStartingGestureEvent &&
currentSpan > previousSpan
- val spanDiff = (abs((1 - currentSpan / previousSpan)) * SCALE_FACTOR)
+ val spanDiff = (abs(1 - currentSpan / previousSpan) * SCALE_FACTOR)
return if (previousSpan <= spanSlop) 1.0f
else if (scaleUp) 1.0f + spanDiff
else 1.0f - spanDiff
@@ -408,9 +407,12 @@
*/
get() = eventTime - prevTime
- companion object {
- private const val TAG = "ZoomGestureDetector"
+ @IntDef(ZOOM_GESTURE_MOVE, ZOOM_GESTURE_BEGIN, ZOOM_GESTURE_END)
+ @Retention(AnnotationRetention.SOURCE)
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ annotation class ZoomGesture
+ companion object {
/** The moving events of a gesture in progress. Reported by pointer motion. */
const val ZOOM_GESTURE_MOVE = 0
/** The beginning of a zoom gesture. Reported by new pointers going down. */
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceProvider.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceProvider.kt
index c98d532..46f8bdc 100644
--- a/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceProvider.kt
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceProvider.kt
@@ -27,11 +27,11 @@
*/
interface ViewfinderSurfaceProvider {
/**
- * Called when a new [Surface] has been requested by the camera.
+ * Called when a new [Surface] has been requested by the frame producer.
*
*
* This is called every time a new surface is required to keep the viewfinder running.
- * The camera may repeatedly request surfaces, but only a single request will be active at a
+ * The producer may repeatedly request surfaces, but only a single request will be active at a
* time.
*
* @param request the request for a surface which contains the requirements of the
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceRequest.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceRequest.kt
index 0d9ddf8..4e02c36 100644
--- a/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceRequest.kt
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceRequest.kt
@@ -17,13 +17,12 @@
package androidx.camera.viewfinder.surface
import android.annotation.SuppressLint
-import android.hardware.camera2.CameraMetadata
import android.util.Size
import android.view.Surface
import androidx.annotation.IntDef
import androidx.annotation.RestrictTo
import androidx.camera.impl.utils.Logger
-import androidx.camera.impl.utils.executor.CameraExecutors
+import androidx.camera.impl.utils.executor.ViewfinderExecutors
import androidx.camera.impl.utils.futures.FutureCallback
import androidx.camera.impl.utils.futures.Futures
import androidx.camera.viewfinder.impl.surface.DeferredSurface
@@ -39,29 +38,26 @@
import java.util.concurrent.atomic.AtomicReference
/**
- * The request to get a [Surface] to display camera feed.
+ * The request to get a [Surface] to display viewfinder input.
*
*
- * This request contains requirements for the surface resolution and camera
- * device information from [CameraCharacteristics].
+ * This request contains requirements for the surface resolution and viewfinder
+ * input and output information.
*
- * Calling [ViewfinderSurfaceRequest.markSurfaceSafeToRelease] will notify the
+ * Calling [ViewfinderSurfaceRequest.markSurfaceSafeToRelease] will notify the
* surface provider that the surface is not needed and related resources can be released.
*
- * Creates a new surface request with surface resolution, camera device, lens facing and
- * sensor orientation information.
+ * Creates a new surface request with surface resolution, viewfinder input and output information.
*
- * @param resolution The requested surface resolution. It is the output surface size
- * the camera is configured with, instead of {@link CameraViewfinder}
- * view size.
- * @param lensFacing The camera lens facing.
- * @param sensorOrientation THe camera sensor orientation.
+ * @param resolution The requested surface resolution.
+ * @param outputMirrorMode The viewfinder output mirror mode.
+ * @param sourceOrientation The viewfinder source orientation.
* @param implementationMode The {@link ImplementationMode} to apply to the viewfinder.
*/
class ViewfinderSurfaceRequest internal constructor(
val resolution: Size,
- @LensFacingValue val lensFacing: Int,
- @SensorOrientationDegreesValue val sensorOrientation: Int,
+ @OutputMirrorMode val outputMirrorMode: Int,
+ @SourceOrientationDegreesValue val sourceOrientation: Int,
val implementationMode: ImplementationMode?
) {
private val mInternalDeferredSurface: DeferredSurface
@@ -121,7 +117,7 @@
Preconditions.checkState(requestCancellationCompleter.set(null))
}
}
- }, CameraExecutors.directExecutor())
+ }, ViewfinderExecutors.directExecutor())
// Create the surface future/completer. This will be used to complete the rest of the
// future chain and can be set externally via SurfaceRequest methods.
@@ -136,7 +132,7 @@
surfaceCompleter = Preconditions.checkNotNull(surfaceCompleterRef.get())
// Create the viewfinder surface which will be used for communicating when the
- // camera and consumer are done using the surface. Note this anonymous inner class holds
+ // producer and consumer are done using the surface. Note this anonymous inner class holds
// an implicit reference to the ViewfinderSurfaceRequest. This is by design, and ensures the
// ViewfinderSurfaceRequest and all contained future completers will not be garbage
// collected as long as the ViewfinderSurface is referenced externally (via
@@ -181,12 +177,12 @@
sessionStatusCompleter.set(null)
}
}
- }, CameraExecutors.directExecutor())
+ }, ViewfinderExecutors.directExecutor())
// If the viewfinder surface is terminated, there are two cases:
- // 1. The surface has not yet been provided to the camera (or marked as 'will not
+ // 1. The surface has not yet been provided to the producer (or marked as 'will not
// complete'). Treat this as if the surface request has been cancelled.
- // 2. The surface was already provided to the camera. In this case the camera is now
+ // 2. The surface was already provided to the producer. In this case the producer is now
// finished with the surface, so cancelling the surface future below will be a no-op.
terminationFuture.addListener({
Logger.d(
@@ -195,7 +191,7 @@
"terminateFuture triggered")
)
surfaceFutureAsync.cancel(true)
- }, CameraExecutors.directExecutor())
+ }, ViewfinderExecutors.directExecutor())
}
/**
@@ -253,14 +249,14 @@
* completed or cancelled.
*
*
- * Once the camera no longer needs the provided surface, the `resultListener` will be
+ * Once the producer no longer needs the provided surface, the `resultListener` will be
* invoked with a [Result] containing [Result.RESULT_SURFACE_USED_SUCCESSFULLY].
* At this point it is safe to release the surface and any underlying resources. Releasing
* the surface before receiving this signal may cause undesired behavior on lower API levels.
*
*
- * If the request is cancelled by the camera before successfully attaching the
- * provided surface to the camera, then the `resultListener` will be invoked with a
+ * If the request is cancelled by the producer before successfully attaching the
+ * provided surface to the producer, then the `resultListener` will be invoked with a
* [Result] containing [Result.RESULT_REQUEST_CANCELLED].
*
*
@@ -272,12 +268,12 @@
* Upon returning from this method, the surface request is guaranteed to be complete.
* However, only the `resultListener` provided to the first invocation of this method
* should be used to track when the provided [Surface] is no longer in use by the
- * camera, as subsequent invocations will always invoke the `resultListener` with a
+ * producer, as subsequent invocations will always invoke the `resultListener` with a
* [Result] containing [Result.RESULT_SURFACE_ALREADY_PROVIDED].
*
* @param surface The surface which will complete the request.
* @param executor Executor used to execute the `resultListener`.
- * @param resultListener Listener used to track how the surface is used by the camera in
+ * @param resultListener Listener used to track how the surface is used by the producer in
* response to being provided by this method.
*/
fun provideSurface(
@@ -300,7 +296,7 @@
override fun onFailure(t: Throwable) {
Preconditions.checkState(
- t is RequestCancelledException, ("Camera " +
+ t is RequestCancelledException, ("Producer " +
"surface session should only fail with request " +
"cancellation. Instead failed due to:\n" + t)
)
@@ -351,21 +347,21 @@
* surface will never be produced to fulfill the request.
*
*
- * This will be called by CameraViewfinder as soon as it is known that the request will not
+ * This will be called by the producer as soon as it is known that the request will not
* be fulfilled. Failure to complete the SurfaceRequest via `willNotProvideSurface()`
* or [.provideSurface] may cause long delays in shutting
- * down the camera.
+ * down the producer.
*
*
* Upon returning from this method, the request is guaranteed to be complete, regardless
* of the return value. If the request was previously successfully completed by
* [.provideSurface], invoking this method will return
- * `false`, and will have no effect on how the surface is used by the camera.
+ * `false`, and will have no effect on how the surface is used by the producer.
*
* @return `true` if this call to `willNotProvideSurface()` successfully
* completes the request, i.e., the request has not already been completed via
* [.provideSurface] or by a previous call to
- * `willNotProvideSurface()` and has not already been cancelled by the camera.
+ * `willNotProvideSurface()` and has not already been cancelled by the producer.
*/
fun willNotProvideSurface(): Boolean {
return surfaceCompleter.setException(
@@ -381,11 +377,11 @@
class Builder {
private val resolution: Size
- @LensFacingValue
- private var lensFacing = CameraMetadata.LENS_FACING_BACK
+ @OutputMirrorMode
+ private var outputMirrorMode = MIRROR_MODE_NONE
- @SensorOrientationDegreesValue
- private var sensorOrientation = 0
+ @SourceOrientationDegreesValue
+ private var sourceOrientation = 0
private var implementationMode: ImplementationMode? = null
/**
@@ -412,8 +408,8 @@
constructor(builder: Builder) {
resolution = builder.resolution
implementationMode = builder.implementationMode
- lensFacing = builder.lensFacing
- sensorOrientation = builder.sensorOrientation
+ outputMirrorMode = builder.outputMirrorMode
+ sourceOrientation = builder.sourceOrientation
}
/**
@@ -429,8 +425,8 @@
constructor(surfaceRequest: ViewfinderSurfaceRequest) {
resolution = surfaceRequest.resolution
implementationMode = surfaceRequest.implementationMode
- lensFacing = surfaceRequest.lensFacing
- sensorOrientation = surfaceRequest.sensorOrientation
+ outputMirrorMode = surfaceRequest.outputMirrorMode
+ sourceOrientation = surfaceRequest.sourceOrientation
}
/**
@@ -451,26 +447,22 @@
}
/**
- * Sets the lens facing.
+ * Sets the output mirror mode.
*
*
* **Possible values:**
*
- * * [FRONT][CameraMetadata.LENS_FACING_FRONT]
- * * [BACK][CameraMetadata.LENS_FACING_BACK]
- * * [EXTERNAL][CameraMetadata.LENS_FACING_EXTERNAL]
+ * * [MIRROR_MODE_NONE][MIRROR_MODE_NONE]
+ * * [MIRROR_MODE_HORIZONTAL][MIRROR_MODE_HORIZONTAL]
*
*
+ * If not set, [MIRROR_MODE_NONE] will be used by default.
*
- * The value can be retrieved from [CameraCharacteristics] by key
- * [CameraCharacteristics.LENS_FACING]. If not set,
- * [CameraMetadata.LENS_FACING_BACK] will be used by default.
- *
- * @param lensFacing The lens facing.
+ * @param outputMirrorMode The viewfinder output mirror mode.
* @return This builder.
*/
- fun setLensFacing(@LensFacingValue lensFacing: Int): Builder {
- this.lensFacing = lensFacing
+ fun setOutputMirrorMode(@OutputMirrorMode outputMirrorMode: Int): Builder {
+ this.outputMirrorMode = outputMirrorMode
return this
}
@@ -482,15 +474,13 @@
* 0, 90, 180, 270
*
*
- * The value can be retrieved from [CameraCharacteristics] by key
- * [CameraCharacteristics.SENSOR_ORIENTATION]. If it is not
- * set, 0 will be used by default.
+ * If it is not set, 0 will be used by default.
*
- * @param sensorOrientation
- * @return this builder.
+ * @param sourceOrientation The viewfinder source orientation.
+ * @return This builder.
*/
- fun setSensorOrientation(@SensorOrientationDegreesValue sensorOrientation: Int): Builder {
- this.sensorOrientation = sensorOrientation
+ fun setSourceOrientation(@SourceOrientationDegreesValue sourceOrientation: Int): Builder {
+ this.sourceOrientation = sourceOrientation
return this
}
@@ -501,27 +491,26 @@
* @throws IllegalArgumentException
*/
fun build(): ViewfinderSurfaceRequest {
- if ((lensFacing != CameraMetadata.LENS_FACING_FRONT
- ) && (lensFacing != CameraMetadata.LENS_FACING_BACK
- ) && (lensFacing != CameraMetadata.LENS_FACING_EXTERNAL)
+ if ((outputMirrorMode != MIRROR_MODE_NONE
+ ) && (outputMirrorMode != MIRROR_MODE_HORIZONTAL)
) {
throw IllegalArgumentException(
- ("Lens facing value: $lensFacing is invalid")
+ ("Output mirror mode : $outputMirrorMode is invalid")
)
}
- if ((sensorOrientation != 0
- ) && (sensorOrientation != 90
- ) && (sensorOrientation != 180
- ) && (sensorOrientation != 270)
+ if ((sourceOrientation != 0
+ ) && (sourceOrientation != 90
+ ) && (sourceOrientation != 180
+ ) && (sourceOrientation != 270)
) {
throw IllegalArgumentException(
- ("Sensor orientation value: $sensorOrientation is invalid")
+ ("Source orientation value: $sourceOrientation is invalid")
)
}
return ViewfinderSurfaceRequest(
resolution,
- lensFacing,
- sensorOrientation,
+ outputMirrorMode,
+ sourceOrientation,
implementationMode
)
}
@@ -563,8 +552,8 @@
annotation class ResultCode()
companion object {
/**
- * Provided surface was successfully used by the camera and eventually detached once no
- * longer needed by the camera.
+ * Provided surface was successfully used by the producer and eventually detached once
+ * no longer needed by the producer.
*
*
* This result denotes that it is safe to release the [Surface] and any underlying
@@ -578,12 +567,12 @@
const val RESULT_SURFACE_USED_SUCCESSFULLY = 0
/**
- * Provided surface was never attached to the camera due to the
- * [ViewfinderSurfaceRequest] being cancelled by the camera.
+ * Provided surface was never attached to the producer due to the
+ * [ViewfinderSurfaceRequest] being cancelled by the producer.
*
*
* It is safe to release or reuse [Surface], assuming it was not previously
- * attached to a camera via [.provideSurface]. If
+ * attached to a producer via [.provideSurface]. If
* reusing the surface for a future surface request, it should be verified that the
* surface still matches the resolution specified by
* [ViewfinderSurfaceRequest.resolution].
@@ -591,7 +580,7 @@
const val RESULT_REQUEST_CANCELLED = 1
/**
- * Provided surface could not be used by the camera.
+ * Provided surface could not be used by the producer.
*
*
* This is likely due to the [Surface] being closed prematurely or the resolution
@@ -601,7 +590,7 @@
const val RESULT_INVALID_SURFACE = 2
/**
- * Surface was not attached to the camera through this invocation of
+ * Surface was not attached to the producer through this invocation of
* [.provideSurface] due to the
* [ViewfinderSurfaceRequest] already being complete with a surface.
*
@@ -611,12 +600,12 @@
*
*
* It is safe to release or reuse the [Surface], assuming it was not previously
- * attached to a camera via [.provideSurface].
+ * attached to a producer via [.provideSurface].
*/
const val RESULT_SURFACE_ALREADY_PROVIDED = 3
/**
- * Surface was not attached to the camera through this invocation of
+ * Surface was not attached to the producer through this invocation of
* [.provideSurface] due to the
* [ViewfinderSurfaceRequest] already being marked as "will not provide surface".
*
@@ -626,32 +615,43 @@
*
*
* It is safe to release or reuse the [Surface], assuming it was not previously
- * attached to a camera via [.provideSurface].
+ * attached to a producer via [.provideSurface].
*/
const val RESULT_WILL_NOT_PROVIDE_SURFACE = 4
}
}
/**
- * Valid integer sensor orientation degrees values.
+ * Valid integer source orientation degrees values.
*/
@IntDef(0, 90, 180, 270)
@Retention(AnnotationRetention.SOURCE)
- internal annotation class SensorOrientationDegreesValue()
+ private annotation class SourceOrientationDegreesValue
/**
- * Valid integer sensor orientation degrees values.
+ * Valid integer output mirror mode.
*/
@IntDef(
- CameraMetadata.LENS_FACING_FRONT,
- CameraMetadata.LENS_FACING_BACK,
- CameraMetadata.LENS_FACING_EXTERNAL
+ MIRROR_MODE_NONE,
+ MIRROR_MODE_HORIZONTAL
)
@Retention(
AnnotationRetention.SOURCE
)
- internal annotation class LensFacingValue()
+ private annotation class OutputMirrorMode
companion object {
private const val TAG = "SurfaceRequest"
+
+ /**
+ * No mirror transform needs to be applied to the viewfinder output.
+ */
+ const val MIRROR_MODE_NONE = 0
+
+ /**
+ * Horizontal mirror transform needs to be applied to the viewfinder output.
+ *
+ * The mirror transform should be applied in display coordinate.
+ */
+ const val MIRROR_MODE_HORIZONTAL = 1
}
}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceRequestExt.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceRequestExt.kt
index 945abdb..925435f 100644
--- a/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceRequestExt.kt
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/viewfinder/surface/ViewfinderSurfaceRequestExt.kt
@@ -21,6 +21,8 @@
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraMetadata
import androidx.annotation.RequiresApi
+import androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Companion.MIRROR_MODE_HORIZONTAL
+import androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Companion.MIRROR_MODE_NONE
/**
* Populates [ViewfinderSurfaceRequest.Builder] from [CameraCharacteristics].
@@ -35,8 +37,11 @@
fun ViewfinderSurfaceRequest.Builder.populateFromCharacteristics(
cameraCharacteristics: CameraCharacteristics
): ViewfinderSurfaceRequest.Builder {
- setLensFacing(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)!!)
- setSensorOrientation(
+ val lensFacing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)!!
+ val mirrorMode = if (lensFacing == CameraMetadata.LENS_FACING_FRONT)
+ MIRROR_MODE_HORIZONTAL else MIRROR_MODE_NONE
+ setOutputMirrorMode(mirrorMode)
+ setSourceOrientation(
cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!)
if (cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
== CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
diff --git a/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/TextureViewImplementationTest.kt b/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/TextureViewImplementationTest.kt
index c64255c..9663d5f 100644
--- a/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/TextureViewImplementationTest.kt
+++ b/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/TextureViewImplementationTest.kt
@@ -36,6 +36,7 @@
import org.junit.After
import org.junit.Assume
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -82,6 +83,7 @@
}
}
+ @Ignore // b/324125795
@LargeTest
@Test(expected = TimeoutException::class)
@Throws(
@@ -93,6 +95,7 @@
request.getSurfaceAsync()[2, TimeUnit.SECONDS]
}
+ @Ignore // b/324125795
@Test
@Throws(Exception::class)
fun provideSurface_ifSurfaceTextureAvailable() {
@@ -106,6 +109,7 @@
Truth.assertThat(surface).isNotNull()
}
+ @Ignore // b/324125795
@Test
@Throws(Exception::class)
fun doNotDestroySurface_whenSurfaceTextureBeingDestroyed_andCameraUsingSurface() {
@@ -123,6 +127,7 @@
).isFalse()
}
+ @Ignore // b/324125795
@Test
@LargeTest
@Throws(Exception::class)
@@ -145,6 +150,7 @@
).isTrue()
}
+ @Ignore // b/324125795
@Test
@LargeTest
@Throws(Exception::class)
@@ -166,6 +172,7 @@
}
}
+ @Ignore // b/324125795
@Test
@LargeTest
@Throws(Exception::class)
@@ -181,6 +188,7 @@
Truth.assertThat(implementation!!.mSurfaceReleaseFuture).isNull()
}
+ @Ignore // b/324125795
@Test
@LargeTest
@Throws(Exception::class)
@@ -200,11 +208,13 @@
Truth.assertThat(implementation!!.mSurfaceTexture).isNull()
}
+ @Ignore // b/324125795
@Test
fun doNotCreateTextureView_beforeSensorOutputSizeKnown() {
Truth.assertThat(parent!!.childCount).isEqualTo(0)
}
+ @Ignore // b/324125795
@Test
@Throws(Exception::class)
fun resetSurfaceTextureOnDetachAndAttachWindow() {
@@ -222,6 +232,7 @@
Truth.assertThat(implementation!!.mTextureView?.surfaceTexture).isEqualTo(surfaceTexture)
}
+ @Ignore // b/324125795
@Test
@LargeTest
@Throws(Exception::class)
@@ -245,6 +256,7 @@
Truth.assertThat(implementation!!.mDetachedSurfaceTexture).isNull()
}
+ @Ignore // b/324125795
@Test
fun keepOnlyLatestTextureView_whenGetSurfaceProviderCalledMultipleTimes() {
implementation!!.onSurfaceRequested(surfaceRequest)
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinder.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinder.java
index 40658f0..03cb83c 100644
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinder.java
+++ b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinder.java
@@ -17,6 +17,7 @@
package androidx.camera.viewfinder;
import static androidx.camera.viewfinder.internal.utils.TransformUtils.createTransformInfo;
+import static androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.MIRROR_MODE_HORIZONTAL;
import android.content.Context;
import android.content.res.TypedArray;
@@ -137,12 +138,12 @@
mViewfinderTransformation.setTransformationInfo(
createTransformInfo(surfaceRequest.getResolution(),
display,
- surfaceRequest.getLensFacing()
- == CameraCharacteristics.LENS_FACING_FRONT,
- surfaceRequest.getSensorOrientation()),
+ surfaceRequest.getOutputMirrorMode()
+ == MIRROR_MODE_HORIZONTAL,
+ surfaceRequest.getSourceOrientation()),
surfaceRequest.getResolution(),
- surfaceRequest.getLensFacing()
- == CameraCharacteristics.LENS_FACING_FRONT);
+ surfaceRequest.getOutputMirrorMode()
+ == MIRROR_MODE_HORIZONTAL);
redrawViewfinder();
}
}
@@ -699,9 +700,9 @@
mViewfinderTransformation.updateTransformInfo(
createTransformInfo(surfaceRequest.getResolution(),
display,
- surfaceRequest.getLensFacing()
- == CameraCharacteristics.LENS_FACING_FRONT,
- surfaceRequest.getSensorOrientation()));
+ surfaceRequest.getOutputMirrorMode()
+ == MIRROR_MODE_HORIZONTAL,
+ surfaceRequest.getSourceOrientation()));
redrawViewfinder();
}
}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/TextureViewImplementation.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/TextureViewImplementation.java
index 1c81fc9..8ed27d3 100644
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/TextureViewImplementation.java
+++ b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/TextureViewImplementation.java
@@ -26,7 +26,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.camera.impl.utils.executor.CameraExecutors;
+import androidx.camera.impl.utils.executor.ViewfinderExecutors;
import androidx.camera.impl.utils.futures.FutureCallback;
import androidx.camera.impl.utils.futures.Futures;
import androidx.camera.viewfinder.internal.utils.Logger;
@@ -224,7 +224,7 @@
completer -> {
Logger.d(TAG, "Surface set on viewfinder.");
mSurfaceRequest.provideSurface(surface,
- CameraExecutors.directExecutor(), new Consumer<Result>() {
+ ViewfinderExecutors.directExecutor(), new Consumer<Result>() {
@Override
public void accept(Result result) {
Logger.d(TAG, "provide surface result = "
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java
index c1b75ca..27c484a 100644
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java
+++ b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java
@@ -20,6 +20,9 @@
import static android.hardware.camera2.CameraMetadata.LENS_FACING_EXTERNAL;
import static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT;
+import static androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.MIRROR_MODE_HORIZONTAL;
+import static androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.MIRROR_MODE_NONE;
+
import android.annotation.SuppressLint;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCharacteristics;
@@ -109,7 +112,7 @@
*/
@SensorOrientationDegreesValue
public int getSensorOrientation() {
- return mViewfinderSurfaceRequest.getSensorOrientation();
+ return mViewfinderSurfaceRequest.getSourceOrientation();
}
/**
@@ -122,7 +125,8 @@
*/
@LensFacingValue
public int getLensFacing() {
- return mViewfinderSurfaceRequest.getLensFacing();
+ return mViewfinderSurfaceRequest.getOutputMirrorMode() == MIRROR_MODE_HORIZONTAL
+ ? LENS_FACING_FRONT : LENS_FACING_BACK;
}
/**
@@ -211,8 +215,9 @@
public Builder(@NonNull ViewfinderSurfaceRequest surfaceRequest) {
mBuilder = new androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest.Builder(
surfaceRequest.getResolution());
- mBuilder.setSensorOrientation(surfaceRequest.getSensorOrientation());
- mBuilder.setLensFacing(surfaceRequest.getLensFacing());
+ mBuilder.setSourceOrientation(surfaceRequest.getSensorOrientation());
+ mBuilder.setOutputMirrorMode(surfaceRequest.getLensFacing() == LENS_FACING_FRONT
+ ? MIRROR_MODE_HORIZONTAL : MIRROR_MODE_NONE);
mBuilder.setImplementationMode(
androidx.camera.viewfinder.surface.ImplementationMode.fromId(
surfaceRequest.getImplementationMode().getId()));
@@ -271,7 +276,8 @@
*/
@NonNull
public Builder setLensFacing(@LensFacingValue int lensFacing) {
- mBuilder.setLensFacing(lensFacing);
+ mBuilder.setOutputMirrorMode(lensFacing == LENS_FACING_FRONT ? MIRROR_MODE_HORIZONTAL :
+ MIRROR_MODE_NONE);
return this;
}
@@ -285,12 +291,12 @@
* {@link CameraCharacteristics#SENSOR_ORIENTATION}. If it is not
* set, 0 will be used by default.
*
- * @param sensorOrientation
+ * @param sensorOrientation The camera sensor orientation.
* @return this builder.
*/
@NonNull
public Builder setSensorOrientation(@SensorOrientationDegreesValue int sensorOrientation) {
- mBuilder.setSensorOrientation(sensorOrientation);
+ mBuilder.setSourceOrientation(sensorOrientation);
return this;
}
diff --git a/camera/integration-tests/coretestapp/build.gradle b/camera/integration-tests/coretestapp/build.gradle
index 4dda00a..7e12083 100644
--- a/camera/integration-tests/coretestapp/build.gradle
+++ b/camera/integration-tests/coretestapp/build.gradle
@@ -65,6 +65,7 @@
implementation(project(":camera:camera-mlkit-vision"))
implementation(project(":camera:camera-view"))
implementation(project(":camera:camera-video"))
+ implementation(project(":camera:camera-viewfinder-core"))
// Needed because AGP enforces same version between main and androidTest classpaths
implementation(project(":concurrent:concurrent-futures"))
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 f118200..f6dac7d 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
@@ -295,6 +295,10 @@
@Test
fun canCaptureImageWithFlashModeScreen_frontCamera() {
+ assumeTrue(
+ "TODO: b/325899701 - Enable when camera-pipe has extensions support",
+ implName != CameraPipeConfig::class.simpleName
+ )
// Front camera usually doesn't have a flash unit. Screen flash will be used in such case.
// Otherwise, physical flash will be used. But capture should be successful either way.
canTakeImages(
@@ -308,6 +312,10 @@
@Test
fun canCaptureImageWithFlashModeScreenAndUseTorch_frontCamera() {
+ assumeTrue(
+ "TODO: b/325899701 - Enable when camera-pipe has extensions support",
+ implName != CameraPipeConfig::class.simpleName
+ )
// Front camera usually doesn't have a flash unit. Screen flash will be used in such case.
// Otherwise, physical flash will be used as torch. Either way, capture should be successful
canTakeImages(
@@ -821,7 +829,8 @@
val cameraSelector =
getCameraSelectorWithSessionProcessor(BACK_SELECTOR, sessionProcessor)
cameraProvider.bindToLifecycle(
- fakeLifecycleOwner, cameraSelector, imageCapture, preview)
+ fakeLifecycleOwner, cameraSelector, imageCapture, preview
+ )
}
}
@@ -1462,7 +1471,8 @@
val callback = FakeImageSavedCallback(capturesCount = 1)
useCase.takePicture(
ImageCapture.OutputFileOptions.Builder(saveLocation).build(),
- mainExecutor, callback)
+ mainExecutor, callback
+ )
// Wait for the signal that the image has been captured and saved.
callback.awaitCapturesAndAssert(savedImagesCount = 1)
@@ -1470,7 +1480,8 @@
// For YUV to JPEG case, the rotation will only be in Exif.
val exif = Exif.createFromFile(saveLocation)
assertThat(exif.rotation).isEqualTo(
- camera.cameraInfo.getSensorRotationDegrees(useCase.targetRotation))
+ camera.cameraInfo.getSensorRotationDegrees(useCase.targetRotation)
+ )
}
@Test
@@ -1666,7 +1677,7 @@
override fun getSessionProcessor(
valueIfMissing: SessionProcessor?
- ): SessionProcessor? {
+ ): SessionProcessor {
return sessionProcessor
}
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index f62d12f..c5144e2 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -68,7 +68,6 @@
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
@@ -138,6 +137,7 @@
import androidx.camera.video.VideoCapture;
import androidx.camera.video.VideoRecordEvent;
import androidx.camera.view.ScreenFlashView;
+import androidx.camera.viewfinder.core.ZoomGestureDetector;
import androidx.core.content.ContextCompat;
import androidx.core.math.MathUtils;
import androidx.core.util.Consumer;
@@ -1970,22 +1970,16 @@
mZoomRatioLabel.setTextColor(getResources().getColor(R.color.zoom_ratio_set));
}
- ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestureListener =
- new ScaleGestureDetector.SimpleOnScaleGestureListener() {
- @Override
- public boolean onScale(@NonNull ScaleGestureDetector detector) {
- if (mCamera == null) {
- return true;
- }
-
- CameraInfo cameraInfo = mCamera.getCameraInfo();
- float newZoom =
- requireNonNull(cameraInfo.getZoomState().getValue()).getZoomRatio()
+ ZoomGestureDetector.OnZoomGestureListener mZoomGestureListener = (type, detector) -> {
+ if (mCamera != null && type == ZoomGestureDetector.ZOOM_GESTURE_MOVE) {
+ CameraInfo cameraInfo = mCamera.getCameraInfo();
+ float newZoom =
+ requireNonNull(cameraInfo.getZoomState().getValue()).getZoomRatio()
* detector.getScaleFactor();
- setZoomRatio(newZoom);
- return true;
- }
- };
+ setZoomRatio(newZoom);
+ }
+ return true;
+ };
GestureDetector.OnGestureListener onTapGestureListener =
new GestureDetector.SimpleOnGestureListener() {
@@ -2131,7 +2125,7 @@
private void setupViewFinderGestureControls() {
GestureDetector tapGestureDetector = new GestureDetector(this, onTapGestureListener);
- ScaleGestureDetector scaleDetector = new ScaleGestureDetector(this, mScaleGestureListener);
+ ZoomGestureDetector scaleDetector = new ZoomGestureDetector(this, mZoomGestureListener);
mViewFinder.setOnTouchListener((view, e) -> {
boolean tapEventProcessed = tapGestureDetector.onTouchEvent(e);
boolean scaleEventProcessed = scaleDetector.onTouchEvent(e);
diff --git a/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/PreviewTest.kt b/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/PreviewTest.kt
index cfaa448..39e05db 100644
--- a/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/PreviewTest.kt
+++ b/camera/integration-tests/coretestapp/src/test/java/androidx/camera/integration/core/PreviewTest.kt
@@ -106,7 +106,7 @@
}
repeat(5) {
- camera.simulateCaptureFrame()
+ camera.simulateCaptureFrameAsync().get(3, TimeUnit.SECONDS)
}
assertThat(countDownLatch.await(3, TimeUnit.SECONDS)).isTrue()
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidation.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidation.kt
index 7db2724..bf30957 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidation.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidation.kt
@@ -32,7 +32,8 @@
import android.view.Surface
import androidx.annotation.RequiresApi
import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat
-import androidx.camera.camera2.interop.Camera2CameraInfo
+import androidx.camera.core.CameraXConfig
+import androidx.camera.core.impl.CameraInfoInternal
import androidx.camera.core.impl.utils.AspectRatioUtil
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.core.internal.utils.SizeUtil
@@ -46,6 +47,7 @@
import androidx.camera.extensions.impl.advanced.OutputSurfaceImpl
import androidx.camera.extensions.impl.advanced.SurfaceOutputConfigImpl
import androidx.camera.extensions.internal.ExtensionVersion
+import androidx.camera.extensions.internal.ExtensionsUtils
import androidx.camera.extensions.internal.Version
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.getImageCaptureSupportedResolutions
@@ -64,6 +66,7 @@
@RequiresApi(28)
class AdvancedExtenderValidation(
+ private val cameraXConfig: CameraXConfig,
private val cameraId: String,
private val extensionMode: Int
) {
@@ -74,6 +77,7 @@
private lateinit var advancedImpl: AdvancedExtenderImpl
fun setUp(): Unit = runBlocking {
+ ProcessCameraProvider.configureInstance(cameraXConfig)
cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
extensionsManager = ExtensionsManager.getInstanceAsync(
@@ -90,7 +94,8 @@
val cameraInfo = withContext(Dispatchers.Main) {
cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector).cameraInfo
}
- cameraCharacteristicsMap = Camera2CameraInfo.from(cameraInfo).cameraCharacteristicsMap
+ cameraCharacteristicsMap =
+ ExtensionsUtils.getCameraCharacteristicsMap(cameraInfo as CameraInfoInternal)
advancedImpl = CameraXExtensionsTestUtil
.createAdvancedExtenderImpl(extensionMode, cameraId, cameraInfo)
}
@@ -262,9 +267,11 @@
SizeCategory.MAXIMUM -> {
sortedList[0]
}
+
SizeCategory.MEDIAN -> {
sortedList[sortedList.size / 2]
}
+
SizeCategory.MINIMUM -> {
sortedList[sortedList.size - 1]
}
@@ -526,10 +533,6 @@
deferred.complete(cameraDevice)
}
- override fun onClosed(camera: CameraDevice) {
- super.onClosed(camera)
- }
-
override fun onDisconnected(cameraDevice: CameraDevice) {
deferred.completeExceptionally(RuntimeException("Camera Disconnected"))
}
@@ -576,14 +579,6 @@
override fun onCaptureQueueEmpty(session: CameraCaptureSession) {
}
-
- override fun onClosed(session: CameraCaptureSession) {
- super.onClosed(session)
- }
-
- override fun onSurfacePrepared(session: CameraCaptureSession, surface: Surface) {
- super.onSurfacePrepared(session, surface)
- }
}
)
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidationTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidationTest.kt
index 8855463..e655ea5 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidationTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidationTest.kt
@@ -16,9 +16,10 @@
package androidx.camera.integration.extensions
-import androidx.camera.camera2.Camera2Config
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
+import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.CameraXExtensionTestParams
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
import androidx.camera.testing.impl.CameraUtil
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
@@ -40,19 +41,26 @@
@LargeTest
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = 28)
-class AdvancedExtenderValidationTest(config: CameraIdExtensionModePair) {
- private val validation = AdvancedExtenderValidation(config.cameraId, config.extensionMode)
+class AdvancedExtenderValidationTest(config: CameraXExtensionTestParams) {
+ private val validation = AdvancedExtenderValidation(
+ config.cameraXConfig, config.cameraId, config.extensionMode
+ )
companion object {
@JvmStatic
@get:Parameterized.Parameters(name = "config = {0}")
- val parameters: Collection<CameraIdExtensionModePair>
+ val parameters: Collection<CameraXExtensionTestParams>
get() = CameraXExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
}
@get:Rule
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = config.implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+ )
+
+ @get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
+ CameraUtil.PreTestCameraIdList(config.cameraXConfig)
)
@Before
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/BindUnbindUseCasesStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/BindUnbindUseCasesStressTest.kt
index 88a6699..3027a7a 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/BindUnbindUseCasesStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/BindUnbindUseCasesStressTest.kt
@@ -21,22 +21,20 @@
import android.os.Handler
import android.os.HandlerThread
import android.util.Size
-import androidx.camera.camera2.Camera2Config
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
-import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
-import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.extensions.ExtensionsManager
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
-import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.VERIFICATION_TARGET_IMAGE_ANALYSIS
+import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.CameraXExtensionTestParams
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.VERIFICATION_TARGET_IMAGE_CAPTURE
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.VERIFICATION_TARGET_PREVIEW
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
import androidx.camera.integration.extensions.utils.CameraSelectorUtil
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.GLUtil
@@ -68,10 +66,15 @@
@LargeTest
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = 21)
-class BindUnbindUseCasesStressTest(private val config: CameraIdExtensionModePair) {
+class BindUnbindUseCasesStressTest(private val config: CameraXExtensionTestParams) {
+ @get:Rule
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = config.implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+ )
+
@get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- PreTestCameraIdList(Camera2Config.defaultConfig())
+ PreTestCameraIdList(config.cameraXConfig)
)
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -88,13 +91,14 @@
@Before
fun setUp(): Unit = runBlocking {
assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
+ val (_, cameraXConfig, cameraId, extensionMode) = config
+ ProcessCameraProvider.configureInstance(cameraXConfig)
cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
extensionsManager = ExtensionsManager.getInstanceAsync(
context,
cameraProvider
)[10000, TimeUnit.MILLISECONDS]
- val (cameraId, extensionMode) = config
baseCameraSelector = CameraSelectorUtil.createCameraSelectorById(cameraId)
assumeTrue(extensionsManager.isExtensionAvailable(baseCameraSelector, extensionMode))
@@ -128,11 +132,12 @@
companion object {
@ClassRule
- @JvmField val stressTest = StressTestRule()
+ @JvmField
+ val stressTest = StressTestRule()
@JvmStatic
@get:Parameterized.Parameters(name = "config = {0}")
- val parameters: Collection<CameraIdExtensionModePair>
+ val parameters: Collection<CameraXExtensionTestParams>
get() = CameraXExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
}
@@ -155,62 +160,14 @@
)
}
- @Test
- fun bindUnbindUseCases_checkPreviewInEachTime_withPreviewImageCaptureImageAnalysis():
- Unit = runBlocking {
- assumeTrue(extensionsManager.isImageAnalysisSupported(
- baseCameraSelector, config.extensionMode))
- val imageAnalysis = createImageAnalysis()
- assumeTrue(camera.isUseCasesCombinationSupported(preview, imageCapture, imageAnalysis))
- bindUseCases_checkOutput_thenUnbindAll_repeatedly(
- preview,
- imageCapture,
- imageAnalysis,
- verificationTarget = VERIFICATION_TARGET_PREVIEW
- )
- }
-
- @Test
- fun bindUnbindUseCases_checkImageCaptureInEachTime_withPreviewImageCaptureImageAnalysis():
- Unit = runBlocking {
- assumeTrue(extensionsManager.isImageAnalysisSupported(
- baseCameraSelector, config.extensionMode))
- val imageAnalysis = createImageAnalysis()
- assumeTrue(camera.isUseCasesCombinationSupported(preview, imageCapture, imageAnalysis))
- bindUseCases_checkOutput_thenUnbindAll_repeatedly(
- preview,
- imageCapture,
- imageAnalysis,
- verificationTarget = VERIFICATION_TARGET_IMAGE_CAPTURE
- )
- }
-
- @Test
- fun bindUnbindUseCases_checkImageAnalysisInEachTime_withPreviewImageCaptureImageAnalysis():
- Unit = runBlocking {
- assumeTrue(extensionsManager.isImageAnalysisSupported(
- baseCameraSelector, config.extensionMode))
- val imageAnalysis = createImageAnalysis()
- assumeTrue(camera.isUseCasesCombinationSupported(preview, imageCapture, imageAnalysis))
- bindUseCases_checkOutput_thenUnbindAll_repeatedly(
- preview,
- imageCapture,
- imageAnalysis,
- verificationTarget = VERIFICATION_TARGET_IMAGE_ANALYSIS
- )
- }
-
/**
* Repeatedly binds use cases, checks the input use cases' capture functions can work well, and
* unbind all use cases.
*
- * <p>This function checks the nullability of the input ImageAnalysis to determine whether it
- * will be bound together to run the test.
*/
private fun bindUseCases_checkOutput_thenUnbindAll_repeatedly(
preview: Preview,
imageCapture: ImageCapture,
- imageAnalysis: ImageAnalysis? = null,
verificationTarget: Int,
repeatCount: Int = CameraXExtensionsTestUtil.getStressTestRepeatingCount()
): Unit = runBlocking {
@@ -230,7 +187,7 @@
cameraProvider.bindToLifecycle(
lifecycleOwner,
extensionCameraSelector,
- *listOfNotNull(preview, imageCapture, imageAnalysis).toTypedArray()
+ *listOfNotNull(preview, imageCapture).toTypedArray()
)
}
@@ -251,17 +208,6 @@
imageCaptureCaptureSuccessMonitor.awaitCaptureSuccessAndAssert()
}
- // Assert: checks that images can be received by the ImageAnalysis.Analyzer
- if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_ANALYSIS) != 0) {
- imageAnalysis!!.let {
- val analyzerFrameAvailableMonitor = ImageAnalysisImageAvailableMonitor()
- it.setAnalyzer(
- Executors.newSingleThreadExecutor(),
- analyzerFrameAvailableMonitor.createAnalyzer()
- )
- }
- }
-
// Clean it up.
withContext(Dispatchers.Main) {
cameraProvider.unbindAll()
@@ -289,62 +235,14 @@
)
}
- @Test
- fun checkPreview_afterBindUnbindUseCasesRepeatedly_withPreviewImageCaptureImageAnalysis():
- Unit = runBlocking {
- assumeTrue(extensionsManager.isImageAnalysisSupported(
- baseCameraSelector, config.extensionMode))
- val imageAnalysis = createImageAnalysis()
- assumeTrue(camera.isUseCasesCombinationSupported(preview, imageCapture, imageAnalysis))
- bindUseCases_unbindAll_repeatedly_thenCheckOutput(
- preview,
- imageCapture,
- imageAnalysis,
- verificationTarget = VERIFICATION_TARGET_PREVIEW
- )
- }
-
- @Test
- fun checkImageCapture_afterBindUnbindUseCasesRepeatedly_withPreviewImageCaptureImageAnalysis():
- Unit = runBlocking {
- assumeTrue(extensionsManager.isImageAnalysisSupported(
- baseCameraSelector, config.extensionMode))
- val imageAnalysis = createImageAnalysis()
- assumeTrue(camera.isUseCasesCombinationSupported(preview, imageCapture, imageAnalysis))
- bindUseCases_unbindAll_repeatedly_thenCheckOutput(
- preview,
- imageCapture,
- imageAnalysis,
- verificationTarget = VERIFICATION_TARGET_IMAGE_CAPTURE
- )
- }
-
- @Test
- fun checkImageAnalysis_afterBindUnbindUseCasesRepeatedly_withPreviewImageCaptureImageAnalysis():
- Unit = runBlocking {
- assumeTrue(extensionsManager.isImageAnalysisSupported(
- baseCameraSelector, config.extensionMode))
- val imageAnalysis = createImageAnalysis()
- assumeTrue(camera.isUseCasesCombinationSupported(preview, imageCapture, imageAnalysis))
- bindUseCases_unbindAll_repeatedly_thenCheckOutput(
- preview,
- imageCapture,
- imageAnalysis,
- verificationTarget = VERIFICATION_TARGET_IMAGE_ANALYSIS
- )
- }
-
/**
* Repeatedly binds use cases and unbind all, then checks the input use cases' capture
* functions can work well.
*
- * <p>This function checks the nullability of the input ImageAnalysis to determine whether it
- * will be bound together to run the test.
*/
private fun bindUseCases_unbindAll_repeatedly_thenCheckOutput(
preview: Preview,
imageCapture: ImageCapture,
- imageAnalysis: ImageAnalysis? = null,
verificationTarget: Int,
repeatCount: Int = CameraXExtensionsTestUtil.getStressTestRepeatingCount()
): Unit = runBlocking {
@@ -366,7 +264,7 @@
cameraProvider.bindToLifecycle(
lifecycleOwner,
extensionCameraSelector,
- *listOfNotNull(preview, imageCapture, imageAnalysis).toTypedArray()
+ *listOfNotNull(preview, imageCapture).toTypedArray()
)
// Clean it up: do not unbind at the last time
@@ -392,26 +290,8 @@
// Assert: checks that the captured image of ImageCapture can be received
imageCaptureCaptureSuccessMonitor.awaitCaptureSuccessAndAssert()
}
-
- if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_ANALYSIS) != 0) {
- imageAnalysis!!.let {
- val analyzerFrameAvailableMonitor = ImageAnalysisImageAvailableMonitor()
- it.setAnalyzer(
- Executors.newSingleThreadExecutor(),
- analyzerFrameAvailableMonitor.createAnalyzer()
- )
-
- // Assert: checks that images can be received by the ImageAnalysis.Analyzer
- analyzerFrameAvailableMonitor.awaitAvailableFramesAndAssert()
- }
- }
}
- private fun createImageAnalysis() =
- ImageAnalysis.Builder().build().also {
- it.setAnalyzer(CameraXExecutors.directExecutor()) { image -> image.close() }
- }
-
private class PreviewFrameAvailableMonitor {
private var isSurfaceTextureReleased = false
private val isSurfaceTextureReleasedLock = Any()
@@ -513,23 +393,4 @@
).isTrue()
}
}
-
- private class ImageAnalysisImageAvailableMonitor {
- private var analyzerFrameCountDownLatch: CountDownLatch? = null
-
- fun createAnalyzer() = ImageAnalysis.Analyzer { image ->
- image.close()
- analyzerFrameCountDownLatch?.countDown()
- }
-
- fun awaitAvailableFramesAndAssert(count: Int = 10, timeoutDurationMs: Long = 3000) {
- analyzerFrameCountDownLatch = CountDownLatch(count)
- assertThat(
- analyzerFrameCountDownLatch!!.await(
- timeoutDurationMs,
- TimeUnit.MILLISECONDS
- )
- ).isTrue()
- }
- }
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ClientVersionBackwardCompatibilityTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ClientVersionBackwardCompatibilityTest.kt
index 2031286..6803cbd 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ClientVersionBackwardCompatibilityTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ClientVersionBackwardCompatibilityTest.kt
@@ -17,7 +17,6 @@
package androidx.camera.integration.extensions
import android.content.Context
-import androidx.camera.camera2.Camera2Config
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.OnImageCapturedCallback
@@ -25,10 +24,12 @@
import androidx.camera.core.Preview
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.extensions.ExtensionsManager
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
+import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.CameraXExtensionTestParams
import androidx.camera.integration.extensions.utils.CameraSelectorUtil
import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
import androidx.camera.testing.impl.CameraUtil
import androidx.camera.testing.impl.SurfaceTextureProvider
import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
@@ -66,17 +67,22 @@
@LargeTest
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = 21)
-class ClientVersionBackwardCompatibilityTest(private val config: CameraIdExtensionModePair) {
+class ClientVersionBackwardCompatibilityTest(private val config: CameraXExtensionTestParams) {
companion object {
@JvmStatic
@get:Parameterized.Parameters(name = "config = {0}")
- val parameters: Collection<CameraIdExtensionModePair>
+ val parameters: Collection<CameraXExtensionTestParams>
get() = CameraXExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
}
@get:Rule
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = config.implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+ )
+
+ @get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
+ CameraUtil.PreTestCameraIdList(config.cameraXConfig)
)
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -89,6 +95,7 @@
@Before
fun setUp(): Unit = runBlocking(Dispatchers.Main) {
+ ProcessCameraProvider.configureInstance(config.cameraXConfig)
cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
lifecycleOwner = FakeLifecycleOwner()
lifecycleOwner.startAndResume()
@@ -173,6 +180,7 @@
fun previewImageCaptureWork_clientVersion_1_0_0() = runBlocking {
assertPreviewAndImageCaptureWorking(clientVersion = "1.0.0")
}
+
@Test
fun previewImageCaptureWork_clientVersion_1_1_0() = runBlocking {
assertPreviewAndImageCaptureWorking(clientVersion = "1.1.0")
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageAnalysisTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageAnalysisTest.kt
deleted file mode 100644
index 8814aec..0000000
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageAnalysisTest.kt
+++ /dev/null
@@ -1,176 +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.camera.integration.extensions
-
-import android.content.Context
-import androidx.camera.camera2.Camera2Config
-import androidx.camera.core.CameraSelector
-import androidx.camera.core.ImageAnalysis
-import androidx.camera.core.ImageCapture
-import androidx.camera.core.ImageCaptureException
-import androidx.camera.core.ImageProxy
-import androidx.camera.core.Preview
-import androidx.camera.core.impl.utils.executor.CameraXExecutors
-import androidx.camera.extensions.ExtensionsManager
-import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
-import androidx.camera.integration.extensions.utils.CameraSelectorUtil
-import androidx.camera.lifecycle.ProcessCameraProvider
-import androidx.camera.testing.impl.CameraUtil
-import androidx.camera.testing.impl.SurfaceTextureProvider
-import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.TimeUnit.SECONDS
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-import kotlinx.coroutines.withTimeoutOrNull
-import org.junit.After
-import org.junit.Assume
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-/**
- * ImageAnalysisTest is to verify if CameraX ImageAnalysis can work properly. The tests will be
- * ignored if OEM doesn't support ImageAnalysis.
- *
- * For advanced extender implementation, ImageAnalysis is supported if
- * AdvancedExtenderImpl#getSupportedYuvAnalysisResolutions returns an non-empty list.). For
- * basic extender implementation, ImageAnalysis is supported if the hardware level of the camera
- * supports the stream configuration determined by the capture processor and the preview processor.
- * For example, if both CaptureProcessor is enabled and the preview's processorType is
- * PROCESSOR_TYPE_IMAGE_PROCESSOR, then the stream configuration is YUV+YUV+YUV which requires
- * hardware level FULL or above to support it.
- */
-@LargeTest
-@RunWith(Parameterized::class)
-@SdkSuppress(minSdkVersion = 21)
-class ImageAnalysisTest(private val config: CameraIdExtensionModePair) {
- companion object {
- @JvmStatic
- @get:Parameterized.Parameters(name = "config = {0}")
- val parameters: Collection<CameraIdExtensionModePair>
- get() = CameraXExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
- }
-
- @get:Rule
- val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
- )
-
- private val context = ApplicationProvider.getApplicationContext<Context>()
- private lateinit var cameraProvider: ProcessCameraProvider
- private lateinit var extensionsManager: ExtensionsManager
- private lateinit var baseCameraSelector: CameraSelector
- private lateinit var extensionCameraSelector: CameraSelector
- private lateinit var lifecycleOwner: FakeLifecycleOwner
-
- @Before
- fun setUp(): Unit = runBlocking(Dispatchers.Main) {
- cameraProvider = ProcessCameraProvider.getInstance(context)[10, SECONDS]
- lifecycleOwner = FakeLifecycleOwner()
- lifecycleOwner.startAndResume()
- baseCameraSelector = CameraSelectorUtil.createCameraSelectorById(config.cameraId)
- extensionsManager = ExtensionsManager.getInstanceAsync(
- context,
- cameraProvider
- )[10000, TimeUnit.MILLISECONDS]
- Assume.assumeTrue(
- extensionsManager.isExtensionAvailable(
- baseCameraSelector,
- config.extensionMode
- )
- )
- Assume.assumeTrue(
- extensionsManager.isImageAnalysisSupported(
- baseCameraSelector, config.extensionMode
- )
- )
- }
-
- @After
- fun tearDown() = runBlocking(Dispatchers.Main) {
- if (::cameraProvider.isInitialized) {
- cameraProvider.shutdownAsync()[10, SECONDS]
- }
-
- if (::extensionsManager.isInitialized) {
- extensionsManager.shutdown()[10, SECONDS]
- }
- }
-
- @Test
- fun imageAnalysisPreviewImageCaptureCanProduceOutput() = runBlocking {
- extensionCameraSelector = extensionsManager
- .getExtensionEnabledCameraSelector(baseCameraSelector, config.extensionMode)
-
- val previewFrameDeferred = CompletableDeferred<Boolean>()
- val captureDeferred = CompletableDeferred<Boolean>()
- val imageAnalysisDeferred = CompletableDeferred<Boolean>()
-
- val preview = Preview.Builder().build()
- val imageCapture = ImageCapture.Builder().build()
- val imageAnalysis = ImageAnalysis.Builder().build()
- imageAnalysis.setAnalyzer(CameraXExecutors.ioExecutor()) {
- it.close()
- imageAnalysisDeferred.complete(true)
- }
-
- withContext(Dispatchers.Main) {
- preview.setSurfaceProvider(
- SurfaceTextureProvider.createAutoDrainingSurfaceTextureProvider {
- previewFrameDeferred.complete(true)
- }
- )
- cameraProvider.bindToLifecycle(
- lifecycleOwner, extensionCameraSelector,
- preview, imageCapture, imageAnalysis
- )
- }
-
- Truth.assertThat(
- withTimeoutOrNull(SECONDS.toMillis(3L)) { previewFrameDeferred.await() } ?: false)
- .isTrue()
-
- Truth.assertThat(
- withTimeoutOrNull(SECONDS.toMillis(3L)) { imageAnalysisDeferred.await() } ?: false)
- .isTrue()
-
- imageCapture.takePicture(
- CameraXExecutors.ioExecutor(),
- object : ImageCapture.OnImageCapturedCallback() {
- override fun onCaptureSuccess(image: ImageProxy) {
- captureDeferred.complete(true)
- }
-
- override fun onError(exception: ImageCaptureException) {
- captureDeferred.completeExceptionally(exception)
- }
- })
- Truth.assertThat(
- withTimeoutOrNull(SECONDS.toMillis(10L)) { captureDeferred.await() } ?: false)
- .isTrue()
- }
-}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt
index 9c4675c..5ff3195 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt
@@ -21,7 +21,6 @@
import android.hardware.camera2.CameraCharacteristics
import android.os.Build
import android.util.Rational
-import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.impl.utils.AspectRatioUtil
@@ -29,11 +28,13 @@
import androidx.camera.extensions.ExtensionsManager
import androidx.camera.extensions.internal.ExtensionVersion
import androidx.camera.extensions.internal.Version
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
+import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.CameraXExtensionTestParams
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.getImageCaptureSupportedResolutions
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
import androidx.camera.integration.extensions.utils.CameraSelectorUtil
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
@@ -56,10 +57,15 @@
@SmallTest
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = 21)
-class ImageCaptureExtenderValidationTest(private val config: CameraIdExtensionModePair) {
+class ImageCaptureExtenderValidationTest(private val config: CameraXExtensionTestParams) {
+ @get:Rule
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = config.implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+ )
+
@get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- PreTestCameraIdList(Camera2Config.defaultConfig())
+ PreTestCameraIdList(config.cameraXConfig)
)
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -75,13 +81,14 @@
assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
assumeTrue(!ExtensionVersion.isAdvancedExtenderSupported())
+ val (_, cameraXConfig, cameraId, extensionMode) = config
+ ProcessCameraProvider.configureInstance(cameraXConfig)
cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
extensionsManager = ExtensionsManager.getInstanceAsync(
context,
cameraProvider
)[10000, TimeUnit.MILLISECONDS]
- val (cameraId, extensionMode) = config
baseCameraSelector = CameraSelectorUtil.createCameraSelectorById(cameraId)
assumeTrue(extensionsManager.isExtensionAvailable(baseCameraSelector, extensionMode))
@@ -115,7 +122,7 @@
companion object {
@JvmStatic
@get:Parameterized.Parameters(name = "config = {0}")
- val parameters: Collection<CameraIdExtensionModePair>
+ val parameters: Collection<CameraXExtensionTestParams>
get() = CameraXExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureTest.kt
index c32f705..a253cea 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureTest.kt
@@ -19,22 +19,23 @@
import android.Manifest
import android.content.Context
import android.os.SystemClock
-import androidx.camera.camera2.Camera2Config
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageProxy
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.extensions.ExtensionsManager
import androidx.camera.extensions.internal.ExtensionVersion
import androidx.camera.extensions.internal.Version
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
+import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.CameraXExtensionTestParams
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.assumeExtensionModeSupported
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.launchCameraExtensionsActivity
import androidx.camera.integration.extensions.util.HOME_TIMEOUT_MS
import androidx.camera.integration.extensions.util.takePictureAndWaitForImageSavedIdle
import androidx.camera.integration.extensions.util.waitForPreviewViewStreaming
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
import androidx.camera.integration.extensions.utils.CameraSelectorUtil
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.CoreAppTestUtil
@@ -64,16 +65,24 @@
*/
@LargeTest
@RunWith(Parameterized::class)
-class ImageCaptureTest(private val config: CameraIdExtensionModePair) {
+class ImageCaptureTest(private val config: CameraXExtensionTestParams) {
private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
@get:Rule
- val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- PreTestCameraIdList(Camera2Config.defaultConfig())
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = config.implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
)
@get:Rule
- val permissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
+ PreTestCameraIdList(config.cameraXConfig)
+ )
+
+ @get:Rule
+ val permissionRule = GrantPermissionRule.grant(
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.RECORD_AUDIO,
+ )
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -95,6 +104,7 @@
// explicitly initiated from within the test.
device.setOrientationNatural()
+ ProcessCameraProvider.configureInstance(config.cameraXConfig)
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
index 31fc9c4..93f8710 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
@@ -18,9 +18,10 @@
import android.Manifest
import android.content.Context
-import androidx.camera.camera2.Camera2Config
import androidx.camera.extensions.ExtensionsManager
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
+import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.CameraXExtensionTestParams
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.VERIFICATION_TARGET_IMAGE_CAPTURE
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.VERIFICATION_TARGET_PREVIEW
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.launchCameraExtensionsActivity
@@ -28,8 +29,8 @@
import androidx.camera.integration.extensions.util.takePictureAndWaitForImageSavedIdle
import androidx.camera.integration.extensions.util.waitForPreviewViewIdle
import androidx.camera.integration.extensions.util.waitForPreviewViewStreaming
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
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.CoreAppTestUtil
@@ -58,16 +59,24 @@
*/
@LargeTest
@RunWith(Parameterized::class)
-class LifecycleStatusChangeStressTest(private val config: CameraIdExtensionModePair) {
+class LifecycleStatusChangeStressTest(private val config: CameraXExtensionTestParams) {
private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
@get:Rule
- val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- PreTestCameraIdList(Camera2Config.defaultConfig())
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = config.implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
)
@get:Rule
- val permissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
+ PreTestCameraIdList(config.cameraXConfig)
+ )
+
+ @get:Rule
+ val permissionRule = GrantPermissionRule.grant(
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.RECORD_AUDIO,
+ )
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -78,10 +87,12 @@
}
private var isTestStarted = false
+
@Before
fun setup() {
assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
CoreAppTestUtil.assumeCompatibleDevice()
+ ProcessCameraProvider.configureInstance(config.cameraXConfig)
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCameraStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCameraStressTest.kt
index e9fd19c..99c36b4 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCameraStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCameraStressTest.kt
@@ -17,7 +17,6 @@
package androidx.camera.integration.extensions
import android.content.Context
-import androidx.camera.camera2.Camera2Config
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraState
@@ -26,10 +25,12 @@
import androidx.camera.core.Preview
import androidx.camera.core.UseCase
import androidx.camera.extensions.ExtensionsManager
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
+import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.CameraXExtensionTestParams
import androidx.camera.integration.extensions.utils.CameraSelectorUtil
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.StressTestRule
@@ -57,10 +58,15 @@
@LargeTest
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = 21)
-class OpenCloseCameraStressTest(private val config: CameraIdExtensionModePair) {
+class OpenCloseCameraStressTest(private val config: CameraXExtensionTestParams) {
+ @get:Rule
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = config.implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+ )
+
@get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- PreTestCameraIdList(Camera2Config.defaultConfig())
+ PreTestCameraIdList(config.cameraXConfig)
)
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -77,7 +83,8 @@
@Before
fun setUp(): Unit = runBlocking {
assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
- val (cameraId, extensionMode) = config
+ val (_, cameraXConfig, cameraId, extensionMode) = config
+ ProcessCameraProvider.configureInstance(cameraXConfig)
cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
extensionsManager = ExtensionsManager.getInstanceAsync(
context,
@@ -120,11 +127,12 @@
companion object {
@ClassRule
- @JvmField val stressTest = StressTestRule()
+ @JvmField
+ val stressTest = StressTestRule()
@JvmStatic
@get:Parameterized.Parameters(name = "config = {0}")
- val parameters: Collection<CameraIdExtensionModePair>
+ val parameters: Collection<CameraXExtensionTestParams>
get() = CameraXExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt
index 41ac298..c7da548 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt
@@ -20,7 +20,6 @@
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraDevice
import android.view.Surface
-import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.interop.Camera2Interop
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
@@ -29,10 +28,12 @@
import androidx.camera.core.Preview
import androidx.camera.core.UseCase
import androidx.camera.extensions.ExtensionsManager
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
+import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.CameraXExtensionTestParams
import androidx.camera.integration.extensions.utils.CameraSelectorUtil
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.StressTestRule
@@ -59,10 +60,15 @@
@LargeTest
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = 21)
-class OpenCloseCaptureSessionStressTest(private val config: CameraIdExtensionModePair) {
+class OpenCloseCaptureSessionStressTest(private val config: CameraXExtensionTestParams) {
+ @get:Rule
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = config.implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+ )
+
@get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- PreTestCameraIdList(Camera2Config.defaultConfig())
+ PreTestCameraIdList(config.cameraXConfig)
)
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -82,13 +88,14 @@
@Before
fun setUp(): Unit = runBlocking {
assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
+ val (_, cameraXConfig, cameraId, extensionMode) = config
+ ProcessCameraProvider.configureInstance(cameraXConfig)
cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
extensionsManager = ExtensionsManager.getInstanceAsync(
context,
cameraProvider
)[10000, TimeUnit.MILLISECONDS]
- val (cameraId, extensionMode) = config
baseCameraSelector = CameraSelectorUtil.createCameraSelectorById(cameraId)
assumeTrue(extensionsManager.isExtensionAvailable(baseCameraSelector, extensionMode))
@@ -148,6 +155,7 @@
.setDeviceStateCallback(object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) {
}
+
// Some device doesn't invoke CameraCaptureSession onClosed callback thus
// we need to invoke when camera is closed.
override fun onClosed(device: CameraDevice) {
@@ -161,6 +169,7 @@
}
})
}
+
@After
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
@@ -227,11 +236,12 @@
companion object {
@ClassRule
- @JvmField val stressTest = StressTestRule()
+ @JvmField
+ val stressTest = StressTestRule()
@JvmStatic
@get:Parameterized.Parameters(name = "config = {0}")
- val parameters: Collection<CameraIdExtensionModePair>
+ val parameters: Collection<CameraXExtensionTestParams>
get() = CameraXExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt
index c32e349..0b07310 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.hardware.camera2.CameraCharacteristics
import android.os.Build
-import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.extensions.ExtensionsManager
@@ -28,10 +27,12 @@
import androidx.camera.extensions.impl.RequestUpdateProcessorImpl
import androidx.camera.extensions.internal.ExtensionVersion
import androidx.camera.extensions.internal.Version
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
+import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.CameraXExtensionTestParams
import androidx.camera.integration.extensions.utils.CameraSelectorUtil
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
@@ -54,10 +55,15 @@
@SmallTest
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = 21)
-class PreviewExtenderValidationTest(private val config: CameraIdExtensionModePair) {
+class PreviewExtenderValidationTest(private val config: CameraXExtensionTestParams) {
+ @get:Rule
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = config.implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+ )
+
@get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- PreTestCameraIdList(Camera2Config.defaultConfig())
+ PreTestCameraIdList(config.cameraXConfig)
)
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -73,13 +79,14 @@
assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
assumeTrue(!ExtensionVersion.isAdvancedExtenderSupported())
+ val (_, cameraXConfig, cameraId, extensionMode) = config
+ ProcessCameraProvider.configureInstance(cameraXConfig)
cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
extensionsManager = ExtensionsManager.getInstanceAsync(
context,
cameraProvider
)[10000, TimeUnit.MILLISECONDS]
- val (cameraId, extensionMode) = config
baseCameraSelector = CameraSelectorUtil.createCameraSelectorById(cameraId)
assumeTrue(extensionsManager.isExtensionAvailable(baseCameraSelector, extensionMode))
@@ -111,7 +118,7 @@
companion object {
@JvmStatic
@get:Parameterized.Parameters(name = "config = {0}")
- val parameters: Collection<CameraIdExtensionModePair>
+ val parameters: Collection<CameraXExtensionTestParams>
get() = CameraXExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
}
@@ -159,8 +166,10 @@
ProcessorType.PROCESSOR_TYPE_NONE -> assertThat(impl.processor).isNull()
ProcessorType.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY ->
assertThat(impl.processor).isInstanceOf(RequestUpdateProcessorImpl::class.java)
+
ProcessorType.PROCESSOR_TYPE_IMAGE_PROCESSOR ->
assertThat(impl.processor).isInstanceOf(PreviewImageProcessorImpl::class.java)
+
else ->
throw IllegalArgumentException("Unexpected ProcessorType: $processorType")
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewTest.kt
index e0f0e17..9e3c845 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewTest.kt
@@ -16,23 +16,26 @@
package androidx.camera.integration.extensions
+import android.Manifest
import android.content.Context
-import androidx.camera.camera2.Camera2Config
import androidx.camera.extensions.ExtensionsManager
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
+import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.CameraXExtensionTestParams
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.assumeExtensionModeSupported
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.launchCameraExtensionsActivity
import androidx.camera.integration.extensions.util.HOME_TIMEOUT_MS
import androidx.camera.integration.extensions.util.waitForPreviewViewStreaming
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
import androidx.camera.integration.extensions.utils.CameraSelectorUtil
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.CoreAppTestUtil
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.GrantPermissionRule
import androidx.test.uiautomator.UiDevice
import androidx.testutils.withActivity
import java.util.concurrent.TimeUnit
@@ -49,12 +52,23 @@
*/
@LargeTest
@RunWith(Parameterized::class)
-class PreviewTest(private val config: CameraIdExtensionModePair) {
+class PreviewTest(private val config: CameraXExtensionTestParams) {
private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
@get:Rule
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = config.implName == CAMERA_PIPE_IMPLEMENTATION_OPTION
+ )
+
+ @get:Rule
val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- PreTestCameraIdList(Camera2Config.defaultConfig())
+ PreTestCameraIdList(config.cameraXConfig)
+ )
+
+ @get:Rule
+ val permissionRule = GrantPermissionRule.grant(
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.RECORD_AUDIO,
)
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -79,6 +93,7 @@
// explicitly initiated from within the test.
device.setOrientationNatural()
+ ProcessCameraProvider.configureInstance(config.cameraXConfig)
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
@@ -133,8 +148,10 @@
)
assumeTrue(
"Cannot find next camera id that supports extensions mode($extensionsMode)",
- nextCameraId != null)
+ nextCameraId != null
+ )
}
+
/**
* Checks that Preview can successfully enter the STREAMING state to show the preview when an
* extension mode is enabled and after switching cameras.
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt
index 3cf5d2f..5d1431c 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt
@@ -19,14 +19,19 @@
import android.Manifest
import android.content.Context
import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig
+import androidx.camera.core.CameraXConfig
import androidx.camera.extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA2_IMPLEMENTATION_OPTION
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.launchCameraExtensionsActivity
import androidx.camera.integration.extensions.util.HOME_TIMEOUT_MS
import androidx.camera.integration.extensions.util.takePictureAndWaitForImageSavedIdle
import androidx.camera.integration.extensions.util.waitForPreviewViewStreaming
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.CoreAppTestUtil
@@ -58,16 +63,28 @@
*/
@LargeTest
@RunWith(Parameterized::class)
-class SwitchAvailableModesStressTest(private val cameraId: String) {
+class SwitchAvailableModesStressTest(
+ private val configName: String,
+ private val cameraXConfig: CameraXConfig,
+ private val cameraId: String
+) {
private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
@get:Rule
- val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- PreTestCameraIdList(Camera2Config.defaultConfig())
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = configName == CAMERA_PIPE_IMPLEMENTATION_OPTION
)
@get:Rule
- val permissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
+ PreTestCameraIdList(cameraXConfig)
+ )
+
+ @get:Rule
+ val permissionRule = GrantPermissionRule.grant(
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.RECORD_AUDIO,
+ )
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -78,9 +95,24 @@
@JvmField
val stressTest = StressTestRule()
- @Parameterized.Parameters(name = "cameraId = {0}")
+ @Parameterized.Parameters(name = "cameraXConfig = {0}, cameraId = {2}")
@JvmStatic
- fun parameters() = CameraUtil.getBackwardCompatibleCameraIdListOrThrow()
+ fun parameters(): List<Array<Any>> {
+ return CameraUtil.getBackwardCompatibleCameraIdListOrThrow().flatMap { cameraId ->
+ listOf(
+ arrayOf(
+ CAMERA2_IMPLEMENTATION_OPTION,
+ Camera2Config.defaultConfig(),
+ cameraId
+ ),
+ arrayOf(
+ CAMERA_PIPE_IMPLEMENTATION_OPTION,
+ CameraPipeConfig.defaultConfig(),
+ cameraId
+ ),
+ )
+ }
+ }
}
private var isTestStarted = false
@@ -90,6 +122,7 @@
assumeTrue(CameraUtil.deviceHasCamera())
assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
+ ProcessCameraProvider.configureInstance(cameraXConfig)
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt
index 7a0ee23..25361ed 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt
@@ -19,8 +19,12 @@
import android.Manifest
import android.content.Context
import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig
import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraXConfig
import androidx.camera.extensions.ExtensionsManager
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA2_IMPLEMENTATION_OPTION
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.launchCameraExtensionsActivity
import androidx.camera.integration.extensions.util.HOME_TIMEOUT_MS
@@ -28,6 +32,7 @@
import androidx.camera.integration.extensions.util.waitForPreviewViewStreaming
import androidx.camera.integration.extensions.utils.ExtensionModeUtil
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.CoreAppTestUtil
@@ -55,16 +60,28 @@
*/
@LargeTest
@RunWith(Parameterized::class)
-class SwitchCameraStressTest(private val extensionMode: Int) {
+class SwitchCameraStressTest(
+ private val configName: String,
+ private val cameraXConfig: CameraXConfig,
+ private val extensionMode: Int
+) {
private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
@get:Rule
- val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
- PreTestCameraIdList(Camera2Config.defaultConfig())
+ val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+ active = configName == CAMERA_PIPE_IMPLEMENTATION_OPTION
)
@get:Rule
- val permissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
+ PreTestCameraIdList(cameraXConfig)
+ )
+
+ @get:Rule
+ val permissionRule = GrantPermissionRule.grant(
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.RECORD_AUDIO,
+ )
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -72,18 +89,36 @@
companion object {
@ClassRule
- @JvmField val stressTest = StressTestRule()
+ @JvmField
+ val stressTest = StressTestRule()
- @Parameterized.Parameters(name = "extensionMode = {0}")
+ @Parameterized.Parameters(name = "cameraXConfig = {0}, extensionMode = {2}")
@JvmStatic
- fun parameters() = ExtensionModeUtil.AVAILABLE_EXTENSION_MODES
+ fun parameters(): List<Array<Any>> {
+ return ExtensionModeUtil.AVAILABLE_EXTENSION_MODES.flatMap { extensionMode ->
+ listOf(
+ arrayOf(
+ CAMERA2_IMPLEMENTATION_OPTION,
+ Camera2Config.defaultConfig(),
+ extensionMode
+ ),
+ arrayOf(
+ CAMERA_PIPE_IMPLEMENTATION_OPTION,
+ CameraPipeConfig.defaultConfig(),
+ extensionMode
+ ),
+ )
+ }
+ }
}
private var isTestStarted = false
+
@Before
fun setup() {
assumeTrue(CameraUtil.deviceHasCamera())
assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
+ ProcessCameraProvider.configureInstance(cameraXConfig)
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsTestUtil.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsTestUtil.kt
index 52563ae..879d170 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsTestUtil.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsTestUtil.kt
@@ -22,11 +22,13 @@
import android.hardware.camera2.CameraCharacteristics
import android.os.Build
import android.util.Size
-import androidx.camera.camera2.interop.Camera2CameraInfo
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig
import androidx.camera.core.CameraInfo
-import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.CameraXConfig
import androidx.camera.core.ImageCapture
import androidx.camera.core.Preview
+import androidx.camera.core.impl.CameraInfoInternal
import androidx.camera.extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager
import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl
@@ -48,12 +50,13 @@
import androidx.camera.extensions.impl.advanced.HdrAdvancedExtenderImpl
import androidx.camera.extensions.impl.advanced.NightAdvancedExtenderImpl
import androidx.camera.extensions.internal.ExtensionVersion
+import androidx.camera.extensions.internal.ExtensionsUtils
import androidx.camera.extensions.internal.Version
import androidx.camera.integration.extensions.CameraExtensionsActivity
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA2_IMPLEMENTATION_OPTION
+import androidx.camera.integration.extensions.CameraExtensionsActivity.CAMERA_PIPE_IMPLEMENTATION_OPTION
import androidx.camera.integration.extensions.IntentExtraKey
-import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
import androidx.camera.integration.extensions.utils.CameraSelectorUtil.createCameraSelectorById
-import androidx.camera.integration.extensions.utils.ExtensionModeUtil
import androidx.camera.integration.extensions.utils.ExtensionModeUtil.AVAILABLE_EXTENSION_MODES
import androidx.camera.testing.impl.CameraUtil
import androidx.camera.testing.impl.LabTestRule
@@ -65,14 +68,23 @@
object CameraXExtensionsTestUtil {
+ data class CameraXExtensionTestParams(
+ val implName: String,
+ val cameraXConfig: CameraXConfig,
+ val cameraId: String,
+ val extensionMode: Int,
+ )
+
/**
* Gets a list of all camera id and extension mode combinations.
*/
@JvmStatic
- fun getAllCameraIdExtensionModeCombinations(): List<CameraIdExtensionModePair> =
+ fun getAllCameraIdExtensionModeCombinations(): List<CameraXExtensionTestParams> =
CameraUtil.getBackwardCompatibleCameraIdListOrThrow().flatMap { cameraId ->
- ExtensionModeUtil.AVAILABLE_EXTENSION_MODES.map { extensionMode ->
- CameraIdExtensionModePair(cameraId, extensionMode)
+ AVAILABLE_EXTENSION_MODES.flatMap { extensionMode ->
+ CAMERAX_CONFIGS.map { config ->
+ CameraXExtensionTestParams(config.first, config.second, cameraId, extensionMode)
+ }
}
}
@@ -85,7 +97,7 @@
arrayListOf<Array<Any>>().apply {
val allModes = mutableListOf<Int>()
allModes.add(0, ExtensionMode.NONE)
- allModes.addAll(ExtensionModeUtil.AVAILABLE_EXTENSION_MODES)
+ allModes.addAll(AVAILABLE_EXTENSION_MODES)
CameraUtil.getBackwardCompatibleCameraIdListOrThrow().forEach { cameraId ->
allModes.forEach { mode ->
add(arrayOf(cameraId, mode))
@@ -163,7 +175,8 @@
ExtensionMode.AUTO -> AutoAdvancedExtenderImpl()
else -> throw AssertionFailedError("No such Preview extender implementation")
}.apply {
- val cameraCharacteristicsMap = Camera2CameraInfo.from(cameraInfo).cameraCharacteristicsMap
+ val cameraCharacteristicsMap =
+ ExtensionsUtils.getCameraCharacteristicsMap(cameraInfo as CameraInfoInternal)
init(cameraId, cameraCharacteristicsMap)
}
@@ -288,7 +301,8 @@
// When there is no capture processor, the image format is JPEG.
// When there is capture processor for post-processing, the image format is YUV_420_888.
if ((impl.captureProcessor == null && it.first == ImageFormat.JPEG) ||
- (impl.captureProcessor != null && it.first == ImageFormat.YUV_420_888)) {
+ (impl.captureProcessor != null && it.first == ImageFormat.YUV_420_888)
+ ) {
return it.second.toList()
}
}
@@ -359,7 +373,10 @@
const val VERIFICATION_TARGET_IMAGE_CAPTURE = 0x2
/**
- * Constant to specify that the verification target is [ImageAnalysis].
+ * A list of supported implementation options and their respective [CameraXConfig].
*/
- const val VERIFICATION_TARGET_IMAGE_ANALYSIS = 0x4
+ private val CAMERAX_CONFIGS = listOf(
+ Pair(CAMERA2_IMPLEMENTATION_OPTION, Camera2Config.defaultConfig()),
+ Pair(CAMERA_PIPE_IMPLEMENTATION_OPTION, CameraPipeConfig.defaultConfig())
+ )
}
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
index 9a85bb1..88439b2 100644
--- a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
@@ -132,6 +132,7 @@
private static final String TAG = "CameraExtensionActivity";
private static final int PERMISSIONS_REQUEST_CODE = 42;
public static final String INTENT_EXTRA_CAMERA_IMPLEMENTATION = "camera_implementation";
+ public static final String CAMERA2_IMPLEMENTATION_OPTION = "camera2";
public static final String CAMERA_PIPE_IMPLEMENTATION_OPTION = "camera_pipe";
private CameraSelector mCurrentCameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/utils/CameraSelectorUtil.kt b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/utils/CameraSelectorUtil.kt
index 29d47c2..537632c 100644
--- a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/utils/CameraSelectorUtil.kt
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/utils/CameraSelectorUtil.kt
@@ -16,26 +16,25 @@
package androidx.camera.integration.extensions.utils
+import android.annotation.SuppressLint
import android.content.Context
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraManager
-import androidx.annotation.OptIn
-import androidx.camera.camera2.interop.Camera2CameraInfo
-import androidx.camera.camera2.interop.ExperimentalCamera2Interop
import androidx.camera.core.CameraFilter
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
+import androidx.camera.core.impl.CameraInfoInternal
import androidx.camera.extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager
object CameraSelectorUtil {
@JvmStatic
- @OptIn(ExperimentalCamera2Interop::class)
+ @SuppressLint("RestrictedApiAndroidX")
fun createCameraSelectorById(cameraId: String) =
CameraSelector.Builder().addCameraFilter(CameraFilter { cameraInfos ->
cameraInfos.forEach {
- if (Camera2CameraInfo.from(it).cameraId.equals(cameraId)) {
+ if ((it as CameraInfoInternal).cameraId.equals(cameraId)) {
return@CameraFilter listOf<CameraInfo>(it)
}
}
@@ -54,8 +53,8 @@
try {
val supportedCameraIdList = cameraManager.cameraIdList.filter {
extensionsManager.isExtensionAvailable(
- createCameraSelectorById(it),
- extensionsMode
+ createCameraSelectorById(it),
+ extensionsMode
)
}
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/EffectsFragmentDeviceTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/EffectsFragmentDeviceTest.kt
index ae0a33b..8365c15 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/EffectsFragmentDeviceTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/EffectsFragmentDeviceTest.kt
@@ -22,6 +22,7 @@
import androidx.camera.core.CameraXConfig
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.Logger
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.testing.impl.CameraPipeConfigTestRule
import androidx.camera.testing.impl.CameraUtil
@@ -59,14 +60,12 @@
@get:Rule
val useCameraRule = CameraUtil.grantCameraPermissionAndPreTest(
- CameraControllerFragmentTest.testCameraRule,
- CameraUtil.PreTestCameraIdList(cameraConfig)
+ CameraControllerFragmentTest.testCameraRule, CameraUtil.PreTestCameraIdList(cameraConfig)
)
@get:Rule
val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
- android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
- android.Manifest.permission.RECORD_AUDIO
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.RECORD_AUDIO
)
private val instrumentation = InstrumentationRegistry.getInstrumentation()
private lateinit var cameraProvider: ProcessCameraProvider
@@ -83,8 +82,7 @@
ApplicationProvider.getApplicationContext()
)[10000, TimeUnit.MILLISECONDS]
fragmentScenario = FragmentScenario.launchInContainer(
- EffectsFragment::class.java, null, R.style.AppTheme,
- null
+ EffectsFragment::class.java, null, R.style.AppTheme, null
)
fragment = fragmentScenario.getFragment()
}
@@ -100,9 +98,34 @@
}
@Test
+ fun toggleCameraLatencyTest() {
+ // Arrange: use COMPATIBLE mode to get an accurate measurement.
+ instrumentation.runOnMainSync {
+ fragment.previewView.implementationMode = PreviewView.ImplementationMode.COMPATIBLE
+ }
+ val startTimeMillis = System.currentTimeMillis()
+ fragment.assertPreviewStreamingState(PreviewView.StreamState.STREAMING)
+
+ // Act: toggle camera for 10 times.
+ val numberOfToggles = 10
+ for (i in 0 until numberOfToggles) {
+ instrumentation.runOnMainSync {
+ fragment.toggleCamera()
+ }
+ fragment.assertPreviewStreamingState(PreviewView.StreamState.IDLE)
+ fragment.assertPreviewStreamingState(PreviewView.StreamState.STREAMING)
+ }
+
+ // Record the average duration of the test.
+ val averageDuration = (System.currentTimeMillis() - startTimeMillis) / numberOfToggles
+ val tag = "toggleCameraLatencyTest"
+ Logger.d(tag, "Effects pipeline performance profiling. duration: [$averageDuration]")
+ }
+
+ @Test
fun launchFragment_surfaceProcessorIsActive() {
// Arrange.
- fragment.assertPreviewStreaming()
+ fragment.assertPreviewStreamingState(PreviewView.StreamState.STREAMING)
// Assert.
assertThat(fragment.getSurfaceProcessor().isSurfaceRequestedAndProvided()).isTrue()
}
@@ -111,7 +134,7 @@
fun takePicture_imageEffectInvoked() {
// Arrange.
fragment.run {
- assertPreviewStreaming()
+ assertPreviewStreamingState(PreviewView.StreamState.STREAMING)
// Act.
assertCanTakePicture()
}
@@ -126,7 +149,7 @@
fragment.surfaceEffectForImageCapture.isChecked = true
}
// Assert.
- fragment.assertPreviewStreaming()
+ fragment.assertPreviewStreamingState(PreviewView.StreamState.STREAMING)
fragment.assertCanTakePicture()
assertThat(fragment.getImageEffect()).isNull()
}
@@ -156,13 +179,15 @@
assertThat(uri).isNotNull()
}
- private fun EffectsFragment.assertPreviewStreaming() {
+ private fun EffectsFragment.assertPreviewStreamingState(
+ streamState: PreviewView.StreamState
+ ) {
val previewStreaming = Semaphore(0)
instrumentation.runOnMainSync {
previewView.previewStreamState.observe(
this
) {
- if (it == PreviewView.StreamState.STREAMING) {
+ if (it == streamState) {
previewStreaming.release()
}
}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/EffectsFragment.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/EffectsFragment.kt
index ca25ba9..e1cf4a4 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/EffectsFragment.kt
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/EffectsFragment.kt
@@ -33,7 +33,6 @@
import androidx.camera.core.CameraEffect.PREVIEW
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
import androidx.camera.core.CameraSelector
-import androidx.camera.core.CameraSelector.LENS_FACING_BACK
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.OutputFileOptions
import androidx.camera.core.ImageCaptureException
@@ -69,6 +68,7 @@
private var recording: Recording? = null
private lateinit var surfaceProcessor: ToneMappingSurfaceProcessor
private var imageEffect: ToneMappingImageEffect? = null
+ private var isBackCamera = true
override fun onCreateView(
inflater: LayoutInflater,
@@ -98,13 +98,7 @@
stopRecording()
}
}
- flip.setOnClickListener {
- if (cameraController.cameraSelector.lensFacing == LENS_FACING_BACK) {
- cameraController.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
- } else {
- cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
- }
- }
+ flip.setOnClickListener { toggleCamera() }
// Set up the surface processor.
surfaceProcessor = ToneMappingSurfaceProcessor()
// Set up the camera controller.
@@ -245,6 +239,17 @@
}
}
+ fun toggleCamera() {
+ cameraController.cameraSelector =
+ if (isBackCamera) {
+ isBackCamera = false
+ CameraSelector.DEFAULT_FRONT_CAMERA
+ } else {
+ isBackCamera = true
+ CameraSelector.DEFAULT_BACK_CAMERA
+ }
+ }
+
@VisibleForTesting
fun getImageEffect(): ToneMappingImageEffect? {
return imageEffect
diff --git a/car/app/app-projected/gradle.properties b/car/app/app-projected/gradle.properties
deleted file mode 100644
index a060082..0000000
--- a/car/app/app-projected/gradle.properties
+++ /dev/null
@@ -1,16 +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.
-#
-androidx.targetSdkVersion = 33
diff --git a/car/app/app-samples/navigation/automotive/build.gradle b/car/app/app-samples/navigation/automotive/build.gradle
index 5b2284f..11dd738 100644
--- a/car/app/app-samples/navigation/automotive/build.gradle
+++ b/car/app/app-samples/navigation/automotive/build.gradle
@@ -33,9 +33,6 @@
defaultConfig {
applicationId "androidx.car.app.sample.navigation"
minSdkVersion 29
- targetSdkVersion 33
- versionCode 1
- versionName "1.0"
}
buildTypes {
diff --git a/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
index 63d9960..2414350 100644
--- a/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
@@ -15,9 +15,7 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:versionCode="1"
- android:versionName="1.0">
+ xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
diff --git a/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SettingsScreen.java b/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SettingsScreen.java
index c673eab..137c537 100644
--- a/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SettingsScreen.java
+++ b/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SettingsScreen.java
@@ -23,6 +23,7 @@
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -71,8 +72,10 @@
sectionBBuilder.build(),
getCarContext().getString(R.string.settings_section_b_label)));
return templateBuilder
- .setHeaderAction(Action.BACK)
- .setTitle(getCarContext().getString(R.string.settings_title))
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.settings_title))
+ .setStartHeaderAction(Action.BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/navigation/mobile/build.gradle b/car/app/app-samples/navigation/mobile/build.gradle
index c99a3f1..ac9a22b 100644
--- a/car/app/app-samples/navigation/mobile/build.gradle
+++ b/car/app/app-samples/navigation/mobile/build.gradle
@@ -32,9 +32,6 @@
defaultConfig {
applicationId "androidx.car.app.sample.navigation"
minSdkVersion 23
- targetSdkVersion 33
- versionCode 1
- versionName "1.0"
}
buildTypes {
diff --git a/car/app/app-samples/navigation/mobile/lint-baseline.xml b/car/app/app-samples/navigation/mobile/lint-baseline.xml
new file mode 100644
index 0000000..7d1e656
--- /dev/null
+++ b/car/app/app-samples/navigation/mobile/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
+
+ <issue
+ id="ForegroundServicePermission"
+ message="foregroundServiceType:location requires permission:[android.permission.FOREGROUND_SERVICE_LOCATION] AND any permission in list:[android.permission.ACCESS_COARSE_LOCATION, android.permission.ACCESS_FINE_LOCATION]"
+ errorLine1=" <service"
+ errorLine2=" ^">
+ <location
+ file="src/main/AndroidManifest.xml"/>
+ </issue>
+
+</issues>
diff --git a/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml
index 5ea244a..e98b4e9 100644
--- a/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml
@@ -15,9 +15,7 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:versionCode="1"
- android:versionName="1.0">
+ xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
diff --git a/car/app/app-samples/showcase/automotive/build.gradle b/car/app/app-samples/showcase/automotive/build.gradle
index 822a062..0b60747 100644
--- a/car/app/app-samples/showcase/automotive/build.gradle
+++ b/car/app/app-samples/showcase/automotive/build.gradle
@@ -23,7 +23,6 @@
defaultConfig {
applicationId "androidx.car.app.sample.showcase"
minSdkVersion 29
- targetSdkVersion 33
// Increment this to generate signed builds for uploading to Playstore
// Make sure this is different from the showcase-mobile version
versionCode 107
diff --git a/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
index 28758d6..8b59a09 100644
--- a/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
@@ -16,9 +16,7 @@
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:versionCode="1"
- android:versionName="1.0">
+ xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/StartScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/StartScreen.java
index 7f9c9f9..e8e477c 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/StartScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/StartScreen.java
@@ -19,8 +19,8 @@
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
-import androidx.car.app.model.ActionStrip;
import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -63,10 +63,10 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.showcase_demos_title))
- .setHeaderAction(Action.APP_ICON)
- .setActionStrip(new ActionStrip.Builder()
- .addAction(createSettingsActionButton())
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.showcase_demos_title))
+ .setStartHeaderAction(Action.APP_ICON)
+ .addEndHeaderAction(createSettingsActionButton())
.build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/MapDemosScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/MapDemosScreen.java
index 7a96c58..78e0e76 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/MapDemosScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/MapDemosScreen.java
@@ -21,6 +21,7 @@
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -63,8 +64,10 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.map_demos_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.map_demos_title))
+ .setStartHeaderAction(BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/SettingsScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/SettingsScreen.java
index 0d321708..4033393 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/SettingsScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/SettingsScreen.java
@@ -25,6 +25,7 @@
import androidx.car.app.CarContext;
import androidx.car.app.CarToast;
import androidx.car.app.Screen;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -84,10 +85,12 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.settings_action_title) + " ("
- + getCarContext().getString(R.string.cal_api_level_prefix,
- getCarContext().getCarAppApiLevel()) + ")")
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.settings_action_title) + " ("
+ + getCarContext().getString(R.string.cal_api_level_prefix,
+ getCarContext().getCarAppApiLevel()) + ")")
+ .setStartHeaderAction(BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/TemplateLayoutsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/TemplateLayoutsDemoScreen.java
index b7cb763..f4dc792 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/TemplateLayoutsDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/TemplateLayoutsDemoScreen.java
@@ -23,7 +23,7 @@
import androidx.car.app.Screen;
import androidx.car.app.constraints.ConstraintManager;
import androidx.car.app.model.Action;
-import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -92,24 +92,24 @@
}
}
+ Header.Builder headerBuilder = new Header.Builder()
+ .setStartHeaderAction(BACK)
+ .setTitle(getCarContext().getString(R.string.template_layouts_demo_title));
+
ListTemplate.Builder builder = new ListTemplate.Builder()
- .setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.template_layouts_demo_title))
- .setHeaderAction(BACK);
+ .setSingleList(listBuilder.build());
// If the current page does not cover the last item, we will show a More button
if ((mPage + 1) * listLimit < screenList.size() && mPage + 1 < MAX_PAGES) {
- builder.setActionStrip(new ActionStrip.Builder()
- .addAction(new Action.Builder()
+ headerBuilder.addEndHeaderAction(new Action.Builder()
.setTitle(getCarContext().getString(R.string.more_action_title))
.setOnClickListener(() -> {
mPage++;
invalidate();
})
- .build())
- .build());
+ .build());
}
- return builder.build();
+ return builder.setHeader(headerBuilder.build()).build();
}
private Row buildRowForTemplate(Screen screen, int title) {
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/UserInteractionsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/UserInteractionsDemoScreen.java
index c8bb5ba..4160e71 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/UserInteractionsDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/UserInteractionsDemoScreen.java
@@ -22,7 +22,6 @@
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
-import androidx.car.app.model.ActionStrip;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.Header;
import androidx.car.app.model.Item;
@@ -71,18 +70,16 @@
return new ListTemplate.Builder()
.setSingleList(builder.build())
- .setTitle(getCarContext().getString(R.string.user_interactions_demo_title))
- .setHeaderAction(BACK)
- .setActionStrip(
- new ActionStrip.Builder()
- .addAction(
- new Action.Builder()
- .setTitle(getCarContext().getString(
- R.string.home_caps_action_title))
- .setOnClickListener(
- () -> getScreenManager().popToRoot())
- .build())
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.user_interactions_demo_title))
+ .setStartHeaderAction(BACK)
+ .addEndHeaderAction(new Action.Builder()
+ .setTitle(getCarContext().getString(
+ R.string.home_caps_action_title))
+ .setOnClickListener(
+ () -> getScreenManager().popToRoot())
.build())
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/MapWithContentDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/MapWithContentDemoScreen.java
index 49d9658..746abb8 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/MapWithContentDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/MapWithContentDemoScreen.java
@@ -21,6 +21,7 @@
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -72,8 +73,10 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.map_demos_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.map_demos_title))
+ .setStartHeaderAction(BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapWithGridTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapWithGridTemplateDemoScreen.java
index 0186731..1409145 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapWithGridTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapWithGridTemplateDemoScreen.java
@@ -28,6 +28,7 @@
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.GridItem;
import androidx.car.app.model.GridTemplate;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.Template;
import androidx.car.app.navigation.model.MapWithContentTemplate;
@@ -54,8 +55,10 @@
GridTemplate gridTemplate = new GridTemplate.Builder()
.setSingleList(gridItemListBuilder.build())
- .setHeaderAction(Action.BACK)
- .setTitle("Report?")
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(Action.BACK)
+ .setTitle("Report?")
+ .build())
.build();
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/NavigationNotificationsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/NavigationNotificationsDemoScreen.java
index 1e6316c..8275639 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/NavigationNotificationsDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/NavigationNotificationsDemoScreen.java
@@ -26,6 +26,7 @@
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -79,8 +80,10 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.nav_notification_demo_title))
- .setHeaderAction(Action.BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.nav_notification_demo_title))
+ .setStartHeaderAction(Action.BACK)
+ .build())
.build();
}
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/paging/PagedListTemplate.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/paging/PagedListTemplate.java
index 721c6bb..c319593 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/paging/PagedListTemplate.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/paging/PagedListTemplate.java
@@ -22,7 +22,7 @@
import androidx.car.app.ScreenManager;
import androidx.car.app.constraints.ConstraintManager;
import androidx.car.app.model.Action;
-import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -77,15 +77,16 @@
}
}
+ Header.Builder headerBuilder = new Header.Builder()
+ .setStartHeaderAction(Action.BACK)
+ .setTitle(mRowList.getTemplateTitle());
+
ListTemplate.Builder builder = new ListTemplate.Builder()
- .setSingleList(listBuilder.build())
- .setTitle(mRowList.getTemplateTitle())
- .setHeaderAction(Action.BACK);
+ .setSingleList(listBuilder.build());
// If the current page does not cover the last item, we will show a More button
if ((mPage + 1) * listLimit < screenList.size() && mPage + 1 < MAX_PAGES) {
- builder.setActionStrip(new ActionStrip.Builder()
- .addAction(new Action.Builder()
+ headerBuilder.addEndHeaderAction(new Action.Builder()
.setTitle(getCarContext().getString(R.string.more_action_title))
.setOnClickListener(() -> {
getScreenManager().push(
@@ -96,11 +97,10 @@
)
);
})
- .build())
- .build());
+ .build());
}
- return builder.build();
+ return builder.setHeader(headerBuilder.build()).build();
}
/** A list of rows, used to populate a {@link PagedListTemplate} */
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ContentLimitsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ContentLimitsDemoScreen.java
index 8a728b4..07d646b 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ContentLimitsDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ContentLimitsDemoScreen.java
@@ -16,7 +16,6 @@
package androidx.car.app.sample.showcase.common.screens.settings;
-import static androidx.car.app.model.Action.BACK;
import static androidx.car.app.sample.showcase.common.screens.settings.LoadingScreen.loadingScreenTemplate;
import android.content.Context;
@@ -27,6 +26,8 @@
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
import androidx.car.app.constraints.ConstraintManager;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -98,8 +99,10 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.content_limits))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.content_limits))
+ .setStartHeaderAction(Action.BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java
index 3b10c80..d8a1a85 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java
@@ -17,7 +17,6 @@
package androidx.car.app.sample.showcase.common.screens.settings;
import static androidx.car.app.CarToast.LENGTH_LONG;
-import static androidx.car.app.model.Action.BACK;
import static androidx.car.app.sample.showcase.common.screens.settings.LoadingScreen.loadingScreenTemplate;
import android.content.Context;
@@ -28,6 +27,8 @@
import androidx.car.app.CarContext;
import androidx.car.app.CarToast;
import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.ParkedOnlyOnClickListener;
@@ -101,8 +102,10 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.parking_vs_driving_demo_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.parking_vs_driving_demo_title))
+ .setStartHeaderAction(Action.BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/GridTemplateMenuDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/GridTemplateMenuDemoScreen.java
index ea8c1f5..4fc02e6 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/GridTemplateMenuDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/GridTemplateMenuDemoScreen.java
@@ -21,6 +21,7 @@
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -52,8 +53,10 @@
R.string.notification_template_demo_title));
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.grid_template_menu_demo_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.grid_template_menu_demo_title))
+ .setStartHeaderAction(BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/ListTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/ListTemplateDemoScreen.java
index d198248..f09a92f 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/ListTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/ListTemplateDemoScreen.java
@@ -21,6 +21,7 @@
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -77,8 +78,10 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.list_template_demo_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.list_template_demo_title))
+ .setStartHeaderAction(BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/MessageTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/MessageTemplateDemoScreen.java
index 32aaa1a..3ac9ccd 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/MessageTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/MessageTemplateDemoScreen.java
@@ -21,6 +21,7 @@
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -52,8 +53,10 @@
R.string.long_msg_template_demo_title));
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.msg_template_demo_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.msg_template_demo_title))
+ .setStartHeaderAction(BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/TabTemplateLayoutsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/TabTemplateLayoutsDemoScreen.java
index 4cb3772..b1b371e 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/TabTemplateLayoutsDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/TabTemplateLayoutsDemoScreen.java
@@ -21,6 +21,7 @@
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -49,8 +50,11 @@
R.string.tab_template_no_tabs_demo_title));
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.tab_template_layouts_demo_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext()
+ .getString(R.string.tab_template_layouts_demo_title))
+ .setStartHeaderAction(BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/GridTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/GridTemplateDemoScreen.java
index 1dfacb0..2bc2203d 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/GridTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/GridTemplateDemoScreen.java
@@ -32,10 +32,10 @@
import androidx.car.app.Screen;
import androidx.car.app.constraints.ConstraintManager;
import androidx.car.app.model.Action;
-import androidx.car.app.model.ActionStrip;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.GridItem;
import androidx.car.app.model.GridTemplate;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.OnClickListener;
import androidx.car.app.model.Template;
@@ -182,14 +182,12 @@
.show())
.build();
return new GridTemplate.Builder()
- .setHeaderAction(Action.APP_ICON)
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(BACK)
+ .setTitle(getCarContext().getString(R.string.grid_template_demo_title))
+ .addEndHeaderAction(settings)
+ .build())
.setSingleList(gridItemListBuilder.build())
- .setTitle(getCarContext().getString(R.string.grid_template_demo_title))
- .setActionStrip(
- new ActionStrip.Builder()
- .addAction(settings)
- .build())
- .setHeaderAction(BACK)
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/NotificationDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/NotificationDemoScreen.java
index 7c9328e4..9a2fdac 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/NotificationDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/gridtemplates/NotificationDemoScreen.java
@@ -16,6 +16,8 @@
package androidx.car.app.sample.showcase.common.screens.templatelayouts.gridtemplates;
+import static androidx.car.app.model.Action.BACK;
+
import static java.util.concurrent.TimeUnit.SECONDS;
import android.annotation.SuppressLint;
@@ -36,11 +38,11 @@
import androidx.car.app.CarContext;
import androidx.car.app.CarToast;
import androidx.car.app.Screen;
-import androidx.car.app.model.Action;
import androidx.car.app.model.CarColor;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.GridItem;
import androidx.car.app.model.GridTemplate;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.Template;
import androidx.car.app.notification.CarAppExtender;
@@ -180,8 +182,10 @@
return new GridTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.notification_demo))
- .setHeaderAction(Action.BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.notification_demo))
+ .setStartHeaderAction(BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ContentProviderIconsDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ContentProviderIconsDemoScreen.java
index e94d105e..d6bd4d6 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ContentProviderIconsDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ContentProviderIconsDemoScreen.java
@@ -16,8 +16,6 @@
package androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
-import static androidx.car.app.model.Action.BACK;
-
import android.net.Uri;
import androidx.annotation.NonNull;
@@ -25,7 +23,9 @@
import androidx.car.app.CarContext;
import androidx.car.app.HostInfo;
import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -80,8 +80,11 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.content_provider_icons_demo_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext()
+ .getString(R.string.content_provider_icons_demo_title))
+ .setStartHeaderAction(Action.BACK)
+ .build())
.build();
}
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/EmptyListDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/EmptyListDemoScreen.java
index 865dd17..a299c91 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/EmptyListDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/EmptyListDemoScreen.java
@@ -21,6 +21,7 @@
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Template;
@@ -43,7 +44,7 @@
)
.build()
)
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder().setStartHeaderAction(BACK).build())
.build();
}
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/RadioButtonListDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/RadioButtonListDemoScreen.java
index df4d004..e2642dd 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/RadioButtonListDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/RadioButtonListDemoScreen.java
@@ -17,15 +17,14 @@
package androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
import static androidx.car.app.CarToast.LENGTH_LONG;
-import static androidx.car.app.model.Action.BACK;
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.CarToast;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
-import androidx.car.app.model.ActionStrip;
import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -71,25 +70,23 @@
getCarContext().getString(R.string.sample_additional_list)));
return templateBuilder
- .setTitle(getCarContext().getString(R.string.radio_button_list_demo_title))
- .setActionStrip(
- new ActionStrip.Builder()
- .addAction(
- new Action.Builder()
- .setTitle(mIsEnabled
- ? getCarContext().getString(
- R.string.disable_all_rows)
- : getCarContext().getString(
- R.string.enable_all_rows))
- .setOnClickListener(
- () -> {
- mIsEnabled = !mIsEnabled;
- invalidate();
- })
- .build())
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.radio_button_list_demo_title))
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(new Action.Builder()
+ .setTitle(mIsEnabled
+ ? getCarContext().getString(
+ R.string.disable_all_rows)
+ : getCarContext().getString(
+ R.string.enable_all_rows))
+ .setOnClickListener(
+ () -> {
+ mIsEnabled = !mIsEnabled;
+ invalidate();
+ })
.build())
- .setHeaderAction(
- BACK).build();
+ .build())
+ .build();
}
private CarIcon buildImageWithResource(int imageId) {
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/SecondaryActionsAndDecorationDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/SecondaryActionsAndDecorationDemoScreen.java
index 1a0229d..8ec5978 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/SecondaryActionsAndDecorationDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/SecondaryActionsAndDecorationDemoScreen.java
@@ -15,8 +15,8 @@
*/
package androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
+
import static androidx.car.app.CarToast.LENGTH_LONG;
-import static androidx.car.app.model.Action.BACK;
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
@@ -24,8 +24,8 @@
import androidx.car.app.Screen;
import androidx.car.app.annotations.RequiresCarApi;
import androidx.car.app.model.Action;
-import androidx.car.app.model.ActionStrip;
import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -60,19 +60,17 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext()
- .getString(R.string.secondary_actions_decoration_button_demo_title))
- .setHeaderAction(BACK)
- .setActionStrip(
- new ActionStrip.Builder()
- .addAction(
- new Action.Builder()
- .setTitle(getCarContext().getString(
- R.string.home_caps_action_title))
- .setOnClickListener(
- () -> getScreenManager().popToRoot())
- .build())
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext()
+ .getString(R.string.secondary_actions_decoration_button_demo_title))
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(new Action.Builder()
+ .setTitle(getCarContext().getString(
+ R.string.home_caps_action_title))
+ .setOnClickListener(
+ () -> getScreenManager().popToRoot())
.build())
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/SectionedItemListDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/SectionedItemListDemoScreen.java
index 4e57e89..c5e3b17 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/SectionedItemListDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/SectionedItemListDemoScreen.java
@@ -16,13 +16,11 @@
package androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
-import static androidx.car.app.model.Action.BACK;
-
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
-import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -57,19 +55,17 @@
.addSectionedList(SectionedItemList.create(listBuilderTwo.build(),
getCarContext()
.getString(R.string.sectioned_item_list_two_title)))
- .setTitle(getCarContext()
- .getString(R.string.sectioned_item_list_demo_title))
- .setHeaderAction(BACK)
- .setActionStrip(
- new ActionStrip.Builder()
- .addAction(
- new Action.Builder()
- .setTitle(getCarContext().getString(
- R.string.home_caps_action_title))
- .setOnClickListener(
- () -> getScreenManager().popToRoot())
- .build())
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext()
+ .getString(R.string.sectioned_item_list_demo_title))
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(new Action.Builder()
+ .setTitle(getCarContext().getString(
+ R.string.home_caps_action_title))
+ .setOnClickListener(
+ () -> getScreenManager().popToRoot())
.build())
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/TextAndIconsDemosScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/TextAndIconsDemosScreen.java
index ceb70e06..49b098a 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/TextAndIconsDemosScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/TextAndIconsDemosScreen.java
@@ -29,6 +29,7 @@
import androidx.car.app.Screen;
import androidx.car.app.model.CarColor;
import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -75,8 +76,10 @@
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.text_icons_demo_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.text_icons_demo_title))
+ .setStartHeaderAction(BACK)
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ToggleButtonListDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ToggleButtonListDemoScreen.java
index 092b530..bd56771 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ToggleButtonListDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/listtemplates/ToggleButtonListDemoScreen.java
@@ -17,7 +17,6 @@
package androidx.car.app.sample.showcase.common.screens.templatelayouts.listtemplates;
import static androidx.car.app.CarToast.LENGTH_LONG;
-import static androidx.car.app.model.Action.BACK;
import static androidx.car.app.model.CarColor.GREEN;
import androidx.annotation.NonNull;
@@ -25,8 +24,8 @@
import androidx.car.app.CarToast;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
-import androidx.car.app.model.ActionStrip;
import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.OnClickListener;
@@ -92,18 +91,16 @@
return new ListTemplate.Builder()
.setSingleList(builder.build())
- .setTitle(getCarContext().getString(R.string.toggle_button_demo_title))
- .setHeaderAction(BACK)
- .setActionStrip(
- new ActionStrip.Builder()
- .addAction(
- new Action.Builder()
- .setTitle(getCarContext().getString(
- R.string.home_caps_action_title))
- .setOnClickListener(
- () -> getScreenManager().popToRoot())
- .build())
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.toggle_button_demo_title))
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(new Action.Builder()
+ .setTitle(getCarContext().getString(
+ R.string.home_caps_action_title))
+ .setOnClickListener(
+ () -> getScreenManager().popToRoot())
.build())
+ .build())
.build();
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/RequestPermissionMenuDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/RequestPermissionMenuDemoScreen.java
index 875d1df..4ca2193 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/RequestPermissionMenuDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/RequestPermissionMenuDemoScreen.java
@@ -16,11 +16,11 @@
package androidx.car.app.sample.showcase.common.screens.userinteractions;
-import static androidx.car.app.model.Action.BACK;
-
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -60,8 +60,11 @@
.build());
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.request_permission_menu_demo_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext()
+ .getString(R.string.request_permission_menu_demo_title))
+ .setStartHeaderAction(Action.BACK)
+ .build())
.build();
}
}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/TaskOverflowDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/TaskOverflowDemoScreen.java
index 7c44b5e..ea7fb18 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/TaskOverflowDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/TaskOverflowDemoScreen.java
@@ -16,12 +16,12 @@
package androidx.car.app.sample.showcase.common.screens.userinteractions;
-import static androidx.car.app.model.Action.BACK;
-
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
import androidx.car.app.constraints.ConstraintManager;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.Header;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
@@ -101,8 +101,10 @@
}
return new ListTemplate.Builder()
.setSingleList(listBuilder.build())
- .setTitle(getCarContext().getString(R.string.application_overflow_title))
- .setHeaderAction(BACK)
+ .setHeader(new Header.Builder()
+ .setTitle(getCarContext().getString(R.string.application_overflow_title))
+ .setStartHeaderAction(Action.BACK)
+ .build())
.build();
}
}
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
index bcac7d9..f8237f0 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
@@ -279,7 +279,7 @@
<string name="png_bitmap_title" msgid="3385912074130977032">"Bit-mapa gisa bidalitako PNG bat"</string>
<string name="just_row_title" msgid="965700021568970725">"Izen bat baino ez"</string>
<string name="title_with_app_icon_row_title" msgid="6294250714820740520">"Aplikazioaren ikonoa duen izena"</string>
- <string name="title_with_res_id_image_row_title" msgid="3813134904602875778">"Baliabide IDaren irudia duen izena"</string>
+ <string name="title_with_res_id_image_row_title" msgid="3813134904602875778">"Baliabide-identifikatzailearen irudia duen izena"</string>
<string name="title_with_svg_image_row_title" msgid="6109092343637263755">"SVG irudi bat duen izena"</string>
<string name="colored_secondary_row_title" msgid="5173288934252528929">"Bigarren mailako testu koloreztatua"</string>
<string name="title_with_secondary_lines_row_title" msgid="7769408881272549837">"Bigarren mailako hainbat testu-lerro dituen izena"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
index a7ce1f3..7e464e9 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
@@ -338,7 +338,7 @@
<string name="user_interactions_demo_title" msgid="1356952319161314986">"Interactions des utilisateurs"</string>
<string name="request_permission_menu_demo_title" msgid="4796486779527427017">"Démos de demandes d\'autorisation"</string>
<string name="application_overflow_title" msgid="396427940886169325">"Programme de validation du menu de développement de l\'appli"</string>
- <string name="application_overflow_subtitle1" msgid="7429415605726615529">"Veuillez tester les modèles suivant lorsque vous faites passer"</string>
+ <string name="application_overflow_subtitle1" msgid="7429415605726615529">"Veuillez tester les modèles suivants lorsque vous changez"</string>
<string name="application_overflow_subtitle2" msgid="4385123036846369714">"l\'état du véhicule de garé à en mouvement"</string>
<string name="perm_group" msgid="3834918337351876270">"Groupe d\'autorisations"</string>
<string name="perm_group_description" msgid="7348847631139139024">"Groupe d\'autorisations pour l\'appli Showcase"</string>
diff --git a/car/app/app-samples/showcase/mobile/build.gradle b/car/app/app-samples/showcase/mobile/build.gradle
index c5b6e95..ee2abc5 100644
--- a/car/app/app-samples/showcase/mobile/build.gradle
+++ b/car/app/app-samples/showcase/mobile/build.gradle
@@ -32,7 +32,6 @@
defaultConfig {
applicationId "androidx.car.app.sample.showcase"
minSdkVersion 23
- targetSdkVersion 33
// Increment this to generate signed builds for uploading to Playstore
// Make sure this is different from the showcase-automotive version
versionCode 106
diff --git a/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
index 917662c..e5cfd60 100644
--- a/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
@@ -16,9 +16,7 @@
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:versionCode="1"
- android:versionName="1.0">
+ xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
diff --git a/car/app/app/api/current.ignore b/car/app/app/api/current.ignore
deleted file mode 100644
index 6fceb72..0000000
--- a/car/app/app/api/current.ignore
+++ /dev/null
@@ -1,9 +0,0 @@
-// Baseline format: 1.0
-RemovedField: androidx.car.app.mediaextensions.MetadataExtras#KEY_CONTENT_FORMAT_DARK_MODE_LARGE_ICON_URI:
- Removed field androidx.car.app.mediaextensions.MetadataExtras.KEY_CONTENT_FORMAT_DARK_MODE_LARGE_ICON_URI
-RemovedField: androidx.car.app.mediaextensions.MetadataExtras#KEY_CONTENT_FORMAT_DARK_MODE_SMALL_ICON_URI:
- Removed field androidx.car.app.mediaextensions.MetadataExtras.KEY_CONTENT_FORMAT_DARK_MODE_SMALL_ICON_URI
-RemovedField: androidx.car.app.mediaextensions.MetadataExtras#KEY_CONTENT_FORMAT_LIGHT_MODE_LARGE_ICON_URI:
- Removed field androidx.car.app.mediaextensions.MetadataExtras.KEY_CONTENT_FORMAT_LIGHT_MODE_LARGE_ICON_URI
-RemovedField: androidx.car.app.mediaextensions.MetadataExtras#KEY_CONTENT_FORMAT_LIGHT_MODE_SMALL_ICON_URI:
- Removed field androidx.car.app.mediaextensions.MetadataExtras.KEY_CONTENT_FORMAT_LIGHT_MODE_SMALL_ICON_URI
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index 8d4fd0f..5e75c7b 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -1122,7 +1122,7 @@
method public void sendDismiss(androidx.car.app.OnDoneCallback);
}
- @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public class Badge {
+ @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public class Badge {
method public androidx.car.app.model.CarColor? getBackgroundColor();
method public androidx.car.app.model.CarIcon? getIcon();
method public boolean hasDot();
@@ -1268,7 +1268,7 @@
}
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class GridItem implements androidx.car.app.model.Item {
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.Badge? getBadge();
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Badge? getBadge();
method public androidx.car.app.model.CarIcon? getImage();
method public int getImageType();
method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
@@ -1283,9 +1283,9 @@
ctor public GridItem.Builder();
method public androidx.car.app.model.GridItem build();
method public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon);
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, androidx.car.app.model.Badge);
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, androidx.car.app.model.Badge);
method public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, int);
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, int, androidx.car.app.model.Badge);
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, int, androidx.car.app.model.Badge);
method public androidx.car.app.model.GridItem.Builder setLoading(boolean);
method public androidx.car.app.model.GridItem.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
method public androidx.car.app.model.GridItem.Builder setText(androidx.car.app.model.CarText);
@@ -1295,32 +1295,34 @@
}
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class GridTemplate implements androidx.car.app.model.Template {
- method public androidx.car.app.model.ActionStrip? getActionStrip();
+ method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public java.util.List<androidx.car.app.model.Action!> getActions();
- method public androidx.car.app.model.Action? getHeaderAction();
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public int getItemImageShape();
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public int getItemSize();
+ method public androidx.car.app.model.Header? getHeader();
+ method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public int getItemImageShape();
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public int getItemSize();
method public androidx.car.app.model.ItemList? getSingleList();
- method public androidx.car.app.model.CarText? getTitle();
+ method @Deprecated public androidx.car.app.model.CarText? getTitle();
method public boolean isLoading();
- field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int ITEM_IMAGE_SHAPE_CIRCLE = 2; // 0x2
- field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int ITEM_IMAGE_SHAPE_UNSET = 1; // 0x1
- field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int ITEM_SIZE_LARGE = 4; // 0x4
- field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int ITEM_SIZE_MEDIUM = 2; // 0x2
- field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int ITEM_SIZE_SMALL = 1; // 0x1
+ field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_IMAGE_SHAPE_CIRCLE = 2; // 0x2
+ field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_IMAGE_SHAPE_UNSET = 1; // 0x1
+ field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_LARGE = 4; // 0x4
+ field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_MEDIUM = 2; // 0x2
+ field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_SMALL = 1; // 0x1
}
public static final class GridTemplate.Builder {
ctor public GridTemplate.Builder();
method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder addAction(androidx.car.app.model.Action);
method public androidx.car.app.model.GridTemplate build();
- method public androidx.car.app.model.GridTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
- method public androidx.car.app.model.GridTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder setItemImageShape(@SuppressCompatibility int);
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder setItemSize(@SuppressCompatibility int);
+ method @Deprecated public androidx.car.app.model.GridTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+ method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder setHeader(androidx.car.app.model.Header);
+ method @Deprecated public androidx.car.app.model.GridTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridTemplate.Builder setItemImageShape(@SuppressCompatibility int);
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridTemplate.Builder setItemSize(@SuppressCompatibility int);
method public androidx.car.app.model.GridTemplate.Builder setLoading(boolean);
method public androidx.car.app.model.GridTemplate.Builder setSingleList(androidx.car.app.model.ItemList);
- method public androidx.car.app.model.GridTemplate.Builder setTitle(CharSequence);
+ method @Deprecated public androidx.car.app.model.GridTemplate.Builder setTitle(CharSequence);
}
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class Header {
@@ -1380,12 +1382,13 @@
}
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ListTemplate implements androidx.car.app.model.Template {
- method public androidx.car.app.model.ActionStrip? getActionStrip();
+ method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
method @androidx.car.app.annotations.RequiresCarApi(6) public java.util.List<androidx.car.app.model.Action!> getActions();
- method public androidx.car.app.model.Action? getHeaderAction();
+ method public androidx.car.app.model.Header? getHeader();
+ method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
method public java.util.List<androidx.car.app.model.SectionedItemList!> getSectionedLists();
method public androidx.car.app.model.ItemList? getSingleList();
- method public androidx.car.app.model.CarText? getTitle();
+ method @Deprecated public androidx.car.app.model.CarText? getTitle();
method public boolean isLoading();
method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ListTemplate.Builder toBuilder();
}
@@ -1396,11 +1399,12 @@
method public androidx.car.app.model.ListTemplate.Builder addSectionedList(androidx.car.app.model.SectionedItemList);
method public androidx.car.app.model.ListTemplate build();
method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ListTemplate.Builder clearSectionedLists();
- method public androidx.car.app.model.ListTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
- method public androidx.car.app.model.ListTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+ method @Deprecated public androidx.car.app.model.ListTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+ method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.ListTemplate.Builder setHeader(androidx.car.app.model.Header);
+ method @Deprecated public androidx.car.app.model.ListTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
method public androidx.car.app.model.ListTemplate.Builder setLoading(boolean);
method public androidx.car.app.model.ListTemplate.Builder setSingleList(androidx.car.app.model.ItemList);
- method public androidx.car.app.model.ListTemplate.Builder setTitle(CharSequence);
+ method @Deprecated public androidx.car.app.model.ListTemplate.Builder setTitle(CharSequence);
}
@SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class LongMessageTemplate implements androidx.car.app.model.Template {
@@ -1424,7 +1428,7 @@
method @Deprecated @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getActionStrip();
method public java.util.List<androidx.car.app.model.Action!> getActions();
method public androidx.car.app.model.CarText? getDebugMessage();
- method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.Header? getHeader();
+ method public androidx.car.app.model.Header? getHeader();
method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
method public androidx.car.app.model.CarIcon? getIcon();
method public androidx.car.app.model.CarText getMessage();
@@ -1506,7 +1510,7 @@
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class PaneTemplate implements androidx.car.app.model.Template {
method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
- method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.Header? getHeader();
+ method public androidx.car.app.model.Header? getHeader();
method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
method public androidx.car.app.model.Pane getPane();
method @Deprecated public androidx.car.app.model.CarText? getTitle();
diff --git a/car/app/app/api/restricted_current.ignore b/car/app/app/api/restricted_current.ignore
deleted file mode 100644
index 6fceb72..0000000
--- a/car/app/app/api/restricted_current.ignore
+++ /dev/null
@@ -1,9 +0,0 @@
-// Baseline format: 1.0
-RemovedField: androidx.car.app.mediaextensions.MetadataExtras#KEY_CONTENT_FORMAT_DARK_MODE_LARGE_ICON_URI:
- Removed field androidx.car.app.mediaextensions.MetadataExtras.KEY_CONTENT_FORMAT_DARK_MODE_LARGE_ICON_URI
-RemovedField: androidx.car.app.mediaextensions.MetadataExtras#KEY_CONTENT_FORMAT_DARK_MODE_SMALL_ICON_URI:
- Removed field androidx.car.app.mediaextensions.MetadataExtras.KEY_CONTENT_FORMAT_DARK_MODE_SMALL_ICON_URI
-RemovedField: androidx.car.app.mediaextensions.MetadataExtras#KEY_CONTENT_FORMAT_LIGHT_MODE_LARGE_ICON_URI:
- Removed field androidx.car.app.mediaextensions.MetadataExtras.KEY_CONTENT_FORMAT_LIGHT_MODE_LARGE_ICON_URI
-RemovedField: androidx.car.app.mediaextensions.MetadataExtras#KEY_CONTENT_FORMAT_LIGHT_MODE_SMALL_ICON_URI:
- Removed field androidx.car.app.mediaextensions.MetadataExtras.KEY_CONTENT_FORMAT_LIGHT_MODE_SMALL_ICON_URI
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index 8d4fd0f..5e75c7b 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -1122,7 +1122,7 @@
method public void sendDismiss(androidx.car.app.OnDoneCallback);
}
- @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public class Badge {
+ @SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public class Badge {
method public androidx.car.app.model.CarColor? getBackgroundColor();
method public androidx.car.app.model.CarIcon? getIcon();
method public boolean hasDot();
@@ -1268,7 +1268,7 @@
}
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class GridItem implements androidx.car.app.model.Item {
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.Badge? getBadge();
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Badge? getBadge();
method public androidx.car.app.model.CarIcon? getImage();
method public int getImageType();
method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
@@ -1283,9 +1283,9 @@
ctor public GridItem.Builder();
method public androidx.car.app.model.GridItem build();
method public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon);
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, androidx.car.app.model.Badge);
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, androidx.car.app.model.Badge);
method public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, int);
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, int, androidx.car.app.model.Badge);
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setImage(androidx.car.app.model.CarIcon, int, androidx.car.app.model.Badge);
method public androidx.car.app.model.GridItem.Builder setLoading(boolean);
method public androidx.car.app.model.GridItem.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
method public androidx.car.app.model.GridItem.Builder setText(androidx.car.app.model.CarText);
@@ -1295,32 +1295,34 @@
}
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class GridTemplate implements androidx.car.app.model.Template {
- method public androidx.car.app.model.ActionStrip? getActionStrip();
+ method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public java.util.List<androidx.car.app.model.Action!> getActions();
- method public androidx.car.app.model.Action? getHeaderAction();
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public int getItemImageShape();
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public int getItemSize();
+ method public androidx.car.app.model.Header? getHeader();
+ method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public int getItemImageShape();
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public int getItemSize();
method public androidx.car.app.model.ItemList? getSingleList();
- method public androidx.car.app.model.CarText? getTitle();
+ method @Deprecated public androidx.car.app.model.CarText? getTitle();
method public boolean isLoading();
- field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int ITEM_IMAGE_SHAPE_CIRCLE = 2; // 0x2
- field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int ITEM_IMAGE_SHAPE_UNSET = 1; // 0x1
- field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int ITEM_SIZE_LARGE = 4; // 0x4
- field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int ITEM_SIZE_MEDIUM = 2; // 0x2
- field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public static final int ITEM_SIZE_SMALL = 1; // 0x1
+ field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_IMAGE_SHAPE_CIRCLE = 2; // 0x2
+ field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_IMAGE_SHAPE_UNSET = 1; // 0x1
+ field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_LARGE = 4; // 0x4
+ field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_MEDIUM = 2; // 0x2
+ field @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final int ITEM_SIZE_SMALL = 1; // 0x1
}
public static final class GridTemplate.Builder {
ctor public GridTemplate.Builder();
method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder addAction(androidx.car.app.model.Action);
method public androidx.car.app.model.GridTemplate build();
- method public androidx.car.app.model.GridTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
- method public androidx.car.app.model.GridTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder setItemImageShape(@SuppressCompatibility int);
- method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder setItemSize(@SuppressCompatibility int);
+ method @Deprecated public androidx.car.app.model.GridTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+ method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.GridTemplate.Builder setHeader(androidx.car.app.model.Header);
+ method @Deprecated public androidx.car.app.model.GridTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridTemplate.Builder setItemImageShape(@SuppressCompatibility int);
+ method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridTemplate.Builder setItemSize(@SuppressCompatibility int);
method public androidx.car.app.model.GridTemplate.Builder setLoading(boolean);
method public androidx.car.app.model.GridTemplate.Builder setSingleList(androidx.car.app.model.ItemList);
- method public androidx.car.app.model.GridTemplate.Builder setTitle(CharSequence);
+ method @Deprecated public androidx.car.app.model.GridTemplate.Builder setTitle(CharSequence);
}
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(5) public final class Header {
@@ -1380,12 +1382,13 @@
}
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class ListTemplate implements androidx.car.app.model.Template {
- method public androidx.car.app.model.ActionStrip? getActionStrip();
+ method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
method @androidx.car.app.annotations.RequiresCarApi(6) public java.util.List<androidx.car.app.model.Action!> getActions();
- method public androidx.car.app.model.Action? getHeaderAction();
+ method public androidx.car.app.model.Header? getHeader();
+ method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
method public java.util.List<androidx.car.app.model.SectionedItemList!> getSectionedLists();
method public androidx.car.app.model.ItemList? getSingleList();
- method public androidx.car.app.model.CarText? getTitle();
+ method @Deprecated public androidx.car.app.model.CarText? getTitle();
method public boolean isLoading();
method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ListTemplate.Builder toBuilder();
}
@@ -1396,11 +1399,12 @@
method public androidx.car.app.model.ListTemplate.Builder addSectionedList(androidx.car.app.model.SectionedItemList);
method public androidx.car.app.model.ListTemplate build();
method @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.ListTemplate.Builder clearSectionedLists();
- method public androidx.car.app.model.ListTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
- method public androidx.car.app.model.ListTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+ method @Deprecated public androidx.car.app.model.ListTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+ method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.ListTemplate.Builder setHeader(androidx.car.app.model.Header);
+ method @Deprecated public androidx.car.app.model.ListTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
method public androidx.car.app.model.ListTemplate.Builder setLoading(boolean);
method public androidx.car.app.model.ListTemplate.Builder setSingleList(androidx.car.app.model.ItemList);
- method public androidx.car.app.model.ListTemplate.Builder setTitle(CharSequence);
+ method @Deprecated public androidx.car.app.model.ListTemplate.Builder setTitle(CharSequence);
}
@SuppressCompatibility @androidx.car.app.annotations.RequiresCarApi(2) public final class LongMessageTemplate implements androidx.car.app.model.Template {
@@ -1424,7 +1428,7 @@
method @Deprecated @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getActionStrip();
method public java.util.List<androidx.car.app.model.Action!> getActions();
method public androidx.car.app.model.CarText? getDebugMessage();
- method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.Header? getHeader();
+ method public androidx.car.app.model.Header? getHeader();
method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
method public androidx.car.app.model.CarIcon? getIcon();
method public androidx.car.app.model.CarText getMessage();
@@ -1506,7 +1510,7 @@
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol public final class PaneTemplate implements androidx.car.app.model.Template {
method @Deprecated public androidx.car.app.model.ActionStrip? getActionStrip();
- method @androidx.car.app.annotations.RequiresCarApi(7) public androidx.car.app.model.Header? getHeader();
+ method public androidx.car.app.model.Header? getHeader();
method @Deprecated public androidx.car.app.model.Action? getHeaderAction();
method public androidx.car.app.model.Pane getPane();
method @Deprecated public androidx.car.app.model.CarText? getTitle();
diff --git a/car/app/app/build.gradle b/car/app/app/build.gradle
index 93d8396..763b66e 100644
--- a/car/app/app/build.gradle
+++ b/car/app/app/build.gradle
@@ -49,6 +49,7 @@
id("AndroidXPlugin")
id("com.android.library")
id("androidx.stableaidl")
+ id("org.jetbrains.kotlin.android")
}
dependencies {
@@ -63,6 +64,9 @@
api("androidx.lifecycle:lifecycle-common-java8:2.2.0")
api("androidx.annotation:annotation-experimental:1.4.0")
+ api(libs.kotlinStdlib)
+ implementation(libs.kotlinStdlibCommon)
+
annotationProcessor(libs.nullaway)
// TODO(shiufai): We need this for assertThrows. Point back to the AndroidX shared version if
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/PersonsEqualityHelper.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/PersonsEqualityHelper.java
deleted file mode 100644
index e5268d6..0000000
--- a/car/app/app/src/main/java/androidx/car/app/messaging/model/PersonsEqualityHelper.java
+++ /dev/null
@@ -1,72 +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.car.app.messaging.model;
-
-import androidx.annotation.Nullable;
-import androidx.core.app.Person;
-
-import java.util.Objects;
-
-/** Helper functions to compare two {@link Person} object. */
-class PersonsEqualityHelper {
-
- /** Calculate the hashcode for {@link Person} object. */
- // TODO(b/266877597): Move to androidx.core.app.Person
- public static int getPersonHashCode(@Nullable Person person) {
- if (person == null) {
- return 0;
- }
-
- // If a unique ID was provided, use it
- String key = person.getKey();
- if (key != null) {
- return key.hashCode();
- }
-
- // Fallback: Use hash code for individual fields
- return Objects.hash(person.getName(), person.getUri(), person.isBot(),
- person.isImportant());
- }
-
- /** Compare two {@link Person} objects. */
- // TODO(b/266877597): Move to androidx.core.app.Person
- public static boolean arePersonsEqual(@Nullable Person person1, @Nullable Person person2) {
- if (person1 == null && person2 == null) {
- return true;
- } else if (person1 == null || person2 == null) {
- return false;
- }
-
- // If a unique ID was provided, use it
- String key1 = person1.getKey();
- String key2 = person2.getKey();
- if (key1 != null || key2 != null) {
- return Objects.equals(key1, key2);
- }
-
- // CharSequence doesn't have well-defined "equals" behavior -- convert to String instead
- String name1 = Objects.toString(person1.getName());
- String name2 = Objects.toString(person2.getName());
-
- // Fallback: Compare field-by-field
- return
- Objects.equals(name1, name2)
- && Objects.equals(person1.getUri(), person2.getUri())
- && Objects.equals(person1.isBot(), person2.isBot())
- && Objects.equals(person1.isImportant(), person2.isImportant());
- }
-}
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/PersonsEqualityHelper.kt b/car/app/app/src/main/java/androidx/car/app/messaging/model/PersonsEqualityHelper.kt
new file mode 100644
index 0000000..641f357
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/PersonsEqualityHelper.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.car.app.messaging.model
+
+import androidx.core.app.Person
+import java.util.Objects
+import java.util.Objects.toString
+
+/** Helper functions to compare two [Person] object. */
+internal object PersonsEqualityHelper {
+ // TODO(b/266877597): Move to androidx.core.app.Person
+ /** Calculate the hashcode for [Person] object. */
+ @JvmStatic
+ fun getPersonHashCode(person: Person?): Int {
+ if (person == null) {
+ return 0
+ }
+
+ // If a unique ID was provided, use it
+ val key = person.key
+ if (key != null) {
+ return key.hashCode()
+ }
+
+ // Fallback: Use hash code for individual fields
+ return Objects.hash(
+ person.name, person.uri, person.isBot,
+ person.isImportant
+ )
+ }
+
+ // TODO(b/266877597): Move to androidx.core.app.Person
+ /** Compare two [Person] objects. */
+ @JvmStatic
+ fun arePersonsEqual(person1: Person?, person2: Person?): Boolean {
+ if (person1 == null && person2 == null) {
+ return true
+ } else if (person1 == null || person2 == null) {
+ return false
+ }
+
+ // If a unique ID was provided, use it
+ val key1 = person1.key
+ val key2 = person2.key
+ if (key1 != null || key2 != null) {
+ return key1 == key2
+ }
+
+ // CharSequence doesn't have well-defined "equals" behavior -- convert to String instead
+ val name1 = toString(person1.name)
+ val name2 = toString(person2.name)
+
+ // Fallback: Compare field-by-field
+ return name1 == name2 &&
+ person1.uri == person2.uri &&
+ person1.isBot == person2.isBot &&
+ person1.isImportant == person2.isImportant
+ }
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Badge.java b/car/app/app/src/main/java/androidx/car/app/model/Badge.java
index ade526c..9547f79 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Badge.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Badge.java
@@ -21,7 +21,6 @@
import androidx.car.app.annotations.CarProtocol;
import androidx.car.app.annotations.ExperimentalCarApi;
import androidx.car.app.annotations.KeepFields;
-import androidx.car.app.annotations.RequiresCarApi;
import java.util.Objects;
@@ -35,7 +34,6 @@
@CarProtocol
@ExperimentalCarApi
@KeepFields
-@RequiresCarApi(7)
public class Badge {
private final boolean mHasDot;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/GridItem.java b/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
index a56d1b8..e2cbe97 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
@@ -31,7 +31,6 @@
import androidx.car.app.annotations.CarProtocol;
import androidx.car.app.annotations.ExperimentalCarApi;
import androidx.car.app.annotations.KeepFields;
-import androidx.car.app.annotations.RequiresCarApi;
import androidx.car.app.model.constraints.CarIconConstraints;
import androidx.car.app.model.constraints.CarTextConstraints;
@@ -153,7 +152,6 @@
*/
@ExperimentalCarApi
@Nullable
- @RequiresCarApi(7)
public Badge getBadge() {
return mBadge;
}
@@ -360,7 +358,6 @@
*/
@NonNull
@ExperimentalCarApi
- @RequiresCarApi(7)
public Builder setImage(@NonNull CarIcon image, @NonNull Badge badge) {
requireNonNull(badge);
mBadge = badge;
@@ -380,7 +377,6 @@
*/
@NonNull
@ExperimentalCarApi
- @RequiresCarApi(7)
public Builder setImage(@NonNull CarIcon image, @GridItemImageType int imageType,
@NonNull Badge badge) {
requireNonNull(badge);
diff --git a/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java
index 22ce7bc..96ae949 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java
@@ -68,7 +68,6 @@
* will be adjusted according to bucket and screen size.
*/
@ExperimentalCarApi
- @RequiresCarApi(7)
@IntDef(
value = {
ITEM_SIZE_SMALL,
@@ -86,7 +85,6 @@
* @see GridTemplate.Builder#setItemSize(int)
*/
@ExperimentalCarApi
- @RequiresCarApi(7)
public static final int ITEM_SIZE_SMALL = (1 << 0);
/**
@@ -95,7 +93,6 @@
* @see GridTemplate.Builder#setItemSize(int)
*/
@ExperimentalCarApi
- @RequiresCarApi(7)
public static final int ITEM_SIZE_MEDIUM = (1 << 1);
/**
@@ -104,7 +101,6 @@
* @see GridTemplate.Builder#setItemSize(int)
*/
@ExperimentalCarApi
- @RequiresCarApi(7)
public static final int ITEM_SIZE_LARGE = (1 << 2);
/**
@@ -113,7 +109,6 @@
* <p>Grid item images will be cropped by the host to match the shape type.
*/
@ExperimentalCarApi
- @RequiresCarApi(7)
@IntDef(
value = {
ITEM_IMAGE_SHAPE_UNSET,
@@ -132,7 +127,6 @@
* @see GridTemplate.Builder#setItemImageShape(int)
*/
@ExperimentalCarApi
- @RequiresCarApi(7)
public static final int ITEM_IMAGE_SHAPE_UNSET = (1 << 0);
/**
@@ -141,16 +135,33 @@
* @see GridTemplate.Builder#setItemImageShape(int)
*/
@ExperimentalCarApi
- @RequiresCarApi(7)
public static final int ITEM_IMAGE_SHAPE_CIRCLE = (1 << 1);
private final boolean mIsLoading;
+
+ /**
+ * @deprecated use {@link Header.Builder#setTitle(CarText)}; mHeader replaces the need
+ * for this field.
+ */
+ @Deprecated
@Nullable
private final CarText mTitle;
+
+ /**
+ * @deprecated use {@link Header.Builder#setStartHeaderAction(Action)}; mHeader replaces the
+ * need for this field.
+ */
+ @Deprecated
@Nullable
private final Action mHeaderAction;
@Nullable
private final ItemList mSingleList;
+
+ /**
+ * @deprecated use {@link Header.Builder#addEndHeaderAction(Action)} for each action; mHeader
+ * replaces the need for this field.
+ */
+ @Deprecated
@Nullable
private final ActionStrip mActionStrip;
private final List<Action> mActions;
@@ -160,10 +171,22 @@
private final int mItemImageShape;
/**
+ * Represents a Header object to set the startHeaderAction, the title and the endHeaderActions
+ *
+ * @see GridTemplate.Builder#setHeader(Header)
+ */
+ @Nullable
+ @RequiresCarApi(7)
+ private final Header mHeader;
+
+ /**
* Returns the title of the template or {@code null} if not set.
*
* @see Builder#setTitle(CharSequence)
+ *
+ * @deprecated use {@link Header#getTitle()} instead.
*/
+ @Deprecated
@Nullable
public CarText getTitle() {
return mTitle;
@@ -174,7 +197,10 @@
* {@code null} if not set.
*
* @see Builder#setHeaderAction(Action)
+ *
+ * @deprecated use {@link Header#getStartHeaderAction()} instead.
*/
+ @Deprecated
@Nullable
public Action getHeaderAction() {
return mHeaderAction;
@@ -184,7 +210,10 @@
* Returns the {@link ActionStrip} for this template or {@code null} if not set.
*
* @see Builder#setActionStrip(ActionStrip)
+ *
+ * @deprecated use {@link Header#getEndHeaderActions()} instead.
*/
+ @Deprecated
@Nullable
public ActionStrip getActionStrip() {
return mActionStrip;
@@ -228,7 +257,6 @@
* @see GridTemplate.Builder#setItemSize(int)
*/
@ExperimentalCarApi
- @RequiresCarApi(7)
@ItemSize
public int getItemSize() {
return mItemSize;
@@ -243,11 +271,41 @@
*/
@ExperimentalCarApi
@ItemImageShape
- @RequiresCarApi(7)
public int getItemImageShape() {
return mItemImageShape;
}
+ /**
+ * Returns the {@link Header} to display in this template.
+ *
+ * <p>This method was introduced in API 7, but is backwards compatible even if the client is
+ * using API 6 or below. </p>
+ *
+ * @see GridTemplate.Builder#setHeader(Header)
+ */
+ @Nullable
+ public Header getHeader() {
+ if (mHeader != null) {
+ return mHeader;
+ }
+ if (mTitle == null && mHeaderAction == null && mActionStrip == null) {
+ return null;
+ }
+ Header.Builder headerBuilder = new Header.Builder();
+ if (mTitle != null) {
+ headerBuilder.setTitle(mTitle);
+ }
+ if (mHeaderAction != null) {
+ headerBuilder.setStartHeaderAction(mHeaderAction);
+ }
+ if (mActionStrip != null) {
+ for (Action action: mActionStrip.getActions()) {
+ headerBuilder.addEndHeaderAction(action);
+ }
+ }
+ return headerBuilder.build();
+ }
+
@NonNull
@Override
public String toString() {
@@ -257,7 +315,7 @@
@Override
public int hashCode() {
return Objects.hash(mIsLoading, mTitle, mHeaderAction, mSingleList, mActionStrip,
- mItemSize, mItemImageShape);
+ mItemSize, mItemImageShape, mHeader);
}
@Override
@@ -277,7 +335,8 @@
&& Objects.equals(mActionStrip, otherTemplate.mActionStrip)
&& Objects.equals(mActions, otherTemplate.mActions)
&& mItemSize == otherTemplate.mItemSize
- && mItemImageShape == otherTemplate.mItemImageShape;
+ && mItemImageShape == otherTemplate.mItemImageShape
+ && Objects.equals(mHeader, otherTemplate.mHeader);
}
GridTemplate(Builder builder) {
@@ -289,6 +348,7 @@
mActions = CollectionUtils.unmodifiableCopy(builder.mActions);
mItemSize = builder.mItemSize;
mItemImageShape = builder.mItemImageShape;
+ mHeader = builder.mHeader;
}
/** Constructs an empty instance, used by serialization code. */
@@ -302,6 +362,7 @@
mActions = Collections.emptyList();
mItemSize = ITEM_SIZE_SMALL;
mItemImageShape = ITEM_IMAGE_SHAPE_UNSET;
+ mHeader = null;
}
/** A builder of {@link GridTemplate}. */
@@ -320,6 +381,8 @@
@ItemSize
int mItemSize = ITEM_SIZE_SMALL;
@ItemImageShape int mItemImageShape = ITEM_IMAGE_SHAPE_UNSET;
+ @Nullable
+ Header mHeader;
/**
* Sets whether the template is in a loading state.
@@ -349,7 +412,10 @@
* @throws IllegalArgumentException if {@code headerAction} does not meet the template's
* requirements
* @throws NullPointerException if {@code headerAction} is {@code null}
+ *
+ * @deprecated Use {@link Header.Builder#setStartHeaderAction(Action)}
*/
+ @Deprecated
@NonNull
public Builder setHeaderAction(@NonNull Action headerAction) {
ACTIONS_CONSTRAINTS_HEADER.validateOrThrow(Collections.singletonList(headerAction));
@@ -367,7 +433,10 @@
*
* @throws NullPointerException if {@code title} is null
* @throws IllegalArgumentException if {@code title} contains unsupported spans
+ *
+ * @deprecated Use {@link Header.Builder#setTitle(CarText)}
*/
+ @Deprecated
@NonNull
public Builder setTitle(@NonNull CharSequence title) {
mTitle = CarText.create(requireNonNull(title));
@@ -399,7 +468,10 @@
*
* @throws IllegalArgumentException if {@code actionStrip} does not meet the requirements
* @throws NullPointerException if {@code actionStrip} is {@code null}
+ *
+ * @deprecated Use {@link Header.Builder#addEndHeaderAction(Action) for each action}
*/
+ @Deprecated
@NonNull
public Builder setActionStrip(@NonNull ActionStrip actionStrip) {
ACTIONS_CONSTRAINTS_SIMPLE.validateOrThrow(requireNonNull(actionStrip).getActions());
@@ -442,7 +514,6 @@
*/
@ExperimentalCarApi
@NonNull
- @RequiresCarApi(7)
public Builder setItemSize(@ItemSize int gridItemSize) {
mItemSize = gridItemSize;
return this;
@@ -458,13 +529,40 @@
*/
@ExperimentalCarApi
@NonNull
- @RequiresCarApi(7)
public Builder setItemImageShape(@ItemImageShape int itemImageShape) {
mItemImageShape = itemImageShape;
return this;
}
/**
+ * Sets the {@link Header} for this template.
+ *
+ * <p>The end header actions will show up differently inside vs outside of a map template.
+ * See {@link Header.Builder#addEndHeaderAction} for more details.</p>
+ *
+ * @throws NullPointerException if {@code header} is null
+ */
+ @NonNull
+ @RequiresCarApi(7)
+ public Builder setHeader(@NonNull Header header) {
+ if (header.getStartHeaderAction() != null) {
+ mHeaderAction = header.getStartHeaderAction();
+ }
+ if (header.getTitle() != null) {
+ mTitle = header.getTitle();
+ }
+ if (!header.getEndHeaderActions().isEmpty()) {
+ ActionStrip.Builder actionStripBuilder = new ActionStrip.Builder();
+ for (Action action: header.getEndHeaderActions()) {
+ actionStripBuilder.addAction(action);
+ }
+ mActionStrip = actionStripBuilder.build();
+ }
+ mHeader = header;
+ return this;
+ }
+
+ /**
* Constructs the template defined by this builder.
*
* <h4>Requirements</h4>
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java
index cb8d752..fb0db24 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java
@@ -71,23 +71,51 @@
static final int MAX_MESSAGES_PER_CONVERSATION = 10;
private final boolean mIsLoading;
+ /**
+ * @deprecated use {@link Header.Builder#setTitle(CarText)}; mHeader replaces the need
+ * for this field.
+ */
+ @Deprecated
@Nullable
private final CarText mTitle;
+ /**
+ * @deprecated use {@link Header.Builder#setStartHeaderAction(Action)}; mHeader replaces the
+ * need for this field.
+ */
+ @Deprecated
@Nullable
private final Action mHeaderAction;
@Nullable
private final ItemList mSingleList;
private final List<SectionedItemList> mSectionedLists;
+ /**
+ * @deprecated use {@link Header.Builder#addEndHeaderAction(Action)} for each action; mHeader
+ * replaces the need for this field.
+ */
+ @Deprecated
@Nullable
private final ActionStrip mActionStrip;
private final List<Action> mActions;
/**
+ * Represents a Header object to set the startHeaderAction, the title and the endHeaderActions
+ *
+ * @see ListTemplate.Builder#setHeader(Header)
+ */
+ @Nullable
+ @RequiresCarApi(7)
+ private final Header mHeader;
+
+
+ /**
* Returns the title of the template or {@code null} if not set.
*
* @see Builder#setTitle(CharSequence)
+ *
+ * @deprecated use {@link Header#getTitle()} instead.
*/
+ @Deprecated
@Nullable
public CarText getTitle() {
return mTitle;
@@ -98,7 +126,10 @@
* {@code null} if not set.
*
* @see Builder#setHeaderAction(Action)
+ *
+ * @deprecated use {@link Header#getStartHeaderAction()} instead.
*/
+ @Deprecated
@Nullable
public Action getHeaderAction() {
return mHeaderAction;
@@ -108,7 +139,10 @@
* Returns the {@link ActionStrip} for this template or {@code null} if not set.
*
* @see Builder#setActionStrip(ActionStrip)
+ *
+ * @deprecated use {@link Header#getEndHeaderActions()} instead.
*/
+ @Deprecated
@Nullable
public ActionStrip getActionStrip() {
return mActionStrip;
@@ -155,6 +189,37 @@
return mActions;
}
+ /**
+ * Returns the {@link Header} to display in this template.
+ *
+ * <p>This method was introduced in API 7, but is backwards compatible even if the client is
+ * using API 6 or below. </p>
+ *
+ * @see ListTemplate.Builder#setHeader(Header)
+ */
+ @Nullable
+ public Header getHeader() {
+ if (mHeader != null) {
+ return mHeader;
+ }
+ if (mTitle == null && mHeaderAction == null && mActionStrip == null) {
+ return null;
+ }
+ Header.Builder headerBuilder = new Header.Builder();
+ if (mTitle != null) {
+ headerBuilder.setTitle(mTitle);
+ }
+ if (mHeaderAction != null) {
+ headerBuilder.setStartHeaderAction(mHeaderAction);
+ }
+ if (mActionStrip != null) {
+ for (Action action: mActionStrip.getActions()) {
+ headerBuilder.addEndHeaderAction(action);
+ }
+ }
+ return headerBuilder.build();
+ }
+
@NonNull
@Override
public String toString() {
@@ -164,7 +229,7 @@
@Override
public int hashCode() {
return Objects.hash(mIsLoading, mTitle, mHeaderAction, mSingleList, mSectionedLists,
- mActionStrip);
+ mActionStrip, mHeader);
}
@Override
@@ -183,7 +248,8 @@
&& Objects.equals(mSingleList, otherTemplate.mSingleList)
&& Objects.equals(mSectionedLists, otherTemplate.mSectionedLists)
&& Objects.equals(mActionStrip, otherTemplate.mActionStrip)
- && Objects.equals(mActions, otherTemplate.mActions);
+ && Objects.equals(mActions, otherTemplate.mActions)
+ && Objects.equals(mHeader, otherTemplate.mHeader);
}
ListTemplate(Builder builder) {
@@ -194,6 +260,7 @@
mSectionedLists = CollectionUtils.unmodifiableCopy(builder.mSectionedLists);
mActionStrip = builder.mActionStrip;
mActions = CollectionUtils.unmodifiableCopy(builder.mActions);
+ mHeader = builder.mHeader;
}
/** Constructs an empty instance, used by serialization code. */
@@ -205,6 +272,7 @@
mSectionedLists = Collections.emptyList();
mActionStrip = null;
mActions = Collections.emptyList();
+ mHeader = null;
}
/**
@@ -231,6 +299,8 @@
boolean mHasSelectableList;
final List<Action> mActions;
+ @Nullable
+ Header mHeader;
/**
* Sets whether the template is in a loading state.
@@ -263,7 +333,10 @@
* @throws IllegalArgumentException if {@code headerAction} does not meet the template's
* requirements
* @throws NullPointerException if {@code headerAction} is {@code null}
+ *
+ * @deprecated Use {@link Header.Builder#setStartHeaderAction(Action)}
*/
+ @Deprecated
@NonNull
public Builder setHeaderAction(@NonNull Action headerAction) {
ACTIONS_CONSTRAINTS_HEADER.validateOrThrow(
@@ -282,7 +355,10 @@
*
* @throws NullPointerException if {@code title} is null
* @throws IllegalArgumentException if {@code title} contains unsupported spans
+ *
+ * @deprecated Use {@link Header.Builder#setTitle(CarText)}
*/
+ @Deprecated
@NonNull
public Builder setTitle(@NonNull CharSequence title) {
mTitle = CarText.create(requireNonNull(title));
@@ -377,7 +453,10 @@
*
* @throws IllegalArgumentException if {@code actionStrip} does not meet the requirements
* @throws NullPointerException if {@code actionStrip} is {@code null}
+ *
+ * @deprecated Use {@link Header.Builder#addEndHeaderAction(Action) for each action}
*/
+ @Deprecated
@NonNull
public Builder setActionStrip(@NonNull ActionStrip actionStrip) {
ACTIONS_CONSTRAINTS_SIMPLE.validateOrThrow(requireNonNull(actionStrip).getActions());
@@ -405,6 +484,34 @@
}
/**
+ * Sets the {@link Header} for this template.
+ *
+ * <p>The end header actions will show up differently inside vs outside of a map template.
+ * See {@link Header.Builder#addEndHeaderAction} for more details.</p>
+ *
+ * @throws NullPointerException if {@code header} is null
+ */
+ @NonNull
+ @RequiresCarApi(7)
+ public Builder setHeader(@NonNull Header header) {
+ if (header.getStartHeaderAction() != null) {
+ mHeaderAction = header.getStartHeaderAction();
+ }
+ if (header.getTitle() != null) {
+ mTitle = header.getTitle();
+ }
+ if (!header.getEndHeaderActions().isEmpty()) {
+ ActionStrip.Builder actionStripBuilder = new ActionStrip.Builder();
+ for (Action action: header.getEndHeaderActions()) {
+ actionStripBuilder.addAction(action);
+ }
+ mActionStrip = actionStripBuilder.build();
+ }
+ mHeader = header;
+ return this;
+ }
+
+ /**
* Constructs the template defined by this builder.
*
* <h4>Requirements</h4>
@@ -470,6 +577,7 @@
mActionStrip = listTemplate.getActionStrip();
mActions = new ArrayList<>(listTemplate.getActions());
+ mHeader = listTemplate.getHeader();
}
}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
index 28cc786..9aba8fd 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
@@ -103,15 +103,11 @@
/**
* Returns the title of the template or {@code null} if not set.
*
- * @deprecated use {@link Header.Builder#setTitle(CarText)}; mHeader replaces the need
- * for this field.
+ * @deprecated use {@link Header#getTitle()} instead.
*/
@Deprecated
@Nullable
public CarText getTitle() {
- if (mHeader != null && mHeader.getTitle() != null) {
- return mHeader.getTitle();
- }
return mTitle;
}
@@ -119,35 +115,23 @@
* Returns the {@link Action} that is set to be displayed in the header of the template, or
* {@code null} if not set.
*
- * @deprecated use {@link Header.Builder#setStartHeaderAction(Action)}; mHeader replaces the
- * need for this field.
+ * @deprecated use {@link Header#getStartHeaderAction()} instead.
*/
@Deprecated
@Nullable
public Action getHeaderAction() {
- if (mHeader != null && mHeader.getStartHeaderAction() != null) {
- return mHeader.getStartHeaderAction();
- }
return mHeaderAction;
}
/**
* Returns the {@link ActionStrip} for this template or {@code null} if not set.
*
- * @deprecated use {@link Header.Builder#addEndHeaderAction(Action) for each action}; mHeader
- * replaces the need for this field.
+ * @deprecated use {@link Header#getEndHeaderActions()} instead.
*/
@Deprecated
@RequiresCarApi(2)
@Nullable
public ActionStrip getActionStrip() {
- if (mHeader != null && !mHeader.getEndHeaderActions().isEmpty()) {
- ActionStrip.Builder actionStripBuilder = new ActionStrip.Builder();
- for (Action action: mHeader.getEndHeaderActions()) {
- actionStripBuilder.addAction(action);
- }
- return actionStripBuilder.build();
- }
return mActionStrip;
}
@@ -195,12 +179,32 @@
/**
* Returns the {@link Header} to display in this template.
*
- * @see Builder#setHeader(Header)
+ * <p>This method was introduced in API 7, but is backwards compatible even if the client is
+ * using API 6 or below. </p>
+ *
+ * @see MessageTemplate.Builder#setHeader(Header)
*/
- @RequiresCarApi(7)
@Nullable
public Header getHeader() {
- return mHeader;
+ if (mHeader != null) {
+ return mHeader;
+ }
+ if (mTitle == null && mHeaderAction == null && mActionStrip == null) {
+ return null;
+ }
+ Header.Builder headerBuilder = new Header.Builder();
+ if (mTitle != null) {
+ headerBuilder.setTitle(mTitle);
+ }
+ if (mHeaderAction != null) {
+ headerBuilder.setStartHeaderAction(mHeaderAction);
+ }
+ if (mActionStrip != null) {
+ for (Action action: mActionStrip.getActions()) {
+ headerBuilder.addEndHeaderAction(action);
+ }
+ }
+ return headerBuilder.build();
}
@NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java
index bacbf416..4950577 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java
@@ -91,15 +91,11 @@
*
* @see Builder#setTitle(CharSequence)
*
- * @deprecated use {@link Header.Builder#setTitle(CarText)}; mHeader replaces the need
- * for this field.
+ * @deprecated use {@link Header#getTitle()} instead.
*/
@Deprecated
@Nullable
public CarText getTitle() {
- if (mHeader != null && mHeader.getTitle() != null) {
- return mHeader.getTitle();
- }
return mTitle;
}
@@ -109,15 +105,11 @@
*
* @see Builder#setHeaderAction(Action)
*
- * @deprecated use {@link Header.Builder#setStartHeaderAction(Action)}; mHeader replaces the
- * need for this field.
+ * @deprecated use {@link Header#getStartHeaderAction()} instead.
*/
@Deprecated
@Nullable
public Action getHeaderAction() {
- if (mHeader != null && mHeader.getStartHeaderAction() != null) {
- return mHeader.getStartHeaderAction();
- }
return mHeaderAction;
}
@@ -126,19 +118,11 @@
*
* @see Builder#setActionStrip(ActionStrip)
*
- * @deprecated use {@link Header.Builder#addEndHeaderAction(Action) for each action}; mHeader
- * replaces the need for this field.
+ * @deprecated use {@link Header#getEndHeaderActions()} instead.
*/
@Deprecated
@Nullable
public ActionStrip getActionStrip() {
- if (mHeader != null && !mHeader.getEndHeaderActions().isEmpty()) {
- ActionStrip.Builder actionStripBuilder = new ActionStrip.Builder();
- for (Action action: mHeader.getEndHeaderActions()) {
- actionStripBuilder.addAction(action);
- }
- return actionStripBuilder.build();
- }
return mActionStrip;
}
@@ -155,12 +139,32 @@
/**
* Returns the {@link Header} to display in this template.
*
+ * <p>This method was introduced in API 7, but is backwards compatible even if the client is
+ * using API 6 or below. </p>
+ *
* @see PaneTemplate.Builder#setHeader(Header)
*/
- @RequiresCarApi(7)
@Nullable
public Header getHeader() {
- return mHeader;
+ if (mHeader != null) {
+ return mHeader;
+ }
+ if (mTitle == null && mHeaderAction == null && mActionStrip == null) {
+ return null;
+ }
+ Header.Builder headerBuilder = new Header.Builder();
+ if (mTitle != null) {
+ headerBuilder.setTitle(mTitle);
+ }
+ if (mHeaderAction != null) {
+ headerBuilder.setStartHeaderAction(mHeaderAction);
+ }
+ if (mActionStrip != null) {
+ for (Action action: mActionStrip.getActions()) {
+ headerBuilder.addEndHeaderAction(action);
+ }
+ }
+ return headerBuilder.build();
}
@NonNull
diff --git a/car/app/app/src/test/java/androidx/car/app/messaging/model/PersonsEqualityHelperTest.java b/car/app/app/src/test/java/androidx/car/app/messaging/model/PersonsEqualityHelperTest.java
deleted file mode 100644
index eb3791c..0000000
--- a/car/app/app/src/test/java/androidx/car/app/messaging/model/PersonsEqualityHelperTest.java
+++ /dev/null
@@ -1,115 +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.car.app.messaging.model;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.net.Uri;
-
-import androidx.core.app.Person;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-/** Tests for {@link PersonsEqualityHelper}. */
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-public class PersonsEqualityHelperTest {
-
- @Test
- public void equalsAndHashCode_minimalPersons_areEqual() {
- Person person1 = createMinimalPerson();
- Person person2 = createMinimalPerson();
-
- assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isTrue();
- assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isEqualTo(
- PersonsEqualityHelper.getPersonHashCode(person2));
- }
-
- @Test
- public void equalsAndHashCode_nullPersons_areEqual() {
- assertThat(PersonsEqualityHelper.getPersonHashCode(null)).isEqualTo(
- PersonsEqualityHelper.getPersonHashCode(null));
- assertThat(PersonsEqualityHelper.arePersonsEqual(null, null)).isTrue();
- }
-
- @Test
- public void equalsAndHashCode_differentName_areNotEqual() {
- Person person1 = createMinimalPersonBuilder().setName("Person1").build();
- Person person2 = createMinimalPersonBuilder().setName("Person2").build();
-
- assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isFalse();
- assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isNotEqualTo(
- PersonsEqualityHelper.getPersonHashCode(person2));
- }
-
- @Test
- public void equalsAndHashCode_differentKey_areNotEqual() {
- Person person1 = createMinimalPersonBuilder().setKey("Person1").build();
- Person person2 = createMinimalPersonBuilder().setKey("Person2").build();
-
- assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isFalse();
- assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isNotEqualTo(
- PersonsEqualityHelper.getPersonHashCode(person2));
- }
-
- @Test
- public void equalsAndHashCode_differentUri_areNotEqual() {
- Uri uri1 =
- Uri.parse("http://foo.com/test/sender/uri1");
- Uri uri2 =
- Uri.parse("http://foo.com/test/sender/uri2");
- Person person1 = createMinimalPersonBuilder().setUri(
- uri1.toString()).build();
- Person person2 = createMinimalPersonBuilder().setName(
- uri2.toString()).build();
-
- assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isFalse();
- assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isNotEqualTo(
- PersonsEqualityHelper.getPersonHashCode(person2));
- }
-
- @Test
- public void equalsAndHashCode_differentBot_areNotEqual() {
- Person person1 = createMinimalPersonBuilder().setBot(true).build();
- Person person2 = createMinimalPersonBuilder().setBot(false).build();
-
- assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isFalse();
- assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isNotEqualTo(
- PersonsEqualityHelper.getPersonHashCode(person2));
- }
-
- @Test
- public void equalsAndHashCode_differentImportant_areNotEqual() {
- Person person1 = createMinimalPersonBuilder().setImportant(true).build();
- Person person2 = createMinimalPersonBuilder().setImportant(false).build();
-
- assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isFalse();
- assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isNotEqualTo(
- PersonsEqualityHelper.getPersonHashCode(person2));
- }
-
- private Person.Builder createMinimalPersonBuilder() {
- return new Person.Builder();
- }
-
- private Person createMinimalPerson() {
- return createMinimalPersonBuilder().build();
- }
-}
diff --git a/car/app/app/src/test/java/androidx/car/app/messaging/model/PersonsEqualityHelperTest.kt b/car/app/app/src/test/java/androidx/car/app/messaging/model/PersonsEqualityHelperTest.kt
new file mode 100644
index 0000000..ac02b92
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/messaging/model/PersonsEqualityHelperTest.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.car.app.messaging.model
+
+import android.net.Uri
+import androidx.core.app.Person
+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
+
+/** Tests for [PersonsEqualityHelper]. */
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+class PersonsEqualityHelperTest {
+ @Test
+ fun equalsAndHashCode_minimalPersons_areEqual() {
+ val person1 = createMinimalPerson()
+ val person2 = createMinimalPerson()
+
+ assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isTrue()
+ assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isEqualTo(
+ PersonsEqualityHelper.getPersonHashCode(person2)
+ )
+ }
+
+ @Test
+ fun equalsAndHashCode_nullPersons_areEqual() {
+ assertThat(PersonsEqualityHelper.getPersonHashCode(null)).isEqualTo(
+ PersonsEqualityHelper.getPersonHashCode(null)
+ )
+ assertThat(PersonsEqualityHelper.arePersonsEqual(null, null)).isTrue()
+ }
+
+ @Test
+ fun equalsAndHashCode_differentName_areNotEqual() {
+ val person1 = createMinimalPersonBuilder().setName("Person1").build()
+ val person2 = createMinimalPersonBuilder().setName("Person2").build()
+
+ assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isFalse()
+ assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isNotEqualTo(
+ PersonsEqualityHelper.getPersonHashCode(person2)
+ )
+ }
+
+ @Test
+ fun equalsAndHashCode_differentKey_areNotEqual() {
+ val person1 = createMinimalPersonBuilder().setKey("Person1").build()
+ val person2 = createMinimalPersonBuilder().setKey("Person2").build()
+
+ assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isFalse()
+ assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isNotEqualTo(
+ PersonsEqualityHelper.getPersonHashCode(person2)
+ )
+ }
+
+ @Test
+ fun equalsAndHashCode_differentUri_areNotEqual() {
+ val uri1 =
+ Uri.parse("http://foo.com/test/sender/uri1")
+ val uri2 =
+ Uri.parse("http://foo.com/test/sender/uri2")
+ val person1 = createMinimalPersonBuilder().setUri(
+ uri1.toString()
+ ).build()
+ val person2 = createMinimalPersonBuilder().setName(
+ uri2.toString()
+ ).build()
+
+ assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isFalse()
+ assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isNotEqualTo(
+ PersonsEqualityHelper.getPersonHashCode(person2)
+ )
+ }
+
+ @Test
+ fun equalsAndHashCode_differentBot_areNotEqual() {
+ val person1 = createMinimalPersonBuilder().setBot(true).build()
+ val person2 = createMinimalPersonBuilder().setBot(false).build()
+
+ assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isFalse()
+ assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isNotEqualTo(
+ PersonsEqualityHelper.getPersonHashCode(person2)
+ )
+ }
+
+ @Test
+ fun equalsAndHashCode_differentImportant_areNotEqual() {
+ val person1 = createMinimalPersonBuilder().setImportant(true).build()
+ val person2 = createMinimalPersonBuilder().setImportant(false).build()
+
+ assertThat(PersonsEqualityHelper.arePersonsEqual(person1, person2)).isFalse()
+ assertThat(PersonsEqualityHelper.getPersonHashCode(person1)).isNotEqualTo(
+ PersonsEqualityHelper.getPersonHashCode(person2)
+ )
+ }
+
+ private fun createMinimalPersonBuilder(): Person.Builder {
+ return Person.Builder()
+ }
+
+ private fun createMinimalPerson(): Person {
+ return createMinimalPersonBuilder().build()
+ }
+}
diff --git a/car/app/app/src/test/java/androidx/car/app/model/GridTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/GridTemplateTest.java
index 2e2a722..a250380 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/GridTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/GridTemplateTest.java
@@ -38,10 +38,15 @@
public void createInstance_emptyList_notLoading_throws() {
assertThrows(
IllegalStateException.class,
- () -> new GridTemplate.Builder().setTitle("Title").build());
+ () -> new GridTemplate.Builder()
+ .setHeader(new Header.Builder().setTitle("Title").build())
+ .build());
// Positive case
- new GridTemplate.Builder().setTitle("Title").setLoading(true).build();
+ new GridTemplate.Builder()
+ .setHeader(new Header.Builder().setTitle("Title").build())
+ .setLoading(true)
+ .build();
}
@Test
@@ -50,7 +55,7 @@
IllegalStateException.class,
() ->
new GridTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder().setTitle("Title").build())
.setLoading(true)
.setSingleList(TestUtils.getGridItemList(2))
.build());
@@ -61,9 +66,7 @@
GridTemplate template = new GridTemplate.Builder().setSingleList(
TestUtils.getGridItemList(2)).build();
- assertThat(template.getTitle()).isNull();
- assertThat(template.getActionStrip()).isNull();
- assertThat(template.getHeaderAction()).isNull();
+ assertThat(template.getHeader()).isNull();
}
@Test
@@ -71,19 +74,21 @@
CharSequence title = TestUtils.getCharSequenceWithColorSpan("Title");
assertThrows(
IllegalArgumentException.class,
- () -> new GridTemplate.Builder().setTitle(title));
+ () -> new GridTemplate.Builder()
+ .setHeader(new Header.Builder().setTitle(title).build()));
// DurationSpan and DistanceSpan do not throw
CharSequence title2 = TestUtils.getCharSequenceWithDistanceAndDurationSpans("Title");
- new GridTemplate.Builder().setTitle(title2).setSingleList(
- TestUtils.getGridItemList(2)).build();
+ new GridTemplate.Builder().setHeader(new Header.Builder().setTitle(title2).build())
+ .setSingleList(TestUtils.getGridItemList(2)).build();
}
@Test
public void createInstance_setSingleList() {
ItemList list = TestUtils.getGridItemList(2);
- GridTemplate template = new GridTemplate.Builder().setTitle("Title").setSingleList(
- list).build();
+ GridTemplate template = new GridTemplate.Builder()
+ .setHeader(new Header.Builder().setTitle("Title").build())
+ .setSingleList(list).build();
assertThat(template.getSingleList()).isEqualTo(list);
}
@@ -91,12 +96,12 @@
public void createInstance_setHeaderAction_invalidActionThrows() {
assertThrows(
IllegalArgumentException.class,
- () ->
- new GridTemplate.Builder()
- .setHeaderAction(
- new Action.Builder().setTitle("Action").setOnClickListener(
- () -> {
- }).build()));
+ () -> new GridTemplate.Builder()
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(new Action.Builder()
+ .setTitle("Action").setOnClickListener(() -> {})
+ .build())
+ .build()));
}
@Test
@@ -104,21 +109,24 @@
GridTemplate template =
new GridTemplate.Builder()
.setSingleList(TestUtils.getGridItemList(2))
- .setHeaderAction(Action.BACK)
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(Action.BACK)
+ .build())
.build();
- assertThat(template.getHeaderAction()).isEqualTo(Action.BACK);
+ assertThat(template.getHeader().getStartHeaderAction()).isEqualTo(Action.BACK);
}
@Test
- public void createInstance_setActionStrip() {
- ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
+ public void createInstance_setEndHeaderAction() {
GridTemplate template =
new GridTemplate.Builder()
.setSingleList(TestUtils.getGridItemList(2))
- .setTitle("Title")
- .setActionStrip(actionStrip)
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .addEndHeaderAction(Action.BACK)
+ .build())
.build();
- assertThat(template.getActionStrip()).isEqualTo(actionStrip);
+ assertThat(template.getHeader().getEndHeaderActions().get(0)).isEqualTo(Action.BACK);
}
@Test
@@ -129,7 +137,9 @@
GridTemplate template =
new GridTemplate.Builder()
.setSingleList(TestUtils.getGridItemList(2))
- .setHeaderAction(Action.BACK)
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(Action.BACK)
+ .build())
.addAction(customAction)
.build();
assertThat(template.getActions()).containsExactly(customAction);
@@ -222,7 +232,9 @@
public void createInstance_setItemSize() {
ItemList list = TestUtils.getGridItemList(2);
GridTemplate template = new GridTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .build())
.setSingleList(list)
.setItemSize(GridTemplate.ITEM_SIZE_LARGE)
.build();
@@ -233,7 +245,9 @@
@Test
public void createInstance_defaultItemSizeIsSmall() {
GridTemplate template = new GridTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .build())
.setLoading(true)
.build();
@@ -261,14 +275,16 @@
public void equals() {
ItemList itemList = new ItemList.Builder().build();
String title = "title";
- ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
+ Action backAction = Action.BACK;
GridTemplate template =
new GridTemplate.Builder()
.setSingleList(itemList)
- .setHeaderAction(Action.BACK)
- .setActionStrip(actionStrip)
- .setTitle(title)
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(backAction)
+ .setTitle(title)
+ .addEndHeaderAction(backAction)
+ .build())
.setItemSize(GridTemplate.ITEM_SIZE_MEDIUM)
.build();
@@ -276,9 +292,11 @@
.isEqualTo(
new GridTemplate.Builder()
.setSingleList(itemList)
- .setHeaderAction(Action.BACK)
- .setActionStrip(actionStrip)
- .setTitle(title)
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(backAction)
+ .setTitle(title)
+ .addEndHeaderAction(backAction)
+ .build())
.setItemSize(GridTemplate.ITEM_SIZE_MEDIUM)
.build());
}
@@ -288,12 +306,15 @@
ItemList itemList = new ItemList.Builder().build();
GridTemplate template =
- new GridTemplate.Builder().setTitle("Title 1").setSingleList(itemList).build();
+ new GridTemplate.Builder()
+ .setHeader(new Header.Builder().setTitle("Title").build())
+ .setSingleList(itemList)
+ .build();
assertThat(template)
.isNotEqualTo(
new GridTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder().setTitle("Title").build())
.setSingleList(
new ItemList.Builder().addItem(
new GridItem.Builder().setTitle("Title 2").setImage(
@@ -306,14 +327,17 @@
ItemList itemList = new ItemList.Builder().build();
GridTemplate template =
- new GridTemplate.Builder().setSingleList(itemList).setHeaderAction(
- Action.BACK).build();
+ new GridTemplate.Builder()
+ .setSingleList(itemList)
+ .setHeader(new Header.Builder().setStartHeaderAction(Action.BACK).build())
+ .build();
assertThat(template)
.isNotEqualTo(
new GridTemplate.Builder()
.setSingleList(itemList)
- .setHeaderAction(Action.APP_ICON)
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(Action.APP_ICON).build())
.build());
}
@@ -322,12 +346,14 @@
ItemList itemList = new ItemList.Builder().build();
String title = "title";
- GridTemplate template = new GridTemplate.Builder().setSingleList(itemList).setTitle(
- title).build();
+ GridTemplate template = new GridTemplate.Builder().setSingleList(itemList)
+ .setHeader(new Header.Builder().setTitle(title).build())
+ .build();
assertThat(template)
- .isNotEqualTo(new GridTemplate.Builder().setSingleList(itemList).setTitle(
- "foo").build());
+ .isNotEqualTo(new GridTemplate.Builder().setSingleList(itemList)
+ .setHeader(new Header.Builder().setTitle("foo").build())
+ .build());
}
@Test
@@ -338,18 +364,20 @@
GridTemplate template =
new GridTemplate.Builder()
.setSingleList(itemList)
- .setTitle(title)
- .setActionStrip(new ActionStrip.Builder().addAction(Action.BACK).build())
+ .setHeader(new Header.Builder()
+ .setTitle(title)
+ .addEndHeaderAction(Action.BACK)
+ .build())
.build();
assertThat(template)
.isNotEqualTo(
new GridTemplate.Builder()
.setSingleList(itemList)
- .setTitle(title)
- .setActionStrip(
- new ActionStrip.Builder().addAction(
- Action.APP_ICON).build())
+ .setHeader(new Header.Builder()
+ .setTitle(title)
+ .addEndHeaderAction(Action.APP_ICON)
+ .build())
.build());
}
diff --git a/car/app/app/src/test/java/androidx/car/app/model/ListTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/ListTemplateTest.java
index dfd43e4..19659af 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/ListTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/ListTemplateTest.java
@@ -42,10 +42,13 @@
public void createInstance_emptyList_notLoading_Throws() {
assertThrows(
IllegalStateException.class,
- () -> new ListTemplate.Builder().setTitle("Title").build());
+ () -> new ListTemplate.Builder()
+ .setHeader(new Header.Builder().setTitle("Title").build())
+ .build());
// Positive case
- new ListTemplate.Builder().setTitle("Title").setLoading(true).build();
+ new ListTemplate.Builder().setHeader(new Header.Builder()
+ .setTitle("Title").build()).setLoading(true).build();
}
@Test
@@ -54,7 +57,7 @@
IllegalStateException.class,
() ->
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder().setTitle("Title").build())
.setLoading(true)
.setSingleList(getList())
.build());
@@ -65,8 +68,9 @@
ItemList emptyList = new ItemList.Builder().build();
assertThrows(
IllegalArgumentException.class,
- () -> new ListTemplate.Builder().setTitle("Title").addSectionedList(
- SectionedItemList.create(emptyList,
+ () -> new ListTemplate.Builder()
+ .setHeader(new Header.Builder().setTitle("Title").build())
+ .addSectionedList(SectionedItemList.create(emptyList,
"header")).build());
}
@@ -74,9 +78,10 @@
public void addSectionedList_emptyHeader_throws() {
assertThrows(
IllegalArgumentException.class,
- () -> new ListTemplate.Builder().setTitle("Title").addSectionedList(
- SectionedItemList.create(getList(),
- "")).build());
+ () -> new ListTemplate.Builder()
+ .setHeader(new Header.Builder().setTitle("Title").build())
+ .addSectionedList(SectionedItemList.create(getList(), ""))
+ .build());
}
@Test
@@ -84,14 +89,14 @@
ItemList list =
new ItemList.Builder()
.addItem(new Row.Builder().setTitle("Title").build())
- .setOnItemsVisibilityChangedListener((start, end) -> {
- })
+ .setOnItemsVisibilityChangedListener((start, end) -> {})
.build();
assertThrows(
IllegalArgumentException.class,
- () -> new ListTemplate.Builder().setTitle("Title").addSectionedList(
- SectionedItemList.create(list,
- "header")).build());
+ () -> new ListTemplate.Builder()
+ .setHeader(new Header.Builder().setTitle("Title").build())
+ .addSectionedList(SectionedItemList.create(list, "header"))
+ .build());
}
@Test
@@ -105,14 +110,14 @@
IllegalArgumentException.class,
() ->
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder().setTitle("Title").build())
.setSingleList(
new ItemList.Builder().addItem(rowExceedsMaxTexts).build())
.build());
// Positive case.
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder().setTitle("Title").build())
.setSingleList(new ItemList.Builder().addItem(rowMeetingMaxTexts).build())
.build();
}
@@ -122,18 +127,22 @@
CharSequence title = TestUtils.getCharSequenceWithColorSpan("Title");
assertThrows(
IllegalArgumentException.class,
- () -> new ListTemplate.Builder().setTitle(title));
+ () -> new ListTemplate.Builder().setHeader(new Header.Builder()
+ .setTitle(title).build()));
// DurationSpan and DistanceSpan do not throw
CharSequence title2 = TestUtils.getCharSequenceWithDistanceAndDurationSpans("Title");
- new ListTemplate.Builder().setTitle(title2).setSingleList(getList()).build();
+ new ListTemplate.Builder().setHeader(new Header.Builder().setTitle(title2).build())
+ .setSingleList(getList()).build();
}
@Test
public void createInstance_setSingleList() {
ItemList list = getList();
- ListTemplate template = new ListTemplate.Builder().setTitle("Title").setSingleList(
- list).build();
+ ListTemplate template = new ListTemplate.Builder()
+ .setHeader(new Header.Builder().setTitle("Title").build())
+ .setSingleList(list)
+ .build();
assertThat(template.getSingleList()).isEqualTo(list);
assertThat(template.getSectionedLists()).isEmpty();
}
@@ -143,9 +152,7 @@
ItemList list = getList();
ListTemplate template = new ListTemplate.Builder().setSingleList(list).build();
- assertThat(template.getHeaderAction()).isNull();
- assertThat(template.getTitle()).isNull();
- assertThat(template.getActionStrip()).isNull();
+ assertThat(template.getHeader()).isNull();
}
@Test
@@ -154,7 +161,7 @@
ItemList list2 = getList();
ListTemplate template =
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder().setTitle("Title").build())
.addSectionedList(SectionedItemList.create(list1, "header1"))
.addSectionedList(SectionedItemList.create(list2, "header2"))
.build();
@@ -173,7 +180,7 @@
ItemList list3 = getList();
ListTemplate template =
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder().setTitle("Title").build())
.addSectionedList(SectionedItemList.create(list1, "header1"))
.addSectionedList(SectionedItemList.create(list2, "header2"))
.setSingleList(list3)
@@ -189,7 +196,7 @@
ItemList list3 = getList();
ListTemplate template =
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder().setTitle("Title").build())
.setSingleList(list1)
.addSectionedList(SectionedItemList.create(list2, "header1"))
.addSectionedList(SectionedItemList.create(list3, "header2"))
@@ -206,7 +213,7 @@
ListTemplate template =
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder().setTitle("Title").build())
.addSectionedList(SectionedItemList.create(list1, "header1"))
.addSectionedList(SectionedItemList.create(list2, "header2"))
.clearSectionedLists()
@@ -223,30 +230,37 @@
IllegalArgumentException.class,
() ->
new ListTemplate.Builder()
- .setHeaderAction(
- new Action.Builder().setTitle("Action").setOnClickListener(
- () -> {
- }).build()));
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(
+ new Action.Builder().setTitle("Action")
+ .setOnClickListener(() -> {})
+ .build())
+ .build()));
+
}
@Test
public void createInstance_setHeaderAction() {
ListTemplate template =
- new ListTemplate.Builder().setSingleList(getList()).setHeaderAction(
- Action.BACK).build();
- assertThat(template.getHeaderAction()).isEqualTo(Action.BACK);
+ new ListTemplate.Builder().setSingleList(getList())
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(Action.BACK)
+ .build())
+ .build();
+ assertThat(template.getHeader().getStartHeaderAction()).isEqualTo(Action.BACK);
}
@Test
- public void createInstance_setActionStrip() {
- ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
+ public void createInstance_setEndHeaderAction() {
ListTemplate template =
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .addEndHeaderAction(Action.BACK)
+ .build())
.setSingleList(getList())
- .setActionStrip(actionStrip)
.build();
- assertThat(template.getActionStrip()).isEqualTo(actionStrip);
+ assertThat(template.getHeader().getEndHeaderActions().get(0)).isEqualTo(Action.BACK);
}
@Test
@@ -366,12 +380,18 @@
ItemList itemList = new ItemList.Builder().build();
ListTemplate template =
- new ListTemplate.Builder().setTitle("Title").setSingleList(itemList).build();
+ new ListTemplate.Builder()
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .build())
+ .setSingleList(itemList).build();
assertThat(template)
.isNotEqualTo(
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .build())
.setSingleList(
new ItemList.Builder().addItem(
new Row.Builder().setTitle(
@@ -384,14 +404,19 @@
ItemList itemList = new ItemList.Builder().build();
ListTemplate template =
- new ListTemplate.Builder().setSingleList(itemList).setHeaderAction(
- Action.BACK).build();
+ new ListTemplate.Builder().setSingleList(itemList)
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(Action.BACK)
+ .build())
+ .build();
assertThat(template)
.isNotEqualTo(
new ListTemplate.Builder()
.setSingleList(itemList)
- .setHeaderAction(Action.APP_ICON)
+ .setHeader(new Header.Builder()
+ .setStartHeaderAction(Action.APP_ICON)
+ .build())
.build());
}
@@ -402,33 +427,40 @@
ListTemplate template =
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .addEndHeaderAction(Action.BACK)
+ .build())
.setSingleList(itemList)
- .setActionStrip(actionStrip)
.build();
assertThat(template)
.isNotEqualTo(
new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .addEndHeaderAction(Action.APP_ICON)
+ .build())
.setSingleList(itemList)
- .setActionStrip(
- new ActionStrip.Builder().addAction(
- Action.APP_ICON).build())
.build());
}
@Test
public void notEquals_differentTitle() {
ItemList itemList = new ItemList.Builder().build();
- String title = "title";
- ListTemplate template = new ListTemplate.Builder().setSingleList(itemList).setTitle(
- title).build();
+ ListTemplate template = new ListTemplate.Builder().setSingleList(itemList)
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .build())
+ .build();
assertThat(template)
- .isNotEqualTo(new ListTemplate.Builder().setSingleList(itemList).setTitle(
- "yo").build());
+ .isNotEqualTo(new ListTemplate.Builder().setSingleList(itemList)
+ .setHeader(new Header.Builder()
+ .setTitle("Title2")
+ .build())
+ .build());
}
@Test
@@ -458,28 +490,30 @@
@Test
public void toBuilder_fieldsCanBeOverwritten() {
ItemList itemList = new ItemList.Builder().build();
- ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
String title = "title";
ListTemplate listTemplate = new ListTemplate.Builder()
.setSingleList(itemList)
- .setActionStrip(actionStrip)
- .setHeaderAction(Action.BACK)
- .setTitle(title)
+ .setHeader(new Header.Builder()
+ .setTitle(title)
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(Action.BACK)
+ .build())
.build();
// Verify fields can be overwritten (no crash)
listTemplate.toBuilder()
.setSingleList(itemList)
- .setActionStrip(actionStrip)
- .setHeaderAction(Action.BACK)
- .setTitle(title)
+ .setHeader(new Header.Builder()
+ .setTitle(title)
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(Action.BACK)
+ .build())
.build();
}
@Test
public void build_addingMoreThanMaxAllowedItemsInSingleList_truncates() {
- ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
String title = "title";
ItemList.Builder itemListBuilder = new ItemList.Builder();
int moreThanMaxAllowedItems = ListTemplate.MAX_ALLOWED_ITEMS + 1;
@@ -488,9 +522,11 @@
}
ListTemplate listTemplate = new ListTemplate.Builder()
.setSingleList(itemListBuilder.build())
- .setActionStrip(actionStrip)
- .setHeaderAction(Action.BACK)
- .setTitle(title)
+ .setHeader(new Header.Builder()
+ .setTitle(title)
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(Action.BACK)
+ .build())
.build();
assertThat(listTemplate.getSingleList().getItems()).hasSize(ListTemplate.MAX_ALLOWED_ITEMS);
@@ -498,7 +534,6 @@
@Test
public void build_addingMoreThanMaxAllowedItemsInSectionedList_truncates() {
- ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
String title = "title";
ItemList.Builder firstListBuilder = new ItemList.Builder();
@@ -522,9 +557,11 @@
.addSectionedList(firstList)
.addSectionedList(secondList)
.addSectionedList(thirdList)
- .setActionStrip(actionStrip)
- .setHeaderAction(Action.BACK)
- .setTitle(title)
+ .setHeader(new Header.Builder()
+ .setTitle(title)
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(Action.BACK)
+ .build())
.build();
assertThat(listTemplate.getSectionedLists()).hasSize(2);
@@ -535,7 +572,6 @@
@Test
public void build_aLotOfConversationMessages_truncates() {
- ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
String title = "title";
ItemList.Builder builder = new ItemList.Builder();
@@ -580,9 +616,11 @@
// Build
ListTemplate listTemplate = new ListTemplate.Builder()
.setSingleList(builder.build())
- .setActionStrip(actionStrip)
- .setHeaderAction(Action.BACK)
- .setTitle(title)
+ .setHeader(new Header.Builder()
+ .setTitle(title)
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(Action.BACK)
+ .build())
.build();
// 11 conversations should have been saved with the last 12th being dropped
@@ -607,7 +645,6 @@
@Test
public void build_addingConversations_neverResultsInAnEmptyConversation() {
// Add 10 conversations with 8 messages each to fill 99 items
- ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
String title = "title";
ItemList.Builder builder = new ItemList.Builder();
@@ -628,9 +665,11 @@
// And try to build
ListTemplate listTemplate = new ListTemplate.Builder()
.setSingleList(builder.build())
- .setActionStrip(actionStrip)
- .setHeaderAction(Action.BACK)
- .setTitle(title)
+ .setHeader(new Header.Builder()
+ .setTitle(title)
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(Action.BACK)
+ .build())
.build();
// Assert that the last conversation was not added despite the item count only being 99
@@ -639,16 +678,17 @@
private static ListTemplate createFullyPopulatedListTemplate() {
ItemList itemList = new ItemList.Builder().build();
- ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
String title = "title";
CarIcon icon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
"ic_test_1");
return new ListTemplate.Builder()
.setSingleList(itemList)
- .setActionStrip(actionStrip)
- .setHeaderAction(Action.BACK)
- .setTitle(title)
+ .setHeader(new Header.Builder()
+ .setTitle(title)
+ .setStartHeaderAction(Action.BACK)
+ .addEndHeaderAction(Action.BACK)
+ .build())
.addAction(TestUtils.createAction(icon, CarColor.BLUE))
.build();
}
diff --git a/car/app/app/src/test/java/androidx/car/app/model/TemplateWrapperTest.java b/car/app/app/src/test/java/androidx/car/app/model/TemplateWrapperTest.java
index 37b7c39..d7fa267 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/TemplateWrapperTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/TemplateWrapperTest.java
@@ -30,8 +30,12 @@
@Test
public void createInstance() {
ListTemplate template =
- new ListTemplate.Builder().setTitle("Title").setSingleList(
- new ItemList.Builder().build()).build();
+ new ListTemplate.Builder()
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .build())
+ .setSingleList(new ItemList.Builder().build())
+ .build();
TemplateWrapper wrapper = TemplateWrapper.wrap(template);
assertThat(wrapper.getTemplate()).isEqualTo(template);
@@ -43,11 +47,19 @@
@Test
public void createInstance_thenUpdate() {
ListTemplate template =
- new ListTemplate.Builder().setTitle("Title").setSingleList(
- new ItemList.Builder().build()).build();
+ new ListTemplate.Builder()
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .build())
+ .setSingleList(new ItemList.Builder().build())
+ .build();
ListTemplate template2 =
- new ListTemplate.Builder().setTitle("Title").setSingleList(
- new ItemList.Builder().build()).build();
+ new ListTemplate.Builder()
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .build())
+ .setSingleList(new ItemList.Builder().build())
+ .build();
TemplateWrapper wrapper = TemplateWrapper.wrap(template);
String id = wrapper.getId();
@@ -77,8 +89,12 @@
@Test
public void copyOf() {
ListTemplate template =
- new ListTemplate.Builder().setTitle("Title").setSingleList(
- new ItemList.Builder().build()).build();
+ new ListTemplate.Builder()
+ .setHeader(new Header.Builder()
+ .setTitle("Title")
+ .build())
+ .setSingleList(new ItemList.Builder().build())
+ .build();
TemplateWrapper source = TemplateWrapper.wrap(template, "ID");
source.setCurrentTaskStep(45);
source.setRefresh(true);
diff --git a/car/app/app/src/test/java/androidx/car/app/navigation/model/MapWithContentTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/MapWithContentTemplateTest.java
index 8a01460..66d775d 100644
--- a/car/app/app/src/test/java/androidx/car/app/navigation/model/MapWithContentTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/MapWithContentTemplateTest.java
@@ -59,7 +59,7 @@
private static GridTemplate createGridTemplate() {
ItemList list = TestUtils.getGridItemList(2);
return new GridTemplate.Builder()
- .setTitle("Title")
+ .setHeader(new Header.Builder().setTitle("Title").build())
.setSingleList(list)
.build();
}
@@ -67,7 +67,10 @@
private static ListTemplate createListTemplate() {
Row row1 = new Row.Builder().setTitle("Bananas").build();
return new ListTemplate.Builder()
- .setTitle("Title")
+ .setHeader(
+ new Header.Builder()
+ .setTitle("Title")
+ .build())
.setSingleList(new ItemList.Builder().addItem(row1).build())
.build();
}
diff --git a/collection/collection-benchmark-kmp/build.gradle b/collection/collection-benchmark-kmp/build.gradle
index 6b84523..2c5b34e 100644
--- a/collection/collection-benchmark-kmp/build.gradle
+++ b/collection/collection-benchmark-kmp/build.gradle
@@ -21,7 +21,6 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import androidx.build.Publish
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -57,18 +56,16 @@
}
targets {
- if (KmpPlatformsKt.enableLinux(project) && HostManager.hostIsLinux) {
+ if (HostManager.hostIsLinux) {
register("linuxX64")
}
- if (KmpPlatformsKt.enableMac(project)) {
- if (HostManager.host == KonanTarget.MACOS_X64) {
- register("macosX64")
- }
+ if (HostManager.host == KonanTarget.MACOS_X64) {
+ register("macosX64")
+ }
- if (HostManager.host == KonanTarget.MACOS_ARM64) {
- register("macosArm64")
- }
+ if (HostManager.host == KonanTarget.MACOS_ARM64) {
+ register("macosArm64")
}
}
}
diff --git a/collection/collection-benchmark/build.gradle b/collection/collection-benchmark/build.gradle
index 09e05f5..d21fc8e 100644
--- a/collection/collection-benchmark/build.gradle
+++ b/collection/collection-benchmark/build.gradle
@@ -21,7 +21,6 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkConfig
@@ -35,8 +34,6 @@
id("androidx.benchmark.darwin")
}
-def macEnabled = KmpPlatformsKt.enableMac(project)
-
androidXMultiplatform {
android()
@@ -80,12 +77,10 @@
}
}
- if (macEnabled) {
- darwinMain {
- dependsOn(commonMain)
- dependencies {
- api(project(":benchmark:benchmark-darwin"))
- }
+ darwinMain {
+ dependsOn(commonMain)
+ dependencies {
+ api(project(":benchmark:benchmark-darwin"))
}
}
diff --git a/collection/collection/api/restricted_current.ignore b/collection/collection/api/restricted_current.ignore
deleted file mode 100644
index 627fb9c..0000000
--- a/collection/collection/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-AddedPackage: androidx.collection.internal:
- Added package androidx.collection.internal
diff --git a/collection/collection/build.gradle b/collection/collection/build.gradle
index 9d80dea..9c138bb 100644
--- a/collection/collection/build.gradle
+++ b/collection/collection/build.gradle
@@ -21,11 +21,9 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import org.jetbrains.kotlin.konan.target.Family
@@ -33,9 +31,6 @@
id("AndroidXPlugin")
}
-def macEnabled = KmpPlatformsKt.enableMac(project)
-def linuxEnabled = KmpPlatformsKt.enableLinux(project)
-
androidXMultiplatform {
jvm {
withJava()
@@ -75,26 +70,20 @@
}
}
- if (macEnabled || linuxEnabled) {
- nativeMain {
- dependsOn(commonMain)
- }
-
- nativeTest {
- dependsOn(commonTest)
- }
+ nativeMain {
+ dependsOn(commonMain)
}
- if (macEnabled) {
- darwinMain {
- dependsOn(nativeMain)
- }
+ nativeTest {
+ dependsOn(commonTest)
}
- if (linuxEnabled) {
- linuxMain {
- dependsOn(nativeMain)
- }
+ darwinMain {
+ dependsOn(nativeMain)
+ }
+
+ linuxMain {
+ dependsOn(nativeMain)
}
targets.all { target ->
diff --git a/compose/animation/animation-core/api/current.ignore b/compose/animation/animation-core/api/current.ignore
index 73fb4e0..33ae83f 100644
--- a/compose/animation/animation-core/api/current.ignore
+++ b/compose/animation/animation-core/api/current.ignore
@@ -1,15 +1,5 @@
// Baseline format: 1.0
ChangedType: androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig#at(T, int):
- Method androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig.at has changed return type from androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> to E (extends androidx.compose.animation.core.KeyframeBaseEntity<T>)
+ Method androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig.at has changed return type from androidx.compose.animation.core.KeyframesSpec.KeyframeEntity to androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>
ChangedType: androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig#atFraction(T, float):
- Method androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig.atFraction has changed return type from androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> to E (extends androidx.compose.animation.core.KeyframeBaseEntity<T>)
-
-
-DefaultValueChange: androidx.compose.animation.core.Animatable#animateDecay(T, androidx.compose.animation.core.DecayAnimationSpec<T>, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Animatable<T,V>,kotlin.Unit>, kotlin.coroutines.Continuation<? super androidx.compose.animation.core.AnimationResult<T,V>>) parameter #3:
- Attempted to remove default value from parameter arg4 in androidx.compose.animation.core.Animatable.animateDecay
-DefaultValueChange: androidx.compose.animation.core.Animatable#animateTo(T, androidx.compose.animation.core.AnimationSpec<T>, T, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Animatable<T,V>,kotlin.Unit>, kotlin.coroutines.Continuation<? super androidx.compose.animation.core.AnimationResult<T,V>>) parameter #4:
- Attempted to remove default value from parameter arg5 in androidx.compose.animation.core.Animatable.animateTo
-DefaultValueChange: androidx.compose.animation.core.SuspendAnimationKt#animateDecay(androidx.compose.animation.core.AnimationState<T,V>, androidx.compose.animation.core.DecayAnimationSpec<T>, boolean, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<T,V>,kotlin.Unit>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #4:
- Attempted to remove default value from parameter arg5 in androidx.compose.animation.core.SuspendAnimationKt.animateDecay
-DefaultValueChange: androidx.compose.animation.core.SuspendAnimationKt#animateTo(androidx.compose.animation.core.AnimationState<T,V>, T, androidx.compose.animation.core.AnimationSpec<T>, boolean, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<T,V>,kotlin.Unit>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #5:
- Attempted to remove default value from parameter arg6 in androidx.compose.animation.core.SuspendAnimationKt.animateTo
+ Method androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig.atFraction has changed return type from androidx.compose.animation.core.KeyframesSpec.KeyframeEntity to androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index dea379c..1332abc 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -76,8 +76,6 @@
}
public enum AnimationEndReason {
- method public static androidx.compose.animation.core.AnimationEndReason valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.animation.core.AnimationEndReason[] values();
enum_constant public static final androidx.compose.animation.core.AnimationEndReason BoundReached;
enum_constant public static final androidx.compose.animation.core.AnimationEndReason Finished;
}
@@ -547,8 +545,6 @@
}
public enum RepeatMode {
- method public static androidx.compose.animation.core.RepeatMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.animation.core.RepeatMode[] values();
enum_constant public static final androidx.compose.animation.core.RepeatMode Restart;
enum_constant public static final androidx.compose.animation.core.RepeatMode Reverse;
}
diff --git a/compose/animation/animation-core/api/restricted_current.ignore b/compose/animation/animation-core/api/restricted_current.ignore
index 73fb4e0..33ae83f 100644
--- a/compose/animation/animation-core/api/restricted_current.ignore
+++ b/compose/animation/animation-core/api/restricted_current.ignore
@@ -1,15 +1,5 @@
// Baseline format: 1.0
ChangedType: androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig#at(T, int):
- Method androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig.at has changed return type from androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> to E (extends androidx.compose.animation.core.KeyframeBaseEntity<T>)
+ Method androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig.at has changed return type from androidx.compose.animation.core.KeyframesSpec.KeyframeEntity to androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>
ChangedType: androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig#atFraction(T, float):
- Method androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig.atFraction has changed return type from androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> to E (extends androidx.compose.animation.core.KeyframeBaseEntity<T>)
-
-
-DefaultValueChange: androidx.compose.animation.core.Animatable#animateDecay(T, androidx.compose.animation.core.DecayAnimationSpec<T>, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Animatable<T,V>,kotlin.Unit>, kotlin.coroutines.Continuation<? super androidx.compose.animation.core.AnimationResult<T,V>>) parameter #3:
- Attempted to remove default value from parameter arg4 in androidx.compose.animation.core.Animatable.animateDecay
-DefaultValueChange: androidx.compose.animation.core.Animatable#animateTo(T, androidx.compose.animation.core.AnimationSpec<T>, T, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Animatable<T,V>,kotlin.Unit>, kotlin.coroutines.Continuation<? super androidx.compose.animation.core.AnimationResult<T,V>>) parameter #4:
- Attempted to remove default value from parameter arg5 in androidx.compose.animation.core.Animatable.animateTo
-DefaultValueChange: androidx.compose.animation.core.SuspendAnimationKt#animateDecay(androidx.compose.animation.core.AnimationState<T,V>, androidx.compose.animation.core.DecayAnimationSpec<T>, boolean, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<T,V>,kotlin.Unit>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #4:
- Attempted to remove default value from parameter arg5 in androidx.compose.animation.core.SuspendAnimationKt.animateDecay
-DefaultValueChange: androidx.compose.animation.core.SuspendAnimationKt#animateTo(androidx.compose.animation.core.AnimationState<T,V>, T, androidx.compose.animation.core.AnimationSpec<T>, boolean, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.AnimationScope<T,V>,kotlin.Unit>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #5:
- Attempted to remove default value from parameter arg6 in androidx.compose.animation.core.SuspendAnimationKt.animateTo
+ Method androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig.atFraction has changed return type from androidx.compose.animation.core.KeyframesSpec.KeyframeEntity to androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 4824942..17fd01b 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -76,8 +76,6 @@
}
public enum AnimationEndReason {
- method public static androidx.compose.animation.core.AnimationEndReason valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.animation.core.AnimationEndReason[] values();
enum_constant public static final androidx.compose.animation.core.AnimationEndReason BoundReached;
enum_constant public static final androidx.compose.animation.core.AnimationEndReason Finished;
}
@@ -547,8 +545,6 @@
}
public enum RepeatMode {
- method public static androidx.compose.animation.core.RepeatMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.animation.core.RepeatMode[] values();
enum_constant public static final androidx.compose.animation.core.RepeatMode Restart;
enum_constant public static final androidx.compose.animation.core.RepeatMode Reverse;
}
diff --git a/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/KeyframesSpecWithSplineBenchmark.kt b/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/KeyframesSpecWithSplineBenchmark.kt
index 1cf16dd..d8e196c 100644
--- a/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/KeyframesSpecWithSplineBenchmark.kt
+++ b/compose/animation/animation-core/benchmark/src/androidTest/java/androidx/compose/animation/core/benchmark/KeyframesSpecWithSplineBenchmark.kt
@@ -93,7 +93,7 @@
// Cal the first frame before measuring, to guarantee the spline interpolation is built
vectorized.getValueFromNanos(
// Avoid using a number that may match one of the given timestamps, it will prevent the
- // MonoSpline from initializing, an irrational number factor should work well
+ // MonoSpline from initializing, a number with periodic decimals should work well
playTimeNanos = playTimeNanosToEvaluate * 2 / 3L,
initialValue = initialVector,
targetValue = targetVector,
@@ -132,7 +132,7 @@
// Cal the first frame before measuring, to guarantee the spline interpolation is built
vectorized.getValueFromNanos(
// Avoid using a number that may match one of the given timestamps, it will prevent the
- // MonoSpline from initializing, an irrational number factor should work well
+ // MonoSpline from initializing, a number with periodic decimals should work well
playTimeNanos = playTimeNanosToEvaluate * 2 / 3L,
initialValue = initialVector,
targetValue = targetVector,
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index fbc84ec..2368951 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
+
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
@@ -40,7 +40,7 @@
sourceSets {
commonMain {
dependencies {
- implementation("androidx.compose.runtime:runtime:1.6.0")
+ implementation(project(":compose:runtime:runtime"))
implementation("androidx.compose.ui:ui:1.6.0")
implementation("androidx.compose.ui:ui-unit:1.6.0")
implementation(project(":compose:ui:ui-graphics"))
@@ -121,7 +121,7 @@
androidx {
name = "Compose Animation Core"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2019"
description = "Animation engine and animation primitives that are the building blocks of the Compose animation library"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/EasingTest.kt b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/EasingTest.kt
index 94f6240..f76a867 100644
--- a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/EasingTest.kt
+++ b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/EasingTest.kt
@@ -122,8 +122,8 @@
bitmap2.getPixels(p2, 0, bitmap2.width, 0, 0, bitmap2.width, bitmap2.height)
var count = 0
- for (x in 0 until bitmap1.width) {
- for (y in 0 until bitmap2.width) {
+ for (y in 0 until bitmap1.height) {
+ for (x in 0 until bitmap1.width) {
val index = y * bitmap1.width + x
val (r1, g1, b1, _) = p1[index]
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 7cb90dd..850ad269 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
@@ -25,8 +25,8 @@
@ExperimentalAnimationSpecApi
internal class MonoSpline(time: FloatArray, y: Array<FloatArray>) {
private val timePoints: FloatArray
- private val values: ArrayList<FloatArray>
- private val tangents: ArrayList<FloatArray>
+ private val values: Array<FloatArray>
+ private val tangents: Array<FloatArray>
private val isExtrapolate = true
private val slopeTemp: FloatArray
@@ -66,7 +66,7 @@
}
}
timePoints = time
- values = copyData(y)
+ values = y
tangents = tangent
}
@@ -74,76 +74,7 @@
* @param a number of arrays
* @param b dimension of Float arrays
*/
- private fun makeFloatArray(a: Int, b: Int): ArrayList<FloatArray> {
- val ret = ArrayList<FloatArray>() // new Float[a][b];
- for (i in 0 until a) {
- ret.add(FloatArray(b))
- }
- return ret
- }
-
- private fun copyData(y: Array<FloatArray>): ArrayList<FloatArray> {
- val ret = ArrayList<FloatArray>()
- ret.addAll(y)
- return ret
- }
-
- /**
- * Get the values of the splines at t.
- * v is an array of all values
- */
- fun getPos(t: Float, v: FloatArray) {
- val n = timePoints.size
- val dim = values[0].size
- if (isExtrapolate) {
- if (t <= timePoints[0]) {
- getSlope(timePoints[0], slopeTemp)
- for (j in 0 until dim) {
- v[j] = values[0][j] + (t - timePoints[0]) * slopeTemp[j]
- }
- return
- }
- if (t >= timePoints[n - 1]) {
- getSlope(timePoints[n - 1], slopeTemp)
- for (j in 0 until dim) {
- v[j] = values[n - 1][j] + (t - timePoints[n - 1]) * slopeTemp[j]
- }
- return
- }
- } else {
- if (t <= timePoints[0]) {
- for (j in 0 until dim) {
- v[j] = values[0][j]
- }
- return
- }
- if (t >= timePoints[n - 1]) {
- for (j in 0 until dim) {
- v[j] = values[n - 1][j]
- }
- return
- }
- }
- for (i in 0 until n - 1) {
- if (t == timePoints[i]) {
- for (j in 0 until dim) {
- v[j] = values[i][j]
- }
- }
- if (t < timePoints[i + 1]) {
- val h = timePoints[i + 1] - timePoints[i]
- val x = (t - 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] = interpolate(h, x, y1, y2, t1, t2)
- }
- return
- }
- }
- }
+ private fun makeFloatArray(a: Int, b: Int): Array<FloatArray> = Array(a) { FloatArray(b) }
/**
* get the value of the j'th spline at time t
@@ -183,49 +114,51 @@
}
/**
- * Populate the values of the spline at time [t] into the given AnimationVector [v].
+ * Populate the values of the spline at time [time] into the given AnimationVector [v].
+ *
+ * You may provide [index] to simplify searching for the correct keyframe for the given [time].
*/
- fun getPos(t: Float, v: AnimationVector) {
+ fun getPos(time: Float, v: AnimationVector, index: Int = 0) {
val n = timePoints.size
val dim = values[0].size
if (isExtrapolate) {
- if (t <= timePoints[0]) {
+ if (time <= timePoints[0]) {
getSlope(timePoints[0], slopeTemp)
for (j in 0 until dim) {
- v[j] = values[0][j] + (t - timePoints[0]) * slopeTemp[j]
+ v[j] = values[0][j] + (time - timePoints[0]) * slopeTemp[j]
}
return
}
- if (t >= timePoints[n - 1]) {
+ if (time >= timePoints[n - 1]) {
getSlope(timePoints[n - 1], slopeTemp)
for (j in 0 until dim) {
- v[j] = values[n - 1][j] + (t - timePoints[n - 1]) * slopeTemp[j]
+ v[j] = values[n - 1][j] + (time - timePoints[n - 1]) * slopeTemp[j]
}
return
}
} else {
- if (t <= timePoints[0]) {
+ if (time <= timePoints[0]) {
for (j in 0 until dim) {
v[j] = values[0][j]
}
return
}
- if (t >= timePoints[n - 1]) {
+ if (time >= timePoints[n - 1]) {
for (j in 0 until dim) {
v[j] = values[n - 1][j]
}
return
}
}
- for (i in 0 until n - 1) {
- if (t == timePoints[i]) {
+ for (i in index until n - 1) {
+ if (time == timePoints[i]) {
for (j in 0 until dim) {
v[j] = values[i][j]
}
}
- 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]
@@ -271,8 +204,10 @@
/**
* Populate the differential values of the spline at the given [time] in to the given
* AnimationVector [v].
+ *
+ * You may provide [index] to simplify searching for the correct keyframe for the given [time].
*/
- fun getSlope(time: Float, v: AnimationVector) {
+ fun getSlope(time: Float, v: AnimationVector, index: Int = 0) {
var t = time
val n = timePoints.size
val dim = values[0].size
@@ -281,7 +216,7 @@
} else if (t >= timePoints[n - 1]) {
t = timePoints[n - 1]
}
- for (i in 0 until n - 1) {
+ for (i in index until n - 1) {
if (t <= timePoints[i + 1]) {
val h = timePoints[i + 1] - timePoints[i]
val x = (t - timePoints[i]) / h
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 082e7a1..0bd8e17 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
@@ -37,12 +37,13 @@
private lateinit var times: FloatArray
// Objects for MonoSpline
- private lateinit var lastInitialValue: V
- private lateinit var lastTargetValue: V
private lateinit var monoSpline: MonoSpline
+ // [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 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) {
valueVector = initialValue.newInstance()
@@ -57,34 +58,45 @@
if (!::monoSpline.isInitialized ||
lastInitialValue != initialValue || lastTargetValue != targetValue
) {
+ val initialChanged = lastInitialValue != initialValue
+ val targetChanged = lastTargetValue != targetValue
lastInitialValue = initialValue
lastTargetValue = targetValue
val dimension = initialValue.size
- // TODO(b/292114811): Re-use objects, after the first pass, only the initial and target
- // may change, and only if the keyframes does not overwrite it
- val values = Array(timestamps.size) {
- when (val timestamp = timestamps[it]) {
- // Start (zero) and end (durationMillis) may not have been declared in keyframes
- 0 -> {
- if (!keyframes.contains(timestamp)) {
- FloatArray(dimension, initialValue::get)
- } else {
- FloatArray(dimension, keyframes[timestamp]!!.first::get)
+ if (!::values.isInitialized) {
+ 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)
+ 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)
+ // All other values are guaranteed to exist
+ else -> FloatArray(dimension, keyframes[timestamp]!!.first::get)
+ }
+ }
+ } 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)
+ }
+ if (targetChanged && !keyframes.contains(durationMillis)) {
+ values[timestamps.binarySearch(durationMillis)] =
+ FloatArray(dimension, targetValue::get)
}
}
monoSpline = MonoSpline(times, values)
@@ -110,10 +122,13 @@
init(initialValue, targetValue, initialVelocity)
- // TODO(b/292114811): Consider also passing the corresponding range index to avoid
- // the linear iteration within MonoSpline
+ // There's no promise on the nature of the given time, so we need to search for the correct
+ // time range at every call
+ val index = findEntryForTimeMillis(clampedPlayTime)
+
monoSpline.getPos(
- t = getEasedTimeSeconds(clampedPlayTime),
+ index = index,
+ time = getEasedTimeFromIndex(index, clampedPlayTime),
v = valueVector
)
return valueVector
@@ -126,17 +141,20 @@
initialVelocity: V
): V {
val playTimeMillis = playTimeNanos / MillisToNanos
- val clampedPlayTime = clampPlayTime(playTimeMillis)
- if (clampedPlayTime < 0L) {
+ val clampedPlayTime = clampPlayTime(playTimeMillis).toInt()
+ if (clampedPlayTime < 0) {
return initialVelocity
}
init(initialValue, targetValue, initialVelocity)
- // TODO(b/292114811): Consider also passing the corresponding range index to avoid
- // the linear iteration within MonoSpline
+ // There's no promise on the nature of the given time, so we need to search for the correct
+ // time range at every call
+ val index = findEntryForTimeMillis(clampedPlayTime)
+
monoSpline.getSlope(
- time = getEasedTimeSeconds(clampedPlayTime.toInt()),
+ index = index,
+ time = getEasedTimeFromIndex(index, clampedPlayTime),
v = velocityVector
)
return velocityVector
@@ -148,10 +166,10 @@
return keyframes[timestamp]?.second ?: LinearEasing
}
- private fun getEasedTimeSeconds(timeMillis: Int): Float {
- // There's no promise on the nature of the given time, so we need to search for the correct
- // time range at every call
- val index = findEntryForTimeMillis(timeMillis)
+ private fun getEasedTimeFromIndex(
+ index: Int,
+ timeMillis: Int
+ ): Float {
if (index >= timestamps.lastIndex) {
// Return the same value. This may only happen at the end of the animation.
return timeMillis.toFloat() / SecondsToMillis
diff --git a/compose/animation/animation-graphics/build.gradle b/compose/animation/animation-graphics/build.gradle
index 150d58b..81cfa24 100644
--- a/compose/animation/animation-graphics/build.gradle
+++ b/compose/animation/animation-graphics/build.gradle
@@ -43,7 +43,7 @@
api(project(":compose:animation:animation"))
api("androidx.compose.foundation:foundation-layout:1.6.0")
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
api("androidx.compose.ui:ui:1.6.0")
api("androidx.compose.ui:ui-geometry:1.6.0")
@@ -119,7 +119,7 @@
androidx {
name = "Compose Animation Graphics"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2021"
description = "Compose Animation Graphics Library for using animated-vector resources in Compose"
}
diff --git a/compose/animation/animation-tooling-internal/api/current.txt b/compose/animation/animation-tooling-internal/api/current.txt
index 9a3915a..f0e5901 100644
--- a/compose/animation/animation-tooling-internal/api/current.txt
+++ b/compose/animation/animation-tooling-internal/api/current.txt
@@ -24,8 +24,6 @@
}
public enum ComposeAnimationType {
- method public static androidx.compose.animation.tooling.ComposeAnimationType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.animation.tooling.ComposeAnimationType[] values();
enum_constant public static final androidx.compose.animation.tooling.ComposeAnimationType ANIMATABLE;
enum_constant public static final androidx.compose.animation.tooling.ComposeAnimationType ANIMATED_CONTENT;
enum_constant public static final androidx.compose.animation.tooling.ComposeAnimationType ANIMATED_VALUE;
diff --git a/compose/animation/animation-tooling-internal/api/restricted_current.txt b/compose/animation/animation-tooling-internal/api/restricted_current.txt
index 9a3915a..f0e5901 100644
--- a/compose/animation/animation-tooling-internal/api/restricted_current.txt
+++ b/compose/animation/animation-tooling-internal/api/restricted_current.txt
@@ -24,8 +24,6 @@
}
public enum ComposeAnimationType {
- method public static androidx.compose.animation.tooling.ComposeAnimationType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.animation.tooling.ComposeAnimationType[] values();
enum_constant public static final androidx.compose.animation.tooling.ComposeAnimationType ANIMATABLE;
enum_constant public static final androidx.compose.animation.tooling.ComposeAnimationType ANIMATED_CONTENT;
enum_constant public static final androidx.compose.animation.tooling.ComposeAnimationType ANIMATED_VALUE;
diff --git a/compose/animation/animation/api/current.ignore b/compose/animation/animation/api/current.ignore
deleted file mode 100644
index 460e952..0000000
--- a/compose/animation/animation/api/current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.compose.animation.AnimatedContentTransitionScope#getKeepUntilTransitionsFinished(androidx.compose.animation.ExitTransition.Companion):
- Added method androidx.compose.animation.AnimatedContentTransitionScope.getKeepUntilTransitionsFinished(androidx.compose.animation.ExitTransition.Companion)
-
-
-RemovedMethod: androidx.compose.animation.AnimatedContentTransitionScope#getHold(androidx.compose.animation.ExitTransition.Companion):
- Removed method androidx.compose.animation.AnimatedContentTransitionScope.getHold(androidx.compose.animation.ExitTransition.Companion)
diff --git a/compose/animation/animation/api/current.txt b/compose/animation/animation/api/current.txt
index 5662141..343fa22 100644
--- a/compose/animation/animation/api/current.txt
+++ b/compose/animation/animation/api/current.txt
@@ -90,8 +90,6 @@
}
@SuppressCompatibility @androidx.compose.animation.ExperimentalAnimationApi public enum EnterExitState {
- method public static androidx.compose.animation.EnterExitState valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.animation.EnterExitState[] values();
enum_constant public static final androidx.compose.animation.EnterExitState PostExit;
enum_constant public static final androidx.compose.animation.EnterExitState PreEnter;
enum_constant public static final androidx.compose.animation.EnterExitState Visible;
diff --git a/compose/animation/animation/api/restricted_current.ignore b/compose/animation/animation/api/restricted_current.ignore
deleted file mode 100644
index 460e952..0000000
--- a/compose/animation/animation/api/restricted_current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.compose.animation.AnimatedContentTransitionScope#getKeepUntilTransitionsFinished(androidx.compose.animation.ExitTransition.Companion):
- Added method androidx.compose.animation.AnimatedContentTransitionScope.getKeepUntilTransitionsFinished(androidx.compose.animation.ExitTransition.Companion)
-
-
-RemovedMethod: androidx.compose.animation.AnimatedContentTransitionScope#getHold(androidx.compose.animation.ExitTransition.Companion):
- Removed method androidx.compose.animation.AnimatedContentTransitionScope.getHold(androidx.compose.animation.ExitTransition.Companion)
diff --git a/compose/animation/animation/api/restricted_current.txt b/compose/animation/animation/api/restricted_current.txt
index 5662141..343fa22 100644
--- a/compose/animation/animation/api/restricted_current.txt
+++ b/compose/animation/animation/api/restricted_current.txt
@@ -90,8 +90,6 @@
}
@SuppressCompatibility @androidx.compose.animation.ExperimentalAnimationApi public enum EnterExitState {
- method public static androidx.compose.animation.EnterExitState valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.animation.EnterExitState[] values();
enum_constant public static final androidx.compose.animation.EnterExitState PostExit;
enum_constant public static final androidx.compose.animation.EnterExitState PreEnter;
enum_constant public static final androidx.compose.animation.EnterExitState Visible;
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index f8204bf..2f11344 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -43,7 +43,7 @@
api(project(":compose:animation:animation-core"))
api("androidx.compose.foundation:foundation-layout:1.6.0")
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
api("androidx.compose.ui:ui:1.6.0")
api("androidx.compose.ui:ui-geometry:1.6.0")
@@ -120,7 +120,7 @@
androidx {
name = "Compose Animation"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2019"
description = "Compose animation library"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt
index 739cbf2..eab846c 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposeBytecodeCodegenTest.kt
@@ -742,4 +742,42 @@
)
}
)
+
+ // regression test for https://youtrack.jetbrains.com/issue/KT-65791
+ @Test
+ fun testCrossinlineCapture() = testCompile(
+ """
+ import androidx.compose.runtime.*
+
+ @Composable
+ fun LazyColumn(
+ content: () -> Unit
+ ): Unit = TODO()
+
+ @Composable
+ inline fun Box(content: @Composable () -> Unit) {
+ content()
+ }
+
+ @Composable
+ inline fun ItemsPage(
+ crossinline itemContent: @Composable (Int) -> Unit,
+ ) {
+ Box {
+ LazyColumn {
+ val lambda: @Composable (item: Int) -> Unit = {
+ itemContent(it)
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun SearchResultScreen() {
+ ItemsPage(
+ itemContent = {},
+ )
+ }
+ """
+ )
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
index 54c4b6c..4063f88 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
@@ -677,4 +677,41 @@
fun foo(block: (Int) -> Int) = block(0)
""",
)
+
+ @Test
+ fun testCrossinlineCapture() = verifyGoldenComposeIrTransform(
+ extra = """
+ import androidx.compose.runtime.Composable
+
+ @Composable fun Lazy(content: () -> Unit) {}
+ @Composable inline fun Box(content: () -> Unit) {}
+ """,
+ source = """
+ import androidx.compose.runtime.Composable
+
+ @Composable inline fun Test(crossinline content: () -> Unit) {
+ Box {
+ Lazy {
+ val items = @Composable { content() }
+ }
+ }
+ }
+
+ @Composable inline fun TestComposable(crossinline content: @Composable () -> Unit) {
+ Box {
+ Lazy {
+ val items = @Composable { content() }
+ }
+ }
+ }
+
+ @Composable inline fun TestSuspend(crossinline content: suspend () -> Unit) {
+ Box {
+ Lazy {
+ val items = suspend { content() }
+ }
+ }
+ }
+ """
+ )
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/StabilityConfigurationParserTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/StabilityConfigurationParserTests.kt
index 9280230..fd9324b 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/StabilityConfigurationParserTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/StabilityConfigurationParserTests.kt
@@ -17,6 +17,7 @@
package androidx.compose.compiler.plugins.kotlin
import androidx.compose.compiler.plugins.kotlin.analysis.StabilityConfigParser
+import org.jetbrains.kotlin.config.CompilerConfiguration
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Test
@@ -125,3 +126,63 @@
""".trimIndent()
)
}
+
+private const val PATH_TO_CONFIG_FILES = "src/test/resources/testStabilityConfigFiles"
+class SingleStabilityConfigurationTest(useFir: Boolean) : AbstractIrTransformTest(useFir) {
+ override fun CompilerConfiguration.updateConfiguration() {
+ put(ComposeConfiguration.STABILITY_CONFIG_PATH_KEY,
+ listOf("$PATH_TO_CONFIG_FILES/config1.conf")
+ )
+ put(ComposeConfiguration.STRONG_SKIPPING_ENABLED_KEY, false)
+ }
+
+ @Test
+ fun testExternalTypeStable() = verifyGoldenComposeIrTransform(
+ source = """
+ import androidx.compose.runtime.Composable
+ import java.time.Instant
+
+ @Composable
+ fun SkippableComposable(list: List<String>) {
+ use(list)
+ }
+
+ @Composable
+ fun UnskippableComposable(instant: Instant) {
+ use(instant)
+ }
+ """.trimIndent(),
+ extra = """
+ fun use(foo: Any) {}
+ """.trimIndent()
+ )
+}
+
+class MultipleStabilityConfigurationTest(useFir: Boolean) : AbstractIrTransformTest(useFir) {
+ override fun CompilerConfiguration.updateConfiguration() {
+ put(ComposeConfiguration.STABILITY_CONFIG_PATH_KEY,
+ listOf(
+ "$PATH_TO_CONFIG_FILES/config1.conf",
+ "$PATH_TO_CONFIG_FILES/config2.conf"
+ )
+ )
+ put(ComposeConfiguration.STRONG_SKIPPING_ENABLED_KEY, false)
+ }
+
+ @Test
+ fun testExternalTypeStable() = verifyGoldenComposeIrTransform(
+ source = """
+ import androidx.compose.runtime.Composable
+ import java.time.Instant
+
+ @Composable
+ fun SkippableComposable(list: List<String>, instant: Instant) {
+ use(list)
+ use(instant)
+ }
+ """.trimIndent(),
+ extra = """
+ fun use(foo: Any) {}
+ """.trimIndent()
+ )
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = false\135.txt"
index 9874dcf..9412fca 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = false\135.txt"
@@ -28,14 +28,15 @@
traceEventStart(<>, %dirty, -1, <>)
}
Dialog({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+ %composer.startReplaceGroup(<>)
+ sourceInformation(%composer, "C:Test.kt")
%composer.startReplaceGroup(<>)
sourceInformation(%composer, "<Test(p...>")
if (false) {
Test(param, %composer, 0b1110 and %dirty)
}
%composer.endReplaceGroup()
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceGroup()
}, %composer, 0)
if (isTraceInProgress()) {
traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = true\135.txt"
index 9874dcf..9412fca 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testEarlyReturnFromCrossInlinedLambda\133useFir = true\135.txt"
@@ -28,14 +28,15 @@
traceEventStart(<>, %dirty, -1, <>)
}
Dialog({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+ %composer.startReplaceGroup(<>)
+ sourceInformation(%composer, "C:Test.kt")
%composer.startReplaceGroup(<>)
sourceInformation(%composer, "<Test(p...>")
if (false) {
Test(param, %composer, 0b1110 and %dirty)
}
%composer.endReplaceGroup()
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceGroup()
}, %composer, 0)
if (isTraceInProgress()) {
traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testCrossinlineCapture\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testCrossinlineCapture\133useFir = false\135.txt"
new file mode 100644
index 0000000..35a7575
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testCrossinlineCapture\133useFir = false\135.txt"
@@ -0,0 +1,92 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.Composable
+
+@Composable inline fun Test(crossinline content: () -> Unit) {
+ Box {
+ Lazy {
+ val items = @Composable { content() }
+ }
+ }
+}
+
+@Composable inline fun TestComposable(crossinline content: @Composable () -> Unit) {
+ Box {
+ Lazy {
+ val items = @Composable { content() }
+ }
+ }
+}
+
+@Composable inline fun TestSuspend(crossinline content: suspend () -> Unit) {
+ Box {
+ Lazy {
+ val items = suspend { content() }
+ }
+ }
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun Test(crossinline content: Function0<Unit>, %composer: Composer?, %changed: Int) {
+ sourceInformationMarkerStart(%composer, <>, "CC(Test)<Lazy>,<Box>:Test.kt")
+ Box({
+ Lazy({
+ val items = composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
+ if (%changed and 0b1011 != 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ content()
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ }
+ }, %composer, 0)
+ }, %composer, 0)
+ sourceInformationMarkerEnd(%composer)
+}
+@Composable
+fun TestComposable(crossinline content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
+ sourceInformationMarkerStart(%composer, <>, "CC(TestComposable)<Lazy>,<Box>:Test.kt")
+ Box({
+ Lazy({
+ val items = composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<conten...>:Test.kt")
+ if (%changed and 0b1011 != 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ content(%composer, 0)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ }
+ }, %composer, 0)
+ }, %composer, 0)
+ sourceInformationMarkerEnd(%composer)
+}
+@Composable
+fun TestSuspend(crossinline content: SuspendFunction0<Unit>, %composer: Composer?, %changed: Int) {
+ sourceInformationMarkerStart(%composer, <>, "CC(TestSuspend)<Lazy>,<Box>:Test.kt")
+ Box({
+ Lazy({
+ val items = suspend {
+ content()
+ }
+ }, %composer, 0)
+ }, %composer, 0)
+ sourceInformationMarkerEnd(%composer)
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testCrossinlineCapture\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testCrossinlineCapture\133useFir = true\135.txt"
new file mode 100644
index 0000000..35a7575
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testCrossinlineCapture\133useFir = true\135.txt"
@@ -0,0 +1,92 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.Composable
+
+@Composable inline fun Test(crossinline content: () -> Unit) {
+ Box {
+ Lazy {
+ val items = @Composable { content() }
+ }
+ }
+}
+
+@Composable inline fun TestComposable(crossinline content: @Composable () -> Unit) {
+ Box {
+ Lazy {
+ val items = @Composable { content() }
+ }
+ }
+}
+
+@Composable inline fun TestSuspend(crossinline content: suspend () -> Unit) {
+ Box {
+ Lazy {
+ val items = suspend { content() }
+ }
+ }
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun Test(crossinline content: Function0<Unit>, %composer: Composer?, %changed: Int) {
+ sourceInformationMarkerStart(%composer, <>, "CC(Test)<Lazy>,<Box>:Test.kt")
+ Box({
+ Lazy({
+ val items = composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C:Test.kt")
+ if (%changed and 0b1011 != 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ content()
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ }
+ }, %composer, 0)
+ }, %composer, 0)
+ sourceInformationMarkerEnd(%composer)
+}
+@Composable
+fun TestComposable(crossinline content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
+ sourceInformationMarkerStart(%composer, <>, "CC(TestComposable)<Lazy>,<Box>:Test.kt")
+ Box({
+ Lazy({
+ val items = composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+ sourceInformation(%composer, "C<conten...>:Test.kt")
+ if (%changed and 0b1011 != 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ content(%composer, 0)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ }
+ }, %composer, 0)
+ }, %composer, 0)
+ sourceInformationMarkerEnd(%composer)
+}
+@Composable
+fun TestSuspend(crossinline content: SuspendFunction0<Unit>, %composer: Composer?, %changed: Int) {
+ sourceInformationMarkerStart(%composer, <>, "CC(TestSuspend)<Lazy>,<Box>:Test.kt")
+ Box({
+ Lazy({
+ val items = suspend {
+ content()
+ }
+ }, %composer, 0)
+ }, %composer, 0)
+ sourceInformationMarkerEnd(%composer)
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.MultipleStabilityConfigurationTest/testExternalTypeStable\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.MultipleStabilityConfigurationTest/testExternalTypeStable\133useFir = false\135.txt"
new file mode 100644
index 0000000..035e18e
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.MultipleStabilityConfigurationTest/testExternalTypeStable\133useFir = false\135.txt"
@@ -0,0 +1,43 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.Composable
+import java.time.Instant
+
+@Composable
+fun SkippableComposable(list: List<String>, instant: Instant) {
+ use(list)
+ use(instant)
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun SkippableComposable(list: List<String>, instant: Instant, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ val %dirty = %changed
+ if (%changed and 0b1110 == 0) {
+ %dirty = %dirty or if (%composer.changed(list)) 0b0100 else 0b0010
+ }
+ if (%changed and 0b01110000 == 0) {
+ %dirty = %dirty or if (%composer.changed(instant)) 0b00100000 else 0b00010000
+ }
+ if (%dirty and 0b01011011 != 0b00010010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %dirty, -1, <>)
+ }
+ use(list)
+ use(instant)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ SkippableComposable(list, instant, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.MultipleStabilityConfigurationTest/testExternalTypeStable\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.MultipleStabilityConfigurationTest/testExternalTypeStable\133useFir = true\135.txt"
new file mode 100644
index 0000000..035e18e
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.MultipleStabilityConfigurationTest/testExternalTypeStable\133useFir = true\135.txt"
@@ -0,0 +1,43 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.Composable
+import java.time.Instant
+
+@Composable
+fun SkippableComposable(list: List<String>, instant: Instant) {
+ use(list)
+ use(instant)
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun SkippableComposable(list: List<String>, instant: Instant, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ val %dirty = %changed
+ if (%changed and 0b1110 == 0) {
+ %dirty = %dirty or if (%composer.changed(list)) 0b0100 else 0b0010
+ }
+ if (%changed and 0b01110000 == 0) {
+ %dirty = %dirty or if (%composer.changed(instant)) 0b00100000 else 0b00010000
+ }
+ if (%dirty and 0b01011011 != 0b00010010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %dirty, -1, <>)
+ }
+ use(list)
+ use(instant)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ SkippableComposable(list, instant, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.SingleStabilityConfigurationTest/testExternalTypeStable\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.SingleStabilityConfigurationTest/testExternalTypeStable\133useFir = false\135.txt"
new file mode 100644
index 0000000..ab43314
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.SingleStabilityConfigurationTest/testExternalTypeStable\133useFir = false\135.txt"
@@ -0,0 +1,57 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.Composable
+import java.time.Instant
+
+@Composable
+fun SkippableComposable(list: List<String>) {
+ use(list)
+}
+
+@Composable
+fun UnskippableComposable(instant: Instant) {
+ use(instant)
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun SkippableComposable(list: List<String>, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ val %dirty = %changed
+ if (%changed and 0b1110 == 0) {
+ %dirty = %dirty or if (%composer.changed(list)) 0b0100 else 0b0010
+ }
+ if (%dirty and 0b1011 != 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %dirty, -1, <>)
+ }
+ use(list)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ SkippableComposable(list, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+}
+@Composable
+fun UnskippableComposable(instant: Instant, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ use(instant)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ UnskippableComposable(instant, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.SingleStabilityConfigurationTest/testExternalTypeStable\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.SingleStabilityConfigurationTest/testExternalTypeStable\133useFir = true\135.txt"
new file mode 100644
index 0000000..ab43314
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.SingleStabilityConfigurationTest/testExternalTypeStable\133useFir = true\135.txt"
@@ -0,0 +1,57 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.Composable
+import java.time.Instant
+
+@Composable
+fun SkippableComposable(list: List<String>) {
+ use(list)
+}
+
+@Composable
+fun UnskippableComposable(instant: Instant) {
+ use(instant)
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun SkippableComposable(list: List<String>, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ val %dirty = %changed
+ if (%changed and 0b1110 == 0) {
+ %dirty = %dirty or if (%composer.changed(list)) 0b0100 else 0b0010
+ }
+ if (%dirty and 0b1011 != 0b0010 || !%composer.skipping) {
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %dirty, -1, <>)
+ }
+ use(list)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ } else {
+ %composer.skipToGroupEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ SkippableComposable(list, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+}
+@Composable
+fun UnskippableComposable(instant: Instant, %composer: Composer?, %changed: Int) {
+ %composer = %composer.startRestartGroup(<>)
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ use(instant)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+ UnskippableComposable(instant, %composer, updateChangedFlags(%changed or 0b0001))
+ }
+}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/testStabilityConfigFiles/config1.conf b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/testStabilityConfigFiles/config1.conf
new file mode 100644
index 0000000..24a2375
--- /dev/null
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/testStabilityConfigFiles/config1.conf
@@ -0,0 +1 @@
+kotlin.collections.List
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/testStabilityConfigFiles/config2.conf b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/testStabilityConfigFiles/config2.conf
new file mode 100644
index 0000000..bdea905
--- /dev/null
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/testStabilityConfigFiles/config2.conf
@@ -0,0 +1 @@
+java.time.Instant
diff --git a/compose/compiler/compiler-hosted/runtime-tests/build.gradle b/compose/compiler/compiler-hosted/runtime-tests/build.gradle
index 99e4482..cb9c454 100644
--- a/compose/compiler/compiler-hosted/runtime-tests/build.gradle
+++ b/compose/compiler/compiler-hosted/runtime-tests/build.gradle
@@ -71,6 +71,12 @@
tasks.withType(KotlinCompile).configureEach {
pluginClasspath.from(composePluginFiles)
+ kotlinOptions {
+ freeCompilerArgs += [
+ "-P",
+ "plugin:androidx.compose.compiler.plugins.kotlin:nonSkippingGroupOptimization=true"
+ ]
+ }
}
androidx {
diff --git a/compose/compiler/compiler-hosted/runtime-tests/src/commonTest/kotlin/androidx/compose/compiler/test/CompositionTests.kt b/compose/compiler/compiler-hosted/runtime-tests/src/commonTest/kotlin/androidx/compose/compiler/test/CompositionTests.kt
index bd5a443..ff016ed 100644
--- a/compose/compiler/compiler-hosted/runtime-tests/src/commonTest/kotlin/androidx/compose/compiler/test/CompositionTests.kt
+++ b/compose/compiler/compiler-hosted/runtime-tests/src/commonTest/kotlin/androidx/compose/compiler/test/CompositionTests.kt
@@ -16,11 +16,15 @@
package androidx.compose.compiler.test
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mock.Text
import androidx.compose.runtime.mock.compositionTest
import androidx.compose.runtime.mock.validate
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
import kotlin.test.Test
class CompositionTests {
@@ -42,4 +46,43 @@
}
}
}
+
+ @Test
+ fun test_crossinline_indirect() = compositionTest {
+ val state = CrossInlineState()
+
+ compose {
+ state.place()
+ }
+
+ state.show {
+ val s = remember { "string" }
+ Text(s)
+ }
+ advance()
+ validate {
+ Text("string")
+ }
+
+ state.show {
+ val i = remember { 1 }
+ Text("$i")
+ }
+ advance()
+ validate {
+ Text("1")
+ }
+ }
+}
+
+class CrossInlineState(content: @Composable () -> Unit = { }) {
+ @PublishedApi
+ internal var content by mutableStateOf(content)
+
+ inline fun show(crossinline content: @Composable () -> Unit) {
+ this.content = { content() }
+ }
+
+ @Composable
+ fun place() { content() }
}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
index 78ed369..bbc589f 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
@@ -16,6 +16,7 @@
package androidx.compose.compiler.plugins.kotlin
+import androidx.compose.compiler.plugins.kotlin.analysis.FqNameMatcher
import androidx.compose.compiler.plugins.kotlin.analysis.StabilityConfigParser
import androidx.compose.compiler.plugins.kotlin.analysis.StabilityInferencer
import androidx.compose.compiler.plugins.kotlin.k1.ComposableCallChecker
@@ -76,7 +77,7 @@
val STRONG_SKIPPING_ENABLED_KEY =
CompilerConfigurationKey<Boolean>("Enable strong skipping mode")
val STABILITY_CONFIG_PATH_KEY =
- CompilerConfigurationKey<String>(
+ CompilerConfigurationKey<List<String>>(
"Path to stability configuration file"
)
val TRACE_MARKERS_ENABLED_KEY =
@@ -247,7 +248,7 @@
ComposeConfiguration.STRONG_SKIPPING_ENABLED_KEY,
value == "true"
)
- STABLE_CONFIG_PATH_OPTION -> configuration.put(
+ STABLE_CONFIG_PATH_OPTION -> configuration.appendList(
ComposeConfiguration.STABILITY_CONFIG_PATH_KEY,
value
)
@@ -374,7 +375,6 @@
@Suppress("OPT_IN_USAGE_ERROR")
TypeResolutionInterceptor.registerExtension(
project,
- @Suppress("IllegalExperimentalApiUsage")
ComposeTypeResolutionInterceptorExtension()
)
DescriptorSerializerPlugin.registerExtension(
@@ -429,9 +429,8 @@
false
)
- val stabilityConfigPath = configuration.get(
- ComposeConfiguration.STABILITY_CONFIG_PATH_KEY,
- ""
+ val stabilityConfigPaths = configuration.getList(
+ ComposeConfiguration.STABILITY_CONFIG_PATH_KEY
)
val traceMarkersEnabled = configuration.get(
ComposeConfiguration.TRACE_MARKERS_ENABLED_KEY,
@@ -439,14 +438,20 @@
)
val msgCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
- val stableTypeMatchers = try {
- StabilityConfigParser.fromFile(stabilityConfigPath).stableTypeMatchers
- } catch (e: Exception) {
- msgCollector?.report(
- CompilerMessageSeverity.ERROR,
- e.message ?: "Error parsing stability configuration"
- )
- emptySet()
+
+ val stableTypeMatchers = mutableSetOf<FqNameMatcher>()
+ for (i in stabilityConfigPaths.indices) {
+ val path = stabilityConfigPaths[i]
+ val matchers = try {
+ StabilityConfigParser.fromFile(path).stableTypeMatchers
+ } catch (e: Exception) {
+ msgCollector?.report(
+ CompilerMessageSeverity.ERROR,
+ e.message ?: "Error parsing stability configuration at $path"
+ )
+ emptySet()
+ }
+ stableTypeMatchers.addAll(matchers)
}
return ComposeIrGenerationExtension(
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
index 3e42f2d..886b1d4 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
@@ -132,6 +132,7 @@
12000 to "1.7.0-alpha01",
12100 to "1.7.0-alpha02",
12200 to "1.7.0-alpha03",
+ 12300 to "1.7.0-alpha04",
)
/**
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/StabilityConfigParser.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/StabilityConfigParser.kt
index e324d01..e8d4a2a 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/StabilityConfigParser.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/StabilityConfigParser.kt
@@ -26,8 +26,6 @@
if (filepath == null) return StabilityConfigParserImpl(emptyList())
val confFile = File(filepath)
- if (!confFile.exists()) return StabilityConfigParserImpl(emptyList())
-
return StabilityConfigParserImpl(confFile.readLines())
}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/StabilityExternalClassNameMatching.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/StabilityExternalClassNameMatching.kt
index 3e26cd3..6c1ca64 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/StabilityExternalClassNameMatching.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/analysis/StabilityExternalClassNameMatching.kt
@@ -208,6 +208,15 @@
}
}
+ override fun equals(other: Any?): Boolean {
+ val otherMatcher = other as? FqNameMatcher ?: return false
+ return this.pattern == otherMatcher.pattern
+ }
+
+ override fun hashCode(): Int {
+ return pattern.hashCode()
+ }
+
companion object {
private const val PATTERN_SINGLE_WILD = "\\w+"
private const val PATTERN_MULTI_WILD = "[\\w\\.]+"
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeTypeResolutionInterceptorExtension.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeTypeResolutionInterceptorExtension.kt
index 182f8ef..3b11e81 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeTypeResolutionInterceptorExtension.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposeTypeResolutionInterceptorExtension.kt
@@ -29,7 +29,7 @@
/**
* If a lambda is marked as `@Composable`, then the inferred type should become `@Composable`
*/
-@Suppress("INVISIBLE_REFERENCE", "EXPERIMENTAL_IS_NOT_ENABLED", "IllegalExperimentalApiUsage")
+@Suppress("INVISIBLE_REFERENCE", "EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(org.jetbrains.kotlin.extensions.internal.InternalNonStableExtensionPoints::class)
open class ComposeTypeResolutionInterceptorExtension : TypeResolutionInterceptorExtension {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index 4647e77..51fb33d 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -2444,7 +2444,8 @@
private fun IrContainerExpression.asSourceOrEarlyExitGroup(
scope: Scope.FunctionScope
): IrContainerExpression {
- if (scope.hasInlineEarlyReturn) {
+ val needsGroup = scope.hasInlineEarlyReturn || scope.isCrossinlineLambda
+ if (needsGroup) {
currentFunctionScope.metrics.recordGroup()
} else if (!collectSourceInformation) {
// If we are not generating source information and the lambda does not contain an
@@ -2456,7 +2457,7 @@
// the group, and we don't have to deal with any of the complicated jump logic that
// could be inside of the block
val makeStart = {
- if (scope.hasInlineEarlyReturn) irStartReplaceGroup(
+ if (needsGroup) irStartReplaceGroup(
this,
scope,
startOffset = startOffset,
@@ -2465,7 +2466,7 @@
else irSourceInformationMarkerStart(this, scope)
}
val makeEnd = {
- if (scope.hasInlineEarlyReturn) irEndReplaceGroup(scope = scope)
+ if (needsGroup) irEndReplaceGroup(scope = scope)
else irSourceInformationMarkerEnd(this, scope)
}
if (!scope.hasComposableCalls && !scope.hasReturn && !scope.hasJump) {
@@ -3916,6 +3917,8 @@
) : BlockScope("fun ${function.name.asString()}") {
val isInlinedLambda: Boolean
get() = transformer.inlineLambdaInfo.isInlineLambda(function)
+ val isCrossinlineLambda: Boolean
+ get() = transformer.inlineLambdaInfo.isCrossinlineLambda(function)
val inComposableCall: Boolean
get() = (parent as? Scope.CallScope)?.expression?.let { call ->
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index ef24c14..6b2c2e0 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -90,6 +90,7 @@
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.isFunctionOrKFunction
import org.jetbrains.kotlin.ir.util.isLocal
+import org.jetbrains.kotlin.ir.util.isSuspendFunctionOrKFunction
import org.jetbrains.kotlin.ir.util.kotlinFqName
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.ir.util.primaryConstructor
@@ -1129,11 +1130,16 @@
stabilityInferencer.stabilityOf(type).knownStable()
private fun IrValueDeclaration.isInlinedLambda(): Boolean =
- type.isFunctionOrKFunction() &&
+ isInlineableFunction() &&
this is IrValueParameter &&
(parent as? IrFunction)?.isInline == true &&
!isNoinline
+ private fun IrValueDeclaration.isInlineableFunction(): Boolean =
+ type.isFunctionOrKFunction() ||
+ type.isSyntheticComposableFunction() ||
+ type.isSuspendFunctionOrKFunction()
+
private fun <T : IrExpression> T.markAsStatic(mark: Boolean): T {
if (mark) {
// Mark it so the ComposableCallTransformer will insert the correct code around this
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrInlineReferenceLocator.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrInlineReferenceLocator.kt
index 8c72dac..5543726 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrInlineReferenceLocator.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrInlineReferenceLocator.kt
@@ -51,6 +51,9 @@
fun isInlineLambda(irFunction: IrFunction): Boolean =
irFunction.symbol in inlineLambdaToParameter.keys
+ fun isCrossinlineLambda(irFunction: IrFunction): Boolean =
+ inlineLambdaToParameter[irFunction.symbol]?.isCrossinline == true
+
fun isInlineFunctionExpression(expression: IrExpression): Boolean =
expression in inlineFunctionExpressions
diff --git a/compose/desktop/desktop/build.gradle b/compose/desktop/desktop/build.gradle
index 8f1e03b..a782d00 100644
--- a/compose/desktop/desktop/build.gradle
+++ b/compose/desktop/desktop/build.gradle
@@ -97,7 +97,7 @@
androidx {
name = "Compose Desktop"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
publish = Publish.SNAPSHOT_AND_RELEASE
inceptionYear = "2020"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/foundation/foundation-layout/api/current.ignore b/compose/foundation/foundation-layout/api/current.ignore
deleted file mode 100644
index 0092041..0000000
--- a/compose/foundation/foundation-layout/api/current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-RemovedMethod: androidx.compose.foundation.layout.WindowInsets_androidKt#getConsumeWindowInsets(androidx.compose.ui.platform.ComposeView):
- Removed method androidx.compose.foundation.layout.WindowInsets_androidKt.getConsumeWindowInsets(androidx.compose.ui.platform.ComposeView)
-RemovedMethod: androidx.compose.foundation.layout.WindowInsets_androidKt#setConsumeWindowInsets(androidx.compose.ui.platform.ComposeView, boolean):
- Removed method androidx.compose.foundation.layout.WindowInsets_androidKt.setConsumeWindowInsets(androidx.compose.ui.platform.ComposeView,boolean)
diff --git a/compose/foundation/foundation-layout/api/current.txt b/compose/foundation/foundation-layout/api/current.txt
index 4009002..dea6c5f 100644
--- a/compose/foundation/foundation-layout/api/current.txt
+++ b/compose/foundation/foundation-layout/api/current.txt
@@ -128,6 +128,14 @@
}
@SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowColumnScope extends androidx.compose.foundation.layout.FlowColumnScope {
+ method public int getIndexInLine();
+ method public int getLineIndex();
+ method public float getMaxHeightInLine();
+ method public float getMaxWidth();
+ property public abstract int indexInLine;
+ property public abstract int lineIndex;
+ property public abstract float maxHeightInLine;
+ property public abstract float maxWidth;
}
public final class ContextualFlowLayoutKt {
@@ -152,6 +160,14 @@
}
@SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowRowScope extends androidx.compose.foundation.layout.FlowRowScope {
+ method public int getIndexInLine();
+ method public int getLineIndex();
+ method public float getMaxHeight();
+ method public float getMaxWidthInLine();
+ property public abstract int indexInLine;
+ property public abstract int lineIndex;
+ property public abstract float maxHeight;
+ property public abstract float maxWidthInLine;
}
@SuppressCompatibility @kotlin.RequiresOptIn(message="The API of this layout is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalLayoutApi {
@@ -221,8 +237,6 @@
}
public enum IntrinsicSize {
- method public static androidx.compose.foundation.layout.IntrinsicSize valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.foundation.layout.IntrinsicSize[] values();
enum_constant public static final androidx.compose.foundation.layout.IntrinsicSize Max;
enum_constant public static final androidx.compose.foundation.layout.IntrinsicSize Min;
}
diff --git a/compose/foundation/foundation-layout/api/restricted_current.ignore b/compose/foundation/foundation-layout/api/restricted_current.ignore
deleted file mode 100644
index 0092041..0000000
--- a/compose/foundation/foundation-layout/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-RemovedMethod: androidx.compose.foundation.layout.WindowInsets_androidKt#getConsumeWindowInsets(androidx.compose.ui.platform.ComposeView):
- Removed method androidx.compose.foundation.layout.WindowInsets_androidKt.getConsumeWindowInsets(androidx.compose.ui.platform.ComposeView)
-RemovedMethod: androidx.compose.foundation.layout.WindowInsets_androidKt#setConsumeWindowInsets(androidx.compose.ui.platform.ComposeView, boolean):
- Removed method androidx.compose.foundation.layout.WindowInsets_androidKt.setConsumeWindowInsets(androidx.compose.ui.platform.ComposeView,boolean)
diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt
index 672b14f..b9d0fef 100644
--- a/compose/foundation/foundation-layout/api/restricted_current.txt
+++ b/compose/foundation/foundation-layout/api/restricted_current.txt
@@ -131,6 +131,14 @@
}
@SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowColumnScope extends androidx.compose.foundation.layout.FlowColumnScope {
+ method public int getIndexInLine();
+ method public int getLineIndex();
+ method public float getMaxHeightInLine();
+ method public float getMaxWidth();
+ property public abstract int indexInLine;
+ property public abstract int lineIndex;
+ property public abstract float maxHeightInLine;
+ property public abstract float maxWidth;
}
public final class ContextualFlowLayoutKt {
@@ -155,6 +163,14 @@
}
@SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ContextualFlowRowScope extends androidx.compose.foundation.layout.FlowRowScope {
+ method public int getIndexInLine();
+ method public int getLineIndex();
+ method public float getMaxHeight();
+ method public float getMaxWidthInLine();
+ property public abstract int indexInLine;
+ property public abstract int lineIndex;
+ property public abstract float maxHeight;
+ property public abstract float maxWidthInLine;
}
@SuppressCompatibility @kotlin.RequiresOptIn(message="The API of this layout is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalLayoutApi {
@@ -226,8 +242,6 @@
}
public enum IntrinsicSize {
- method public static androidx.compose.foundation.layout.IntrinsicSize valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.foundation.layout.IntrinsicSize[] values();
enum_constant public static final androidx.compose.foundation.layout.IntrinsicSize Max;
enum_constant public static final androidx.compose.foundation.layout.IntrinsicSize Min;
}
diff --git a/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/DeepRowColumnBenchmark.kt b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/DeepRowColumnBenchmark.kt
new file mode 100644
index 0000000..df4f78d
--- /dev/null
+++ b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/DeepRowColumnBenchmark.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.layout.benchmark
+
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkFirstLayout
+import androidx.compose.testutils.benchmark.benchmarkFirstMeasure
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Benchmark that runs [DeepRowColumnTestCase]. The purpose of this benchmark is to measure
+ * compose, measure, and layout performance of Row and Column, which are extremely common layouts.
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+class DeepRowColumnBenchmark(
+ private val depth: Int,
+ private val breadth: Int,
+ private val weight: Boolean,
+ private val align: Boolean,
+) {
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "depth={0}_breadth={1}_weight={2}_align={3}")
+ fun initParameters(): Array<Any> = arrayOf(
+ arrayOf(1, 3, true, false),
+ arrayOf(1, 3, false, true),
+ arrayOf(1, 3, false, false),
+ arrayOf(1, 100, false, false),
+ arrayOf(100, 1, false, false),
+ )
+ }
+
+ @get:Rule
+ val benchmarkRule = ComposeBenchmarkRule()
+
+ private val caseFactory = { DeepRowColumnTestCase(weight, align, depth, breadth) }
+
+ @Test
+ fun compose() {
+ benchmarkRule.benchmarkFirstCompose(caseFactory)
+ }
+
+ @Test
+ fun measure() {
+ benchmarkRule.benchmarkFirstMeasure(caseFactory)
+ }
+
+ @Test
+ fun layout() {
+ benchmarkRule.benchmarkFirstLayout(caseFactory)
+ }
+}
diff --git a/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/DeepRowColumnTestCase.kt b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/DeepRowColumnTestCase.kt
new file mode 100644
index 0000000..1cffdda
--- /dev/null
+++ b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/DeepRowColumnTestCase.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.layout.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Test case representing a layout hierarchy of nested rows and columns. The purpose of this
+ * benchmark is to measure compose, measure, and layout performance of Row and Column, which are
+ * extremely common layouts. The parameters can be used to change the nature of the hierarchy (wide
+ * vs deep, etc.) and stress different situations. The benchmark attempts to use different common
+ * arrangements/alignments as well as child modifiers such as weight and alignBy.
+ * @param useWeight If true, weight modifiers will be added to some children in the hierarchy
+ * @param useAlign If true, align modifiers will be added to some children in the hierarchy
+ * @param depth This is the depth of the resulting hierarchy. Be careful making this number too big
+ * as it will quickly increase the runtime of the benchmark.
+ * @param breadth This is the number of direct children each row/column has at each level of the
+ * hierarchy. Be careful making this number too big as it will quickly increase the runtime of the
+ * benchmark.
+ */
+class DeepRowColumnTestCase(
+ private val useWeight: Boolean,
+ private val useAlign: Boolean,
+ private val depth: Int,
+ private val breadth: Int,
+) : LayeredComposeTestCase() {
+
+ @Composable
+ override fun MeasuredContent() {
+ Row {
+ DeepTree(useWeight, useAlign, depth, breadth)
+ }
+ }
+}
+
+val blueBackground = Modifier.background(color = Color.Blue)
+val magentaBackground = Modifier.background(color = Color.Magenta)
+val blackBackground = Modifier.background(color = Color.Black)
+
+@Composable
+@NonRestartableComposable
+private fun Terminal(style: Int, modifier: Modifier = Modifier) {
+ val background = when (style) {
+ 0 -> blueBackground
+ 1 -> blackBackground
+ else -> magentaBackground
+ }
+ Box(modifier = modifier
+ .fillMaxSize()
+ .then(background))
+}
+
+private fun horizArrangementFor(id: Int) = when (id % 2) {
+ 0 -> Arrangement.Start
+ 1 -> Arrangement.End
+ else -> Arrangement.Center
+}
+
+private fun vertArrangementFor(id: Int) = when (id % 2) {
+ 0 -> Arrangement.Top
+ 1 -> Arrangement.Bottom
+ else -> Arrangement.Center
+}
+
+private fun vertAlignmentFor(id: Int) = when (id % 2) {
+ 0 -> Alignment.Top
+ 1 -> Alignment.CenterVertically
+ else -> Alignment.Bottom
+}
+
+private fun horizAlignmentFor(id: Int) = when (id % 2) {
+ 0 -> Alignment.Start
+ 1 -> Alignment.CenterHorizontally
+ else -> Alignment.End
+}
+
+private fun ColumnScope.modifierFor(id: Int, useWeight: Boolean, useAlign: Boolean): Modifier {
+ return if (useWeight && id == 0)
+ Modifier.weight(0.5f, true)
+ else if (useAlign && id == 0)
+ Modifier.align(Alignment.CenterHorizontally)
+ else
+ Modifier.fillMaxWidth()
+}
+
+private fun ColumnScope.terminalModifierFor(id: Int, useWeight: Boolean): Modifier {
+ return if (useWeight && id == 0)
+ Modifier.weight(0.5f, true)
+ else
+ Modifier
+}
+
+private fun RowScope.modifierFor(id: Int, useWeight: Boolean, useAlign: Boolean): Modifier {
+ return if (useWeight && id == 0)
+ Modifier.weight(0.5f, true)
+ else if (useAlign && id == 0)
+ Modifier.align(Alignment.CenterVertically)
+ else
+ Modifier.fillMaxHeight()
+}
+
+private fun RowScope.terminalModifierFor(id: Int, useWeight: Boolean): Modifier {
+ return if (useWeight && id == 0)
+ Modifier.weight(0.5f, true)
+ else
+ Modifier
+}
+
+@Composable
+@NonRestartableComposable
+private fun ColumnScope.DeepTree(
+ useWeight: Boolean,
+ useAlign: Boolean,
+ depth: Int,
+ breadth: Int,
+ id: Int = 0
+) {
+ Row(
+ modifier = modifierFor(id, useWeight, useAlign),
+ horizontalArrangement = horizArrangementFor(id),
+ verticalAlignment = vertAlignmentFor(id),
+ ) {
+ if (depth == 0) {
+ Terminal(
+ style = id % 3,
+ modifier = terminalModifierFor(id, useWeight),
+ )
+ } else {
+ repeat(breadth) {
+ DeepTree(useWeight, useAlign, depth - 1, breadth, it)
+ }
+ }
+ }
+}
+
+@Composable
+@NonRestartableComposable
+private fun RowScope.DeepTree(
+ useWeight: Boolean,
+ useAlign: Boolean,
+ depth: Int,
+ breadth: Int,
+ id: Int = 0
+) {
+ Column(
+ modifier = modifierFor(id, useWeight, useAlign),
+ verticalArrangement = vertArrangementFor(id),
+ horizontalAlignment = horizAlignmentFor(id),
+ ) {
+ if (depth == 0) {
+ Terminal(
+ style = id % 3,
+ modifier = terminalModifierFor(id, useWeight),
+ )
+ } else {
+ repeat(breadth) {
+ DeepTree(useWeight, useAlign, depth - 1, breadth, it)
+ }
+ }
+ }
+}
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 14a7610..7c6ca0e 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -42,7 +42,7 @@
implementation(libs.kotlinStdlibCommon)
api("androidx.compose.ui:ui:1.6.0")
- implementation("androidx.compose.runtime:runtime:1.6.0")
+ implementation(project(":compose:runtime:runtime"))
implementation(project(":compose:ui:ui-util"))
implementation("androidx.collection:collection:1.4.0")
implementation(project(":compose:ui:ui-unit"))
@@ -115,7 +115,7 @@
androidx {
name = "Compose Layouts"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2019"
description = "Compose layout implementations"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowColumnDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowColumnDemo.kt
index 07401d3..594bf45 100644
--- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowColumnDemo.kt
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowColumnDemo.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.samples.ContextualFlowColMaxLineDynamicSeeMore
+import androidx.compose.foundation.layout.samples.ContextualFlowColumn_ItemPosition
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
@@ -27,5 +28,6 @@
fun ContextualFlowColumnDemo() {
Column(Modifier.verticalScroll(rememberScrollState())) {
ContextualFlowColMaxLineDynamicSeeMore()
+ ContextualFlowColumn_ItemPosition()
}
}
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowRowDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowRowDemo.kt
index be0237b..099cfa6 100644
--- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowRowDemo.kt
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/ContextualFlowRowDemo.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.samples.ContextualFlowRowMaxLineDynamicSeeMore
+import androidx.compose.foundation.layout.samples.ContextualFlowRow_ItemPosition
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
@@ -27,5 +28,6 @@
fun ContextualFlowRowDemo() {
Column(Modifier.verticalScroll(rememberScrollState())) {
ContextualFlowRowMaxLineDynamicSeeMore()
+ ContextualFlowRow_ItemPosition()
}
}
diff --git a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowColumnSample.kt b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowColumnSample.kt
index 3d6bdeb..ea694e9 100644
--- a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowColumnSample.kt
+++ b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowColumnSample.kt
@@ -25,6 +25,7 @@
import androidx.compose.foundation.layout.ContextualFlowColumnOverflow
import androidx.compose.foundation.layout.ContextualFlowColumnOverflowScope
import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -43,6 +44,7 @@
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import kotlin.random.Random
@OptIn(ExperimentalLayoutApi::class)
@Sampled
@@ -106,3 +108,36 @@
}
}
}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Sampled
+@Composable
+fun ContextualFlowColumn_ItemPosition() {
+ Text("Ln: Line No\nPs: Position No. in Line", modifier = Modifier.padding(20.dp))
+ ContextualFlowColumn(
+ modifier = Modifier
+ .fillMaxHeight(1f)
+ .width(210.dp)
+ .padding(20.dp),
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalArrangement = Arrangement.spacedBy(20.dp),
+ maxItemsInEachColumn = 4,
+ itemCount = 12
+ ) {
+ val width = 50.dp.coerceAtMost(maxWidth)
+ val height = Random.nextInt(80, 100).dp.coerceAtMost(maxHeightInLine)
+ Box(
+ Modifier
+ .width(width)
+ .height(height)
+ .background(MatchingColors.getByIndex(indexInLine)!!.color)
+ ) {
+ Text(
+ text = "Ln: ${this@ContextualFlowColumn.lineIndex}" +
+ "\nPs: ${this@ContextualFlowColumn.indexInLine}",
+ fontSize = 18.sp,
+ modifier = Modifier.padding(3.dp)
+ )
+ }
+ }
+}
diff --git a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowRowSample.kt b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowRowSample.kt
index 0410a1f..28bd857 100644
--- a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowRowSample.kt
+++ b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/ContextualFlowRowSample.kt
@@ -43,6 +43,7 @@
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import kotlin.random.Random
@OptIn(ExperimentalLayoutApi::class)
@Sampled
@@ -104,6 +105,52 @@
}
}
+@OptIn(ExperimentalLayoutApi::class)
+@Sampled
+@Composable
+fun ContextualFlowRow_ItemPosition() {
+ Text("Ln: Line No\nPs: Position No. in Line", modifier = Modifier.padding(20.dp))
+ ContextualFlowRow(
+ modifier = Modifier
+ .fillMaxWidth(1f)
+ .height(210.dp)
+ .padding(20.dp),
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalArrangement = Arrangement.spacedBy(20.dp),
+ maxItemsInEachRow = 4,
+ itemCount = 12
+ ) {
+ val width = Random.nextInt(80, 100).dp.coerceAtMost(maxWidthInLine)
+ val height = 50.dp.coerceAtMost(maxHeight)
+ Box(
+ Modifier
+ .width(width)
+ .height(height)
+ .background(MatchingColors.getByIndex(indexInLine)!!.color)
+ ) {
+ Text(
+ text = "Ln: ${this@ContextualFlowRow.lineIndex}" +
+ "\nPs: ${this@ContextualFlowRow.indexInLine}",
+ fontSize = 18.sp,
+ modifier = Modifier.padding(3.dp)
+ )
+ }
+ }
+}
+
+enum class MatchingColors(val index: Int, val color: Color) {
+ ZERO(0, Color.Green),
+ ONE(1, Color.Yellow),
+ TWO(2, Color.Blue),
+ THREE(3, Color.Cyan);
+
+ companion object {
+ fun getByIndex(index: Int): MatchingColors? {
+ return values().firstOrNull { it.index == index }
+ }
+ }
+}
+
@Composable
internal fun DynamicSeeMore(
isHorizontal: Boolean,
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt
index 6ff0af0..bc6097c 100644
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.clickable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -37,6 +38,7 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -3929,4 +3931,316 @@
Truth.assertThat(height).isEqualTo(200)
Truth.assertThat(noOfItemsPlaced).isEqualTo(0)
}
+
+ @Test
+ fun testContextualFlowRow_LayoutInfo() {
+ val list: MutableList<FlowLineInfo> = mutableListOf()
+ val mainAxisSize = mutableStateOf(300.dp)
+ val crossAxisSize = mutableStateOf(Dp.Infinity)
+ val lineOneItemSizePx = 50
+ val lineOneItemSize = lineOneItemSizePx.dp
+ val itemSize = 100.dp
+ val spacing = 10.dp
+ val crossAxisSpacing = 20.dp
+ val maxItemsInMainAxis = mutableIntStateOf(4)
+ val itemCount = 12
+ rule.setContent {
+ CompositionLocalProvider(
+ LocalDensity provides NoOpDensity
+ ) {
+ val heightState = remember { crossAxisSize }
+ val widthState by remember { mainAxisSize }
+ val maxItemsInMainAxisState by remember { maxItemsInMainAxis }
+ ContextualFlowRow(
+ modifier = Modifier
+ .width(widthState)
+ .run {
+ val heightValue = heightState.value
+ if (heightValue == Dp.Infinity) {
+ this.wrapContentHeight()
+ } else {
+ this.height(heightValue)
+ }
+ },
+ horizontalArrangement = Arrangement.spacedBy(spacing),
+ verticalArrangement = Arrangement.spacedBy(crossAxisSpacing),
+ maxItemsInEachRow = maxItemsInMainAxisState,
+ itemCount = itemCount,
+ overflow = ContextualFlowRowOverflow.Clip
+ ) {
+ Box(
+ Modifier
+ .width(if (lineIndex == 0) lineOneItemSize else itemSize)
+ .height(
+ if (lineIndex == 0) {
+ // choose varying heights, to
+ // ensure the calculation considers the actual
+ // cross axis size of the row.
+ if (indexInLine == 0) {
+ lineOneItemSize
+ } else {
+ Random.nextInt(10, (lineOneItemSizePx - 10)).dp
+ }
+ } else {
+ itemSize
+ }
+ ).onGloballyPositioned {
+ list.add(
+ FlowLineInfo(
+ lineIndex,
+ indexInLine,
+ maxWidthInLine,
+ maxHeight
+ )
+ )
+ }
+ )
+ }
+ }
+ }
+ rule.waitForIdle()
+ rule.runOnIdle {
+ Truth.assertThat(list.size).isEqualTo(itemCount)
+ val maxCrossAxis = list[0].maxCrossAxisSize
+ verifyFlowLineInfo(
+ list,
+ mainAxisSize.value,
+ maxCrossAxis,
+ lineOneItemSize,
+ itemSize,
+ spacing,
+ maxItemsInMainAxis.value,
+ crossAxisSpacing
+ )
+ }
+
+ rule.runOnIdle {
+ crossAxisSize.value = 210.dp
+ list.clear()
+ }
+
+ advanceClock()
+
+ rule.runOnIdle {
+ Truth.assertThat(list.size).isEqualTo(6)
+ verifyFlowLineInfo(
+ list,
+ mainAxisSize.value,
+ crossAxisSize.value,
+ lineOneItemSize,
+ itemSize,
+ spacing,
+ maxItemsInMainAxis.value,
+ crossAxisSpacing
+ )
+ }
+
+ rule.runOnIdle {
+ mainAxisSize.value = 150.dp
+ maxItemsInMainAxis.value = 2
+ list.clear()
+ }
+
+ advanceClock()
+
+ rule.runOnIdle {
+ Truth.assertThat(list.size).isEqualTo(3)
+ verifyFlowLineInfo(
+ list,
+ mainAxisSize.value,
+ crossAxisSize.value,
+ lineOneItemSize,
+ itemSize,
+ spacing,
+ maxItemsInMainAxis.value,
+ crossAxisSpacing
+ )
+ }
+ }
+
+ @Test
+ fun testContextualFlowColumn_LayoutInfo() {
+ val list: MutableList<FlowLineInfo> = mutableListOf()
+ val mainAxisSize = mutableStateOf(300.dp)
+ val crossAxisSize = mutableStateOf(Dp.Infinity)
+ val lineOneItemSizePx = 50
+ val lineOneItemSize = lineOneItemSizePx.dp
+ val itemSize = 100.dp
+ val spacing = 10.dp
+ val crossAxisSpacing = 20.dp
+ val itemCount = 12
+ val maxItemsInMainAxis = mutableIntStateOf(4)
+ rule.setContent {
+ CompositionLocalProvider(
+ LocalDensity provides NoOpDensity
+ ) {
+ val widthState = remember { crossAxisSize }
+ val heightState by remember { mainAxisSize }
+ val maxItemsInMainAxisState by remember { maxItemsInMainAxis }
+ ContextualFlowColumn(
+ modifier = Modifier
+ .height(heightState)
+ .run {
+ val widthValue = widthState.value
+ if (widthValue == Dp.Infinity) {
+ this.wrapContentWidth()
+ } else {
+ this.width(widthValue)
+ }
+ },
+ verticalArrangement = Arrangement.spacedBy(spacing),
+ horizontalArrangement = Arrangement.spacedBy(crossAxisSpacing),
+ maxItemsInEachColumn = maxItemsInMainAxisState,
+ itemCount = itemCount,
+ overflow = ContextualFlowColumnOverflow.Clip
+ ) {
+ Box(
+ Modifier
+ .height(if (lineIndex == 0) lineOneItemSize else itemSize)
+ .width(
+ if (lineIndex == 0) {
+ // choose varying heights, to
+ // ensure the calculation considers the actual
+ // cross axis size of the row.
+ if (indexInLine == 0) {
+ lineOneItemSize
+ } else {
+ Random.nextInt(10, (lineOneItemSizePx - 10)).dp
+ }
+ } else {
+ itemSize
+ }
+ ).onGloballyPositioned {
+ list.add(
+ FlowLineInfo(
+ lineIndex,
+ indexInLine,
+ maxMainAxisSize = maxHeightInLine,
+ maxCrossAxisSize = maxWidth,
+ )
+ )
+ }
+ )
+ }
+ }
+ }
+ rule.waitForIdle()
+ rule.runOnIdle {
+ Truth.assertThat(list.size).isEqualTo(itemCount)
+ val maxCrossAxis = list[0].maxCrossAxisSize
+ verifyFlowLineInfo(
+ list,
+ mainAxisSize.value,
+ maxCrossAxis,
+ lineOneItemSize,
+ itemSize,
+ spacing,
+ maxItemsInMainAxis.value,
+ crossAxisSpacing
+ )
+ }
+ rule.runOnIdle {
+ crossAxisSize.value = 210.dp
+ list.clear()
+ }
+
+ advanceClock()
+
+ rule.runOnIdle {
+ Truth.assertThat(list.size).isEqualTo(6)
+ verifyFlowLineInfo(
+ list,
+ mainAxisSize.value,
+ crossAxisSize.value,
+ lineOneItemSize,
+ itemSize,
+ spacing,
+ maxItemsInMainAxis.value,
+ crossAxisSpacing
+ )
+ }
+
+ rule.runOnIdle {
+ list.clear()
+ mainAxisSize.value = 150.dp
+ maxItemsInMainAxis.value = 2
+ }
+
+ advanceClock()
+
+ rule.runOnIdle {
+ Truth.assertThat(list.size).isEqualTo(3)
+ verifyFlowLineInfo(
+ list,
+ mainAxisSize.value,
+ crossAxisSize.value,
+ lineOneItemSize,
+ itemSize,
+ spacing,
+ maxItemsInMainAxis.value,
+ crossAxisSpacing
+ )
+ }
+ }
+
+ private fun verifyFlowLineInfo(
+ list: MutableList<FlowLineInfo>,
+ mainAxisSize: Dp,
+ maxCrossAxis: Dp,
+ lineOneItemSize: Dp,
+ itemSize: Dp,
+ spacing: Dp,
+ maxItemsInMainAxis: Int,
+ crossAxisSpacing: Dp
+ ) {
+ var leftOver = mainAxisSize
+ val lineInfo = FlowLineInfo(
+ 0,
+ 0,
+ leftOver,
+ maxCrossAxis
+ )
+ var leftOverCrossAxis = maxCrossAxis
+ list.forEach { info ->
+ val lineItemSize = if (info.lineIndex == 0) {
+ lineOneItemSize
+ } else {
+ itemSize
+ }
+ Truth.assertThat(info.positionInLine).isEqualTo(lineInfo.positionInLine)
+ Truth.assertThat(info.maxMainAxisSize).isEqualTo(lineInfo.maxMainAxisSize)
+ Truth.assertThat(info.maxCrossAxisSize).isEqualTo(lineInfo.maxCrossAxisSize)
+ Truth.assertThat(info.lineIndex).isEqualTo(lineInfo.lineIndex)
+ leftOver = leftOver - lineItemSize - spacing
+ val pastMaxItems = lineInfo.positionInLine + 1 >= maxItemsInMainAxis
+ if (pastMaxItems) {
+ leftOver = mainAxisSize
+ leftOverCrossAxis = leftOverCrossAxis - lineItemSize - crossAxisSpacing
+ lineInfo.update(
+ positionInLine = 0,
+ maxMainAxisSize = leftOver,
+ maxCrossAxisSize = leftOverCrossAxis,
+ lineIndex = lineInfo.lineIndex + 1
+ )
+ } else if (leftOver < 0.dp) {
+ leftOver = mainAxisSize - lineItemSize - spacing
+ // when the left over is less than 0, the FlowRow will automatically wrap it.
+ // the next item index position will be the second item in the row
+ leftOverCrossAxis = leftOverCrossAxis - lineItemSize - crossAxisSpacing
+ lineInfo.update(
+ positionInLine = 1,
+ maxMainAxisSize = leftOver,
+ maxCrossAxisSize = leftOverCrossAxis,
+ lineIndex = lineInfo.lineIndex + 1
+ )
+ } else {
+ lineInfo.update(
+ positionInLine = info.positionInLine + 1,
+ maxMainAxisSize = leftOver,
+ maxCrossAxisSize = leftOverCrossAxis,
+ lineIndex = lineInfo.lineIndex
+ )
+ }
+ }
+ }
}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
index ce07967..60fe7ae 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
@@ -26,6 +26,7 @@
import androidx.compose.ui.layout.SubcomposeMeasureScope
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
/**
* [ContextualFlowRow] is a specialized version of the [FlowRow] layout. It is designed to
@@ -87,8 +88,14 @@
overflowState,
itemCount,
list
- ) { index ->
- ContextualFlowRowScopeInstance.content(index)
+ ) { index, info ->
+ val scope = ContextualFlowRowScopeImpl(
+ info.lineIndex,
+ info.positionInLine,
+ maxWidthInLine = info.maxMainAxisSize,
+ maxHeight = info.maxCrossAxisSize
+ )
+ scope.content(index)
}
SubcomposeLayout(
modifier = modifier,
@@ -156,8 +163,14 @@
overflowState,
itemCount,
list,
- ) { index ->
- ContextualFlowColumnScopeInstance.content(index)
+ ) { index, info ->
+ val scope = ContextualFlowColumnScopeImpl(
+ info.lineIndex,
+ info.positionInLine,
+ maxHeightInLine = info.maxMainAxisSize,
+ maxWidth = info.maxCrossAxisSize
+ )
+ scope.content(index)
}
SubcomposeLayout(
@@ -167,12 +180,57 @@
}
/**
- * Scope for the children of [ContextualFlowRow].
+ * Defines the scope for items within a [ContextualFlowRow].
*/
@LayoutScopeMarker
@Immutable
@ExperimentalLayoutApi
-interface ContextualFlowRowScope : FlowRowScope
+interface ContextualFlowRowScope : FlowRowScope {
+ /**
+ * Identifies the row or column index where the UI component(s) are to be placed, provided they
+ * do not exceed the specified [maxWidthInLine]
+ * and [maxHeight] for that row or column.
+ *
+ * Should the component(s) surpass these dimensions, their placement may shift to the subsequent
+ * row/column or they may be omitted from display, contingent upon the defined constraints.
+ *
+ * Example:
+ * @sample androidx.compose.foundation.layout.samples.ContextualFlowRow_ItemPosition
+ * @sample androidx.compose.foundation.layout.samples.ContextualFlowColumn_ItemPosition
+ */
+ val lineIndex: Int
+
+ /**
+ * Marks the index within the current row/column where the next component is to be inserted,
+ * assuming it conforms to the row's or column's [maxWidthInLine]
+ * and [maxHeight] limitations.
+ *
+ * In scenarios where multiple UI components are returned in one index call,
+ * this parameter is relevant solely to the first returned UI component, presuming it complies
+ * with the row's or column's defined constraints.
+ *
+ * Example:
+ * @sample androidx.compose.foundation.layout.samples.ContextualFlowRow_ItemPosition
+ * @sample androidx.compose.foundation.layout.samples.ContextualFlowColumn_ItemPosition
+ */
+ val indexInLine: Int
+
+ /**
+ * Specifies the maximum permissible width (main-axis) for the upcoming UI component
+ * at the given [lineIndex] and [indexInLine]. Exceeding this width may result
+ * in the component being reallocated to the following row
+ * within the [ContextualFlowRow] structure, subject to existing constraints.
+ */
+ val maxWidthInLine: Dp
+
+ /**
+ * Determines the maximum allowable height (cross-axis) for the forthcoming UI component,
+ * aligned with its [lineIndex] and [indexInLine]. Should this height threshold be exceeded,
+ * the component's visibility will depend on the overflow settings, potentially
+ * leading to its exclusion.
+ */
+ val maxHeight: Dp
+}
/**
* Scope for the overflow [ContextualFlowRow].
@@ -191,20 +249,73 @@
interface ContextualFlowColumnOverflowScope : FlowColumnOverflowScope
/**
- * Scope for the children of [ContextualFlowColumn].
+ * Provides a scope for items within a [ContextualFlowColumn].
*/
@LayoutScopeMarker
@Immutable
@ExperimentalLayoutApi
-interface ContextualFlowColumnScope : FlowColumnScope
+interface ContextualFlowColumnScope : FlowColumnScope {
+ /**
+ * Identifies the row or column index where the UI component(s) are to be placed, provided they
+ * do not exceed the specified [maxWidth]
+ * and [maxHeightInLine] for that row or column.
+ *
+ * Should the component(s) surpass these dimensions, their placement may shift to the subsequent
+ * row/column or they may be omitted from display, contingent upon the defined constraints.
+ *
+ * Example:
+ * @sample androidx.compose.foundation.layout.samples.ContextualFlowRow_ItemPosition
+ * @sample androidx.compose.foundation.layout.samples.ContextualFlowColumn_ItemPosition
+ */
+ val lineIndex: Int
+
+ /**
+ * Marks the index within the current row/column where the next component is to be inserted,
+ * assuming it conforms to the row's or column's [maxWidth]
+ * and [maxHeightInLine] limitations.
+ *
+ * In scenarios where multiple UI components are returned in one index call,
+ * this parameter is relevant solely to the first returned UI component, presuming it complies
+ * with the row's or column's defined constraints.
+ *
+ * Example:
+ * @sample androidx.compose.foundation.layout.samples.ContextualFlowRow_ItemPosition
+ * @sample androidx.compose.foundation.layout.samples.ContextualFlowColumn_ItemPosition
+ */
+ val indexInLine: Int
+
+ /**
+ * Sets the maximum width (cross-axis dimension) that the upcoming UI component can occupy,
+ * based on its [lineIndex] and [indexInLine]. Exceeding this width might result in the
+ * component not being displayed, depending on the [ContextualFlowColumnOverflow.Visible]
+ * overflow configuration.
+ */
+ val maxWidth: Dp
+
+ /**
+ * Establishes the maximum height (main-axis dimension) permissible for the next UI component,
+ * aligned with its [lineIndex] and [indexInLine]. Should the component's height exceed
+ * this limit, it may be shifted to the subsequent column in [ContextualFlowColumn], subject to
+ * the predefined constraints.
+ */
+ val maxHeightInLine: Dp
+}
@OptIn(ExperimentalLayoutApi::class)
-internal object ContextualFlowRowScopeInstance :
- FlowRowScope by FlowRowScopeInstance, ContextualFlowRowScope
+internal class ContextualFlowRowScopeImpl(
+ override val lineIndex: Int,
+ override val indexInLine: Int,
+ override val maxWidthInLine: Dp,
+ override val maxHeight: Dp
+) : FlowRowScope by FlowRowScopeInstance, ContextualFlowRowScope
@OptIn(ExperimentalLayoutApi::class)
-internal object ContextualFlowColumnScopeInstance : FlowColumnScope by FlowColumnScopeInstance,
- ContextualFlowColumnScope
+internal class ContextualFlowColumnScopeImpl(
+ override val lineIndex: Int,
+ override val indexInLine: Int,
+ override val maxWidth: Dp,
+ override val maxHeightInLine: Dp
+) : FlowColumnScope by FlowColumnScopeInstance, ContextualFlowColumnScope
@ExperimentalLayoutApi
internal class ContextualFlowRowOverflowScopeImpl(
@@ -226,7 +337,8 @@
itemCount: Int,
overflowComposables: List<@Composable () -> Unit>,
getComposable: @Composable (
- index: Int
+ index: Int,
+ info: FlowLineInfo
) -> Unit
): (SubcomposeMeasureScope, Constraints) -> MeasureResult {
return remember(
@@ -266,7 +378,8 @@
itemCount: Int,
overflowComposables: List<@Composable () -> Unit>,
getComposable: @Composable (
- index: Int
+ index: Int,
+ info: FlowLineInfo
) -> Unit
): (SubcomposeMeasureScope, Constraints) -> MeasureResult {
return remember(
@@ -314,7 +427,8 @@
private val overflow: FlowLayoutOverflowState,
private val overflowComposables: List<@Composable () -> Unit>,
private val getComposable: @Composable (
- index: Int
+ index: Int,
+ info: FlowLineInfo
) -> Unit
) {
fun getMeasurePolicy(): (SubcomposeMeasureScope, Constraints) -> MeasureResult {
@@ -336,9 +450,9 @@
}
val measurablesIterator = ContextualFlowItemIterator(
itemCount
- ) { index ->
+ ) { index, info ->
this.subcompose(index) {
- getComposable(index)
+ getComposable(index, info)
}
}
overflow.itemCount = itemCount
@@ -371,7 +485,8 @@
internal class ContextualFlowItemIterator(
private val itemCount: Int,
private val getMeasurables: (
- index: Int
+ index: Int,
+ info: FlowLineInfo
) -> List<Measurable>
) : Iterator<Measurable> {
private val _list: MutableList<Measurable> = mutableListOf()
@@ -384,6 +499,12 @@
}
override fun next(): Measurable {
+ return getNext()
+ }
+
+ internal fun getNext(
+ info: FlowLineInfo = FlowLineInfo()
+ ): Measurable {
// when we are at the end of the list, we fetch a new item from getMeasurables
// and add to the list.
// otherwise, we continue through the list.
@@ -392,7 +513,7 @@
listIndex++
measurable
} else if (itemIndex < itemCount) {
- val measurables = getMeasurables(itemIndex)
+ val measurables = getMeasurables(itemIndex, info)
itemIndex++
if (measurables.isEmpty()) {
next()
@@ -409,3 +530,32 @@
}
}
}
+
+/**
+ * Contextual
+ * Line Info for the current
+ * lazy call for [ContextualFlowRow] or [ContextualFlowColumn]
+ */
+internal class FlowLineInfo(
+ internal var lineIndex: Int = 0,
+ internal var positionInLine: Int = 0,
+ internal var maxMainAxisSize: Dp = 0.dp,
+ internal var maxCrossAxisSize: Dp = 0.dp,
+) {
+
+ /**
+ * To allow reuse of the same object to reduce allocation,
+ * simply update the same value
+ */
+ internal fun update(
+ lineIndex: Int,
+ positionInLine: Int,
+ maxMainAxisSize: Dp,
+ maxCrossAxisSize: Dp,
+ ) {
+ this.lineIndex = lineIndex
+ this.positionInLine = positionInLine
+ this.maxMainAxisSize = maxMainAxisSize
+ this.maxCrossAxisSize = maxCrossAxisSize
+ }
+}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
index c4b653f..adbac9c 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
@@ -235,10 +235,10 @@
@ExperimentalLayoutApi
interface FlowColumnScope : ColumnScope {
/**
- * Have the item fill (possibly only partially) the max width of the tallest item in the
+ * Have the item fill (possibly only partially) the max width of the widest item in the
* column it was placed in, within the [FlowColumn].
*
- * @param fraction The fraction of the max width of the tallest item
+ * @param fraction The fraction of the max width of the widest item
* between `0` and `1`, inclusive.
*
* Example usage:
@@ -1071,11 +1071,26 @@
0,
crossAxisMax
)
- // nextSize of the list, pre-calculated
+
var index = 0
var measurable: Measurable?
+
+ var lineIndex = 0
+ var leftOver = mainAxisMax
+ var leftOverCrossAxis = crossAxisMax
+ val lineInfo = if (measurablesIterator is ContextualFlowItemIterator) {
+ FlowLineInfo(
+ lineIndex = lineIndex,
+ positionInLine = 0,
+ maxMainAxisSize = leftOver.toDp(),
+ maxCrossAxisSize = leftOverCrossAxis.toDp()
+ )
+ } else {
+ null
+ }
+
var nextSize = measurablesIterator.hasNext().run {
- measurable = if (!this) null else measurablesIterator.safeNext()
+ measurable = if (!this) null else measurablesIterator.safeNext(lineInfo)
measurable?.measureAndCache(subsetConstraints, orientation) { placeable ->
placeables[0] = placeable
}
@@ -1086,10 +1101,7 @@
var startBreakLineIndex = 0
val endBreakLineList = mutableIntListOf()
val crossAxisSizes = mutableIntListOf()
- var lineIndex = 0
- var leftOver = mainAxisMax
- var leftOverCrossAxis = crossAxisMax
val buildingBlocks = FlowLayoutBuildingBlocks(
maxItemsInMainAxis = maxItemsInMainAxis,
mainAxisSpacing = spacing,
@@ -1135,8 +1147,28 @@
leftOver -= itemMainAxisSize
overflow.itemShown = index + 1
measurables.add(measurable!!)
+
+ val nextIndexInLine = (index + 1) - startBreakLineIndex
+ val willFitLine = nextIndexInLine < maxItemsInMainAxis
+
+ lineInfo?.update(
+ lineIndex = if (willFitLine) lineIndex else lineIndex + 1,
+ positionInLine = if (willFitLine) nextIndexInLine else 0,
+ maxMainAxisSize = if (willFitLine) {
+ (leftOver - spacing).coerceAtLeast(0)
+ } else {
+ mainAxisMax
+ }.toDp(),
+ maxCrossAxisSize = if (willFitLine) {
+ leftOverCrossAxis
+ } else {
+ (leftOverCrossAxis - currentLineCrossAxisSize -
+ crossAxisSpacing).coerceAtLeast(0)
+ }.toDp()
+ )
+
nextSize = measurablesIterator.hasNext().run {
- measurable = if (!this) null else measurablesIterator.safeNext()
+ measurable = if (!this) null else measurablesIterator.safeNext(lineInfo)
measurable?.measureAndCache(subsetConstraints, orientation) { placeable ->
placeables[index + 1] = placeable
}
@@ -1151,7 +1183,7 @@
nextSize = if (nextSize == null) null else
IntIntPair(nextMainAxisSize!!, nextCrossAxisSize!!),
currentLineCrossAxisSize = currentLineCrossAxisSize,
- nextIndexInLine = (index + 1) - startBreakLineIndex,
+ nextIndexInLine = nextIndexInLine,
isWrappingRound = false,
isEllipsisWrap = false,
lineIndex = lineIndex
@@ -1248,9 +1280,13 @@
return handleFlowResult(flowResult, constraints, measureHelper)
}
-private fun Iterator<Measurable>.safeNext(): Measurable? {
+private fun Iterator<Measurable>.safeNext(info: FlowLineInfo?): Measurable? {
return try {
- next()
+ if (this is ContextualFlowItemIterator) {
+ this.getNext(info!!)
+ } else {
+ next()
+ }
} catch (e: ArrayIndexOutOfBoundsException) {
null
}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index 6a74dde..953637a 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -93,7 +93,11 @@
override fun IntrinsicMeasureScope.minIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
- ) = MinIntrinsicWidthMeasureBlock(orientation)(
+ ) = if (orientation == Horizontal) IntrinsicMeasureBlocks.HorizontalMinWidth(
+ measurables,
+ height,
+ arrangementSpacing.roundToPx()
+ ) else IntrinsicMeasureBlocks.VerticalMinWidth(
measurables,
height,
arrangementSpacing.roundToPx()
@@ -102,7 +106,11 @@
override fun IntrinsicMeasureScope.minIntrinsicHeight(
measurables: List<IntrinsicMeasurable>,
width: Int
- ) = MinIntrinsicHeightMeasureBlock(orientation)(
+ ) = if (orientation == Horizontal) IntrinsicMeasureBlocks.HorizontalMinHeight(
+ measurables,
+ width,
+ arrangementSpacing.roundToPx()
+ ) else IntrinsicMeasureBlocks.VerticalMinHeight(
measurables,
width,
arrangementSpacing.roundToPx()
@@ -111,7 +119,11 @@
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
- ) = MaxIntrinsicWidthMeasureBlock(orientation)(
+ ) = if (orientation == Horizontal) IntrinsicMeasureBlocks.HorizontalMaxWidth(
+ measurables,
+ height,
+ arrangementSpacing.roundToPx()
+ ) else IntrinsicMeasureBlocks.VerticalMaxWidth(
measurables,
height,
arrangementSpacing.roundToPx()
@@ -120,7 +132,11 @@
override fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurables: List<IntrinsicMeasurable>,
width: Int
- ) = MaxIntrinsicHeightMeasureBlock(orientation)(
+ ) = if (orientation == Horizontal) IntrinsicMeasureBlocks.HorizontalMaxHeight(
+ measurables,
+ width,
+ arrangementSpacing.roundToPx()
+ ) else IntrinsicMeasureBlocks.VerticalMaxHeight(
measurables,
width,
arrangementSpacing.roundToPx()
@@ -405,154 +421,110 @@
internal val RowColumnParentData?.isRelative: Boolean
get() = this.crossAxisAlignment?.isRelative ?: false
-private fun MinIntrinsicWidthMeasureBlock(orientation: LayoutOrientation) =
- if (orientation == LayoutOrientation.Horizontal) {
- IntrinsicMeasureBlocks.HorizontalMinWidth
- } else {
- IntrinsicMeasureBlocks.VerticalMinWidth
- }
-
-private fun MinIntrinsicHeightMeasureBlock(orientation: LayoutOrientation) =
- if (orientation == LayoutOrientation.Horizontal) {
- IntrinsicMeasureBlocks.HorizontalMinHeight
- } else {
- IntrinsicMeasureBlocks.VerticalMinHeight
- }
-
-private fun MaxIntrinsicWidthMeasureBlock(orientation: LayoutOrientation) =
- if (orientation == LayoutOrientation.Horizontal) {
- IntrinsicMeasureBlocks.HorizontalMaxWidth
- } else {
- IntrinsicMeasureBlocks.VerticalMaxWidth
- }
-
-private fun MaxIntrinsicHeightMeasureBlock(orientation: LayoutOrientation) =
- if (orientation == LayoutOrientation.Horizontal) {
- IntrinsicMeasureBlocks.HorizontalMaxHeight
- } else {
- IntrinsicMeasureBlocks.VerticalMaxHeight
- }
-
private object IntrinsicMeasureBlocks {
- val HorizontalMinWidth: (List<IntrinsicMeasurable>, Int, Int) -> Int =
- { measurables, availableHeight, mainAxisSpacing ->
- intrinsicSize(
- measurables,
- { h -> minIntrinsicWidth(h) },
- { w -> maxIntrinsicHeight(w) },
- availableHeight,
- mainAxisSpacing,
- LayoutOrientation.Horizontal,
- LayoutOrientation.Horizontal
- )
- }
- val VerticalMinWidth: (List<IntrinsicMeasurable>, Int, Int) -> Int =
- { measurables, availableHeight, mainAxisSpacing ->
- intrinsicSize(
- measurables,
- { h -> minIntrinsicWidth(h) },
- { w -> maxIntrinsicHeight(w) },
- availableHeight,
- mainAxisSpacing,
- LayoutOrientation.Vertical,
- LayoutOrientation.Horizontal
- )
- }
- val HorizontalMinHeight: (List<IntrinsicMeasurable>, Int, Int) -> Int =
- { measurables, availableWidth, mainAxisSpacing ->
- intrinsicSize(
- measurables,
- { w -> minIntrinsicHeight(w) },
- { h -> maxIntrinsicWidth(h) },
- availableWidth,
- mainAxisSpacing,
- LayoutOrientation.Horizontal,
- LayoutOrientation.Vertical
- )
- }
- val VerticalMinHeight: (List<IntrinsicMeasurable>, Int, Int) -> Int =
- { measurables, availableWidth, mainAxisSpacing ->
- intrinsicSize(
- measurables,
- { w -> minIntrinsicHeight(w) },
- { h -> maxIntrinsicWidth(h) },
- availableWidth,
- mainAxisSpacing,
- LayoutOrientation.Vertical,
- LayoutOrientation.Vertical
- )
- }
- val HorizontalMaxWidth: (List<IntrinsicMeasurable>, Int, Int) -> Int =
- { measurables, availableHeight, mainAxisSpacing ->
- intrinsicSize(
- measurables,
- { h -> maxIntrinsicWidth(h) },
- { w -> maxIntrinsicHeight(w) },
- availableHeight,
- mainAxisSpacing,
- LayoutOrientation.Horizontal,
- LayoutOrientation.Horizontal
- )
- }
- val VerticalMaxWidth: (List<IntrinsicMeasurable>, Int, Int) -> Int =
- { measurables, availableHeight, mainAxisSpacing ->
- intrinsicSize(
- measurables,
- { h -> maxIntrinsicWidth(h) },
- { w -> maxIntrinsicHeight(w) },
- availableHeight,
- mainAxisSpacing,
- LayoutOrientation.Vertical,
- LayoutOrientation.Horizontal
- )
- }
- val HorizontalMaxHeight: (List<IntrinsicMeasurable>, Int, Int) -> Int =
- { measurables, availableWidth, mainAxisSpacing ->
- intrinsicSize(
- measurables,
- { w -> maxIntrinsicHeight(w) },
- { h -> maxIntrinsicWidth(h) },
- availableWidth,
- mainAxisSpacing,
- LayoutOrientation.Horizontal,
- LayoutOrientation.Vertical
- )
- }
- val VerticalMaxHeight: (List<IntrinsicMeasurable>, Int, Int) -> Int =
- { measurables, availableWidth, mainAxisSpacing ->
- intrinsicSize(
- measurables,
- { w -> maxIntrinsicHeight(w) },
- { h -> maxIntrinsicWidth(h) },
- availableWidth,
- mainAxisSpacing,
- LayoutOrientation.Vertical,
- LayoutOrientation.Vertical
- )
- }
+ fun HorizontalMinWidth(
+ measurables: List<IntrinsicMeasurable>,
+ availableHeight: Int,
+ mainAxisSpacing: Int
+ ): Int {
+ return intrinsicMainAxisSize(
+ measurables,
+ { h -> minIntrinsicWidth(h) },
+ availableHeight,
+ mainAxisSpacing,
+ )
+ }
+ fun VerticalMinWidth(
+ measurables: List<IntrinsicMeasurable>,
+ availableHeight: Int,
+ mainAxisSpacing: Int
+ ): Int {
+ return intrinsicCrossAxisSize(
+ measurables,
+ { w -> maxIntrinsicHeight(w) },
+ { h -> minIntrinsicWidth(h) },
+ availableHeight,
+ mainAxisSpacing,
+ )
+ }
+ fun HorizontalMinHeight(
+ measurables: List<IntrinsicMeasurable>,
+ availableWidth: Int,
+ mainAxisSpacing: Int
+ ): Int {
+ return intrinsicCrossAxisSize(
+ measurables,
+ { h -> maxIntrinsicWidth(h) },
+ { w -> minIntrinsicHeight(w) },
+ availableWidth,
+ mainAxisSpacing,
+ )
+ }
+ fun VerticalMinHeight(
+ measurables: List<IntrinsicMeasurable>,
+ availableWidth: Int,
+ mainAxisSpacing: Int
+ ): Int {
+ return intrinsicMainAxisSize(
+ measurables,
+ { w -> minIntrinsicHeight(w) },
+ availableWidth,
+ mainAxisSpacing,
+ )
+ }
+ fun HorizontalMaxWidth(
+ measurables: List<IntrinsicMeasurable>,
+ availableHeight: Int,
+ mainAxisSpacing: Int
+ ): Int {
+ return intrinsicMainAxisSize(
+ measurables,
+ { h -> maxIntrinsicWidth(h) },
+ availableHeight,
+ mainAxisSpacing,
+ )
+ }
+ fun VerticalMaxWidth(
+ measurables: List<IntrinsicMeasurable>,
+ availableHeight: Int,
+ mainAxisSpacing: Int
+ ): Int {
+ return intrinsicCrossAxisSize(
+ measurables,
+ { w -> maxIntrinsicHeight(w) },
+ { h -> maxIntrinsicWidth(h) },
+ availableHeight,
+ mainAxisSpacing,
+ )
+ }
+ fun HorizontalMaxHeight(
+ measurables: List<IntrinsicMeasurable>,
+ availableWidth: Int,
+ mainAxisSpacing: Int
+ ): Int {
+ return intrinsicCrossAxisSize(
+ measurables,
+ { h -> maxIntrinsicWidth(h) },
+ { w -> maxIntrinsicHeight(w) },
+ availableWidth,
+ mainAxisSpacing,
+ )
+ }
+ fun VerticalMaxHeight(
+ measurables: List<IntrinsicMeasurable>,
+ availableWidth: Int,
+ mainAxisSpacing: Int
+ ): Int {
+ return intrinsicMainAxisSize(
+ measurables,
+ { w -> maxIntrinsicHeight(w) },
+ availableWidth,
+ mainAxisSpacing,
+ )
+ }
}
-private fun intrinsicSize(
- children: List<IntrinsicMeasurable>,
- intrinsicMainSize: IntrinsicMeasurable.(Int) -> Int,
- intrinsicCrossSize: IntrinsicMeasurable.(Int) -> Int,
- crossAxisAvailable: Int,
- mainAxisSpacing: Int,
- layoutOrientation: LayoutOrientation,
- intrinsicOrientation: LayoutOrientation
-) = if (layoutOrientation == intrinsicOrientation) {
- intrinsicMainAxisSize(children, intrinsicMainSize, crossAxisAvailable, mainAxisSpacing)
-} else {
- intrinsicCrossAxisSize(
- children,
- intrinsicCrossSize,
- intrinsicMainSize,
- crossAxisAvailable,
- mainAxisSpacing
- )
-}
-
-private fun intrinsicMainAxisSize(
+private inline fun intrinsicMainAxisSize(
children: List<IntrinsicMeasurable>,
mainAxisSize: IntrinsicMeasurable.(Int) -> Int,
crossAxisAvailable: Int,
@@ -576,7 +548,7 @@
(children.size - 1) * mainAxisSpacing
}
-private fun intrinsicCrossAxisSize(
+private inline fun intrinsicCrossAxisSize(
children: List<IntrinsicMeasurable>,
mainAxisSize: IntrinsicMeasurable.(Int) -> Int,
crossAxisSize: IntrinsicMeasurable.(Int) -> Int,
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 303288b..b825cd6 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -193,8 +193,6 @@
}
public enum MutatePriority {
- method public static androidx.compose.foundation.MutatePriority valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.foundation.MutatePriority[] values();
enum_constant public static final androidx.compose.foundation.MutatePriority Default;
enum_constant public static final androidx.compose.foundation.MutatePriority PreventUserInput;
enum_constant public static final androidx.compose.foundation.MutatePriority UserInput;
@@ -525,8 +523,6 @@
}
public enum Orientation {
- method public static androidx.compose.foundation.gestures.Orientation valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.foundation.gestures.Orientation[] values();
enum_constant public static final androidx.compose.foundation.gestures.Orientation Horizontal;
enum_constant public static final androidx.compose.foundation.gestures.Orientation Vertical;
}
@@ -1130,16 +1126,16 @@
}
@SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface PrefetchExecutor {
- method public void requestPrefetch(androidx.compose.foundation.lazy.layout.PrefetchExecutor.Request request);
+ method public void requestPrefetch(androidx.compose.foundation.lazy.layout.PrefetchRequest prefetchRequest);
}
- public static sealed interface PrefetchExecutor.Request {
- method public boolean isComposed();
- method public boolean isValid();
- method public void performComposition();
- method public void performMeasure();
- property public abstract boolean isComposed;
- property public abstract boolean isValid;
+ @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface PrefetchRequest {
+ method public boolean execute(androidx.compose.foundation.lazy.layout.PrefetchRequestScope);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface PrefetchRequestScope {
+ method public long getAvailableTimeNanos();
+ property public abstract long availableTimeNanos;
}
}
@@ -1395,6 +1391,10 @@
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public androidx.compose.ui.geometry.Rect calculateRectForParent(androidx.compose.ui.geometry.Rect localRect);
}
+ public final class ScrollIntoView {
+ method public static suspend Object? scrollIntoView(androidx.compose.ui.node.DelegatableNode, optional androidx.compose.ui.geometry.Rect? rect, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ }
+
}
package androidx.compose.foundation.selection {
@@ -1524,12 +1524,10 @@
public final class BasicSecureTextFieldKt {
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
- method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
}
public final class BasicTextField2Kt {
- method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
- method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+ method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
}
public final class BasicTextFieldKt {
@@ -1602,18 +1600,22 @@
@androidx.compose.runtime.Immutable public final class KeyboardOptions {
ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
- ctor public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus);
+ ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus);
+ ctor public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
- method public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean showKeyboardOnFocus);
+ method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus);
+ method public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean showKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
method public boolean getAutoCorrect();
method public int getCapitalization();
+ method public androidx.compose.ui.text.intl.LocaleList? getHintLocales();
method public int getImeAction();
method public int getKeyboardType();
method public androidx.compose.ui.text.input.PlatformImeOptions? getPlatformImeOptions();
method public boolean getShouldShowKeyboardOnFocus();
property public final boolean autoCorrect;
property public final int capitalization;
+ property public final androidx.compose.ui.text.intl.LocaleList? hintLocales;
property public final int imeAction;
property public final int keyboardType;
property public final androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions;
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 2ec7f69..7a02cf6 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -193,8 +193,6 @@
}
public enum MutatePriority {
- method public static androidx.compose.foundation.MutatePriority valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.foundation.MutatePriority[] values();
enum_constant public static final androidx.compose.foundation.MutatePriority Default;
enum_constant public static final androidx.compose.foundation.MutatePriority PreventUserInput;
enum_constant public static final androidx.compose.foundation.MutatePriority UserInput;
@@ -527,8 +525,6 @@
}
public enum Orientation {
- method public static androidx.compose.foundation.gestures.Orientation valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.foundation.gestures.Orientation[] values();
enum_constant public static final androidx.compose.foundation.gestures.Orientation Horizontal;
enum_constant public static final androidx.compose.foundation.gestures.Orientation Vertical;
}
@@ -1132,16 +1128,16 @@
}
@SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface PrefetchExecutor {
- method public void requestPrefetch(androidx.compose.foundation.lazy.layout.PrefetchExecutor.Request request);
+ method public void requestPrefetch(androidx.compose.foundation.lazy.layout.PrefetchRequest prefetchRequest);
}
- public static sealed interface PrefetchExecutor.Request {
- method public boolean isComposed();
- method public boolean isValid();
- method public void performComposition();
- method public void performMeasure();
- property public abstract boolean isComposed;
- property public abstract boolean isValid;
+ @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface PrefetchRequest {
+ method public boolean execute(androidx.compose.foundation.lazy.layout.PrefetchRequestScope);
+ }
+
+ @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface PrefetchRequestScope {
+ method public long getAvailableTimeNanos();
+ property public abstract long availableTimeNanos;
}
}
@@ -1397,6 +1393,10 @@
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public androidx.compose.ui.geometry.Rect calculateRectForParent(androidx.compose.ui.geometry.Rect localRect);
}
+ public final class ScrollIntoView {
+ method public static suspend Object? scrollIntoView(androidx.compose.ui.node.DelegatableNode, optional androidx.compose.ui.geometry.Rect? rect, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ }
+
}
package androidx.compose.foundation.selection {
@@ -1526,12 +1526,10 @@
public final class BasicSecureTextFieldKt {
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
- method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
}
public final class BasicTextField2Kt {
- method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
- method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+ method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
}
public final class BasicTextFieldKt {
@@ -1604,18 +1602,22 @@
@androidx.compose.runtime.Immutable public final class KeyboardOptions {
ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
- ctor public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus);
+ ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus);
+ ctor public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
- method public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean showKeyboardOnFocus);
+ method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus);
+ method public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean showKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
method public boolean getAutoCorrect();
method public int getCapitalization();
+ method public androidx.compose.ui.text.intl.LocaleList? getHintLocales();
method public int getImeAction();
method public int getKeyboardType();
method public androidx.compose.ui.text.input.PlatformImeOptions? getPlatformImeOptions();
method public boolean getShouldShowKeyboardOnFocus();
property public final boolean autoCorrect;
property public final int capitalization;
+ property public final androidx.compose.ui.text.intl.LocaleList? hintLocales;
property public final int imeAction;
property public final int keyboardType;
property public final androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions;
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index ab1f2c9..350770b 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -43,7 +43,7 @@
implementation(libs.kotlinStdlibCommon)
api("androidx.collection:collection:1.4.0")
api(project(":compose:animation:animation"))
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
api(project(":compose:ui:ui"))
implementation("androidx.compose.ui:ui-text:1.6.0")
implementation("androidx.compose.ui:ui-util:1.6.0")
@@ -154,7 +154,7 @@
androidx {
name = "Compose Foundation"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2018"
description = "Higher level abstractions of the Compose UI primitives. This library is design system agnostic, providing the high-level building blocks for both application and design-system developers"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeTextPointerIcon.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeTextPointerIcon.kt
index 88b4d52..18d950b 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeTextPointerIcon.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeTextPointerIcon.kt
@@ -22,7 +22,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
@@ -37,9 +37,15 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+private enum class ParentOverride { None, Override, NoOverride }
+
@Preview
@Composable
fun TextPointerIconDemo() {
@@ -51,28 +57,64 @@
) {
Text(
"""The texts below demonstrate how different pointer hover icons work on
- | different texts and text fields."""
+ | different texts and text fields with different permutations of
+ | overrideDescendants."""
.trimMargin().replace("\n", "")
)
- IconDemoColumn()
- Column(
- modifier = Modifier
- .pointerHoverIcon(PointerIcon.Hand)
- .border(1.dp, Color.LightGray)
- .padding(16.dp),
- verticalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- Text("This box is set to the hand icon.")
- IconDemoColumn()
+ IconDemoRectangle(parentOverride = ParentOverride.None, textOverride = false)
+ IconDemoRectangle(parentOverride = ParentOverride.None, textOverride = true)
+ IconDemoRectangle(parentOverride = ParentOverride.NoOverride, textOverride = false)
+ IconDemoRectangle(parentOverride = ParentOverride.NoOverride, textOverride = true)
+ IconDemoRectangle(parentOverride = ParentOverride.Override, textOverride = false)
+ IconDemoRectangle(parentOverride = ParentOverride.Override, textOverride = true)
+ }
+}
+
+@Composable
+private fun IconDemoRectangle(parentOverride: ParentOverride, textOverride: Boolean) {
+ val rectanglePointerIconModifier = when (parentOverride) {
+ ParentOverride.None -> Modifier
+ else -> Modifier.pointerHoverIcon(
+ icon = PointerIcon.Hand,
+ overrideDescendants = parentOverride == ParentOverride.Override
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .then(rectanglePointerIconModifier)
+ .border(1.dp, Color.LightGray)
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ val annotatedString = buildAnnotatedString {
+ when (parentOverride) {
+ ParentOverride.None -> {
+ append("This box does ")
+ withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { append("not") }
+ append(" set a pointer hover icon.")
+ }
+
+ else -> {
+ append("This box sets the hand pointer icon with ")
+ appendCode("overrideDescendants = ${parentOverride == ParentOverride.Override}")
+ append(".")
+ }
+ }
+ append(" Each Text/TextField uses ")
+ appendCode("overrideDescendants = $textOverride")
+ append(" when setting their pointer hover icons.")
}
+ Text(annotatedString)
+ IconDemoColumn(overrideDescendants = textOverride)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun IconDemoColumn() {
+private fun IconDemoColumn(overrideDescendants: Boolean) {
val borderMod = Modifier.border(1.dp, Color.LightGray)
- val iconMod = borderMod.pointerHoverIcon(PointerIcon.Crosshair)
+ val iconMod = borderMod.pointerHoverIcon(PointerIcon.Crosshair, overrideDescendants)
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Text("Regular Text, icon not set", borderMod)
@@ -93,9 +135,9 @@
TextField(mod, { mod = it }, iconMod)
val nonModTfs = rememberTextFieldState("BTF2, icon not set")
- BasicTextField2(nonModTfs, borderMod)
+ BasicTextField(nonModTfs, borderMod)
val modTfs = rememberTextFieldState("BTF2, icon crosshair")
- BasicTextField2(modTfs, iconMod)
+ BasicTextField(modTfs, iconMod)
}
}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
index 54d24fc..006e233 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
@@ -17,13 +17,13 @@
package androidx.compose.foundation.demos.text
import androidx.compose.foundation.demos.text2.BasicSecureTextFieldDemos
-import androidx.compose.foundation.demos.text2.BasicTextField2CustomPinFieldDemo
-import androidx.compose.foundation.demos.text2.BasicTextField2Demos
-import androidx.compose.foundation.demos.text2.BasicTextField2FilterDemos
-import androidx.compose.foundation.demos.text2.BasicTextField2InScrollableDemo
-import androidx.compose.foundation.demos.text2.BasicTextField2LongTextDemo
-import androidx.compose.foundation.demos.text2.BasicTextField2OutputTransformationDemos
-import androidx.compose.foundation.demos.text2.BasicTextField2ValueCallbackDemo
+import androidx.compose.foundation.demos.text2.BasicTextFieldCustomPinFieldDemo
+import androidx.compose.foundation.demos.text2.BasicTextFieldDemos
+import androidx.compose.foundation.demos.text2.BasicTextFieldFilterDemos
+import androidx.compose.foundation.demos.text2.BasicTextFieldInScrollableDemo
+import androidx.compose.foundation.demos.text2.BasicTextFieldLongTextDemo
+import androidx.compose.foundation.demos.text2.BasicTextFieldOutputTransformationDemos
+import androidx.compose.foundation.demos.text2.BasicTextFieldValueCallbackDemo
import androidx.compose.foundation.demos.text2.DecorationBoxDemos
import androidx.compose.foundation.demos.text2.KeyboardActionsDemos
import androidx.compose.foundation.demos.text2.KeyboardOptionsDemos
@@ -31,10 +31,10 @@
import androidx.compose.foundation.demos.text2.ScrollableDemos
import androidx.compose.foundation.demos.text2.ScrollableDemosRtl
import androidx.compose.foundation.demos.text2.SwapFieldSameStateDemo
-import androidx.compose.foundation.demos.text2.TextField2CursorNotBlinkingInUnfocusedWindowDemo
+import androidx.compose.foundation.demos.text2.TextFieldCursorNotBlinkingInUnfocusedWindowDemo
import androidx.compose.foundation.demos.text2.TextFieldLineLimitsDemos
import androidx.compose.foundation.demos.text2.TextFieldReceiveContentDemo
-import androidx.compose.foundation.samples.BasicTextField2UndoSample
+import androidx.compose.foundation.samples.BasicTextFieldUndoSample
import androidx.compose.integration.demos.common.ComposableDemo
import androidx.compose.integration.demos.common.DemoCategory
@@ -109,7 +109,7 @@
)
),
DemoCategory(
- "Text Input",
+ "Legacy Text Input (BasicTextFieldv1)",
listOf(
ComposableDemo("Basic input fields") { InputFieldDemo() },
ComposableDemo("Capitalization/AutoCorrect") {
@@ -153,10 +153,10 @@
)
),
DemoCategory(
- "BasicTextField2",
+ "Text Input (BasicTextFieldv2)",
listOf(
- ComposableDemo("Basic text input") { BasicTextField2Demos() },
- ComposableDemo("Value/callback overload") { BasicTextField2ValueCallbackDemo() },
+ ComposableDemo("Basic text input") { BasicTextFieldDemos() },
+ ComposableDemo("Value/callback overload") { BasicTextFieldValueCallbackDemo() },
ComposableDemo("Keyboard Options") { KeyboardOptionsDemos() },
ComposableDemo("Keyboard Actions") { KeyboardActionsDemos() },
ComposableDemo("Decoration Box") { DecorationBoxDemos() },
@@ -165,21 +165,21 @@
ComposableDemo("Ltr") { ScrollableDemos() },
ComposableDemo("Rtl") { ScrollableDemosRtl() },
)),
- ComposableDemo("Inside Scrollable") { BasicTextField2InScrollableDemo() },
- ComposableDemo("Filters") { BasicTextField2FilterDemos() },
+ ComposableDemo("Inside Scrollable") { BasicTextFieldInScrollableDemo() },
+ ComposableDemo("Filters") { BasicTextFieldFilterDemos() },
DemoCategory("Receive Content", listOf(
ComposableDemo("Basic") { TextFieldReceiveContentDemo() },
ComposableDemo("Nested") { NestedReceiveContentDemo() },
)),
ComposableDemo("Output transformation") {
- BasicTextField2OutputTransformationDemos()
+ BasicTextFieldOutputTransformationDemos()
},
ComposableDemo("Secure Field") { BasicSecureTextFieldDemos() },
ComposableDemo("Swap the field but reuse the state") { SwapFieldSameStateDemo() },
- ComposableDemo("Custom PIN field") { BasicTextField2CustomPinFieldDemo() },
- ComposableDemo("Undo/Redo") { BasicTextField2UndoSample() },
- ComposableDemo("Long text") { BasicTextField2LongTextDemo() },
- ComposableDemo("Cursor") { TextField2CursorNotBlinkingInUnfocusedWindowDemo() }
+ ComposableDemo("Custom PIN field") { BasicTextFieldCustomPinFieldDemo() },
+ ComposableDemo("Undo/Redo") { BasicTextFieldUndoSample() },
+ ComposableDemo("Long text") { BasicTextFieldLongTextDemo() },
+ ComposableDemo("Cursor") { TextFieldCursorNotBlinkingInUnfocusedWindowDemo() }
)
),
DemoCategory(
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextFieldKeyboardTypeDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextFieldKeyboardTypeDemo.kt
index d33beff..90b24cd 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextFieldKeyboardTypeDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextFieldKeyboardTypeDemo.kt
@@ -16,9 +16,17 @@
package androidx.compose.foundation.demos.text
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardOptions
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.text.input.KeyboardType
+import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.ui.tooling.preview.Preview
@Preview
@@ -33,6 +41,7 @@
item { Item(KeyboardType.Email) }
item { Item(KeyboardType.Password) }
item { Item(KeyboardType.NumberPassword) }
+ item { HintLocaleDemo(LocaleList("de")) }
}
}
@@ -41,3 +50,20 @@
TagLine(tag = "Keyboard Type: $keyboardType")
EditLine(keyboardType = keyboardType)
}
+
+@Composable
+private fun HintLocaleDemo(localeList: LocaleList) {
+ Column {
+ TagLine(tag = "Hints IME Locale: $localeList")
+
+ var text by remember { mutableStateOf("") }
+ BasicTextField(
+ modifier = demoTextFieldModifiers,
+ value = text,
+ onValueChange = { text = it },
+ keyboardOptions = KeyboardOptions(
+ hintLocales = localeList
+ )
+ )
+ }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2CustomPinFieldDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldCustomPinFieldDemo.kt
similarity index 98%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2CustomPinFieldDemo.kt
rename to compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldCustomPinFieldDemo.kt
index 48cb7eb..b433b85f 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2CustomPinFieldDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldCustomPinFieldDemo.kt
@@ -28,7 +28,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldBuffer
@@ -74,7 +74,7 @@
import kotlinx.coroutines.flow.filter
@Composable
-fun BasicTextField2CustomPinFieldDemo() {
+fun BasicTextFieldCustomPinFieldDemo() {
val viewModel = remember { VerifyPinViewModel() }
VerifyPinScreen(viewModel)
}
@@ -188,7 +188,7 @@
val contentAlpha = if (enabled) 1f else 0.3f
val contentColor = LocalContentColor.current.copy(alpha = contentAlpha)
- BasicTextField2(
+ BasicTextField(
state = state.textState,
inputTransformation = state.filter,
modifier = modifier
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldDemos.kt
similarity index 83%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt
rename to compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldDemos.kt
index c4dc19a..ceab4e2 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldDemos.kt
@@ -28,7 +28,7 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.verticalScroll
@@ -62,12 +62,12 @@
Text("Swap")
}
if (swapped) {
- BasicTextField2(
+ BasicTextField(
state,
Modifier.border(1.dp, Color.Magenta)
)
} else {
- BasicTextField2(
+ BasicTextField(
state,
Modifier.border(1.dp, Color.Blue)
)
@@ -76,31 +76,31 @@
}
@Composable
-fun BasicTextField2Demos() {
+fun BasicTextFieldDemos() {
Column(
Modifier
.imePadding()
.verticalScroll(rememberScrollState())
) {
- TagLine(tag = "Plain BasicTextField2")
- PlainBasicTextField2()
+ TagLine(tag = "Plain BasicTextField")
+ PlainBasicTextField()
- TagLine(tag = "Single Line BasicTextField2")
- SingleLineBasicTextField2()
+ TagLine(tag = "Single Line BasicTextField")
+ SingleLineBasicTextField()
- TagLine(tag = "Multi Line BasicTextField2")
- MultiLineBasicTextField2()
+ TagLine(tag = "Multi Line BasicTextField")
+ MultiLineBasicTextField()
- TagLine(tag = "State toggling BasicTextField2")
- StateTogglingBasicTextField2()
+ TagLine(tag = "State toggling BasicTextField")
+ StateTogglingBasicTextField()
- TagLine(tag = "BasicTextField2 Edit Controls")
- BasicTextField2EditControls()
+ TagLine(tag = "BasicTextField Edit Controls")
+ BasicTextFieldEditControls()
}
}
@Composable
-fun BasicTextField2ValueCallbackDemo() {
+fun BasicTextFieldValueCallbackDemo() {
Column(
Modifier
.imePadding()
@@ -117,7 +117,7 @@
@Composable
private fun SimpleValueCallbackDemo() {
var text by remember { mutableStateOf("") }
- BasicTextField2(
+ BasicTextField(
value = text,
onValueChange = { text = it },
modifier = demoTextFieldModifiers
@@ -127,7 +127,7 @@
@Composable
private fun CapitalizeValueCallbackDemo() {
var text by remember { mutableStateOf("") }
- BasicTextField2(
+ BasicTextField(
value = text,
onValueChange = { text = it.toUpperCase(Locale.current) },
modifier = demoTextFieldModifiers
@@ -137,16 +137,16 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun PlainBasicTextField2() {
+fun PlainBasicTextField() {
val state = remember { TextFieldState() }
- BasicTextField2(state, demoTextFieldModifiers, textStyle = LocalTextStyle.current)
+ BasicTextField(state, demoTextFieldModifiers, textStyle = LocalTextStyle.current)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun SingleLineBasicTextField2() {
+fun SingleLineBasicTextField() {
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = demoTextFieldModifiers,
textStyle = TextStyle(fontSize = fontSize8),
@@ -156,9 +156,9 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun MultiLineBasicTextField2() {
+fun MultiLineBasicTextField() {
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = demoTextFieldModifiers,
textStyle = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Center),
@@ -171,7 +171,7 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun StateTogglingBasicTextField2() {
+fun StateTogglingBasicTextField() {
var counter by remember { mutableIntStateOf(0) }
val states = remember { listOf(TextFieldState(), TextFieldState()) }
val state = states[counter]
@@ -180,12 +180,12 @@
counter %= 2
})
- BasicTextField2(state, demoTextFieldModifiers, textStyle = LocalTextStyle.current)
+ BasicTextField(state, demoTextFieldModifiers, textStyle = LocalTextStyle.current)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun BasicTextField2EditControls() {
+fun BasicTextFieldEditControls() {
var enabled by remember { mutableStateOf(true) }
var readOnly by remember { mutableStateOf(false) }
val state = remember { TextFieldState("Content goes here") }
@@ -201,7 +201,7 @@
Checkbox(checked = readOnly, onCheckedChange = { readOnly = it })
}
- BasicTextField2(
+ BasicTextField(
state,
demoTextFieldModifiers,
textStyle = LocalTextStyle.current,
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldFilterDemos.kt
similarity index 82%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt
rename to compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldFilterDemos.kt
index 1951d83..cf89d0e 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldFilterDemos.kt
@@ -26,12 +26,12 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.samples.BasicTextField2ChangeIterationSample
-import androidx.compose.foundation.samples.BasicTextField2ChangeReverseIterationSample
-import androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
-import androidx.compose.foundation.samples.BasicTextField2InputTransformationByValueChooseSample
-import androidx.compose.foundation.samples.BasicTextField2InputTransformationByValueReplaceSample
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.samples.BasicTextFieldChangeIterationSample
+import androidx.compose.foundation.samples.BasicTextFieldChangeReverseIterationSample
+import androidx.compose.foundation.samples.BasicTextFieldCustomInputTransformationSample
+import androidx.compose.foundation.samples.BasicTextFieldInputTransformationByValueChooseSample
+import androidx.compose.foundation.samples.BasicTextFieldInputTransformationByValueReplaceSample
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldBuffer
@@ -53,7 +53,7 @@
import androidx.core.text.isDigitsOnly
@Composable
-fun BasicTextField2FilterDemos() {
+fun BasicTextFieldFilterDemos() {
Column(
Modifier
.imePadding()
@@ -65,7 +65,7 @@
TagLine(tag = "maxLength(5)")
FilterDemo(filter = InputTransformation.maxLengthInChars(5))
- TagLine(tag = "Digits Only BasicTextField2")
+ TagLine(tag = "Digits Only BasicTextField")
DigitsOnlyDemo()
TagLine(tag = "Change filter")
@@ -73,27 +73,27 @@
TagLine(tag = "Custom (type backwards with prompt)")
Box(demoTextFieldModifiers, propagateMinConstraints = true) {
- BasicTextField2CustomInputTransformationSample()
+ BasicTextFieldCustomInputTransformationSample()
}
TagLine(tag = "Custom (string,string->string with replacement)")
Box(demoTextFieldModifiers, propagateMinConstraints = true) {
- BasicTextField2InputTransformationByValueReplaceSample()
+ BasicTextFieldInputTransformationByValueReplaceSample()
}
TagLine(tag = "Custom (string,string->string with choice)")
Box(demoTextFieldModifiers, propagateMinConstraints = true) {
- BasicTextField2InputTransformationByValueChooseSample()
+ BasicTextFieldInputTransformationByValueChooseSample()
}
TagLine(tag = "Change tracking (change logging sample)")
Box(demoTextFieldModifiers, propagateMinConstraints = true) {
- BasicTextField2ChangeIterationSample()
+ BasicTextFieldChangeIterationSample()
}
TagLine(tag = "Change tracking (insert mode sample)")
Box(demoTextFieldModifiers, propagateMinConstraints = true) {
- BasicTextField2ChangeReverseIterationSample()
+ BasicTextFieldChangeReverseIterationSample()
}
}
}
@@ -120,7 +120,7 @@
@Composable
private fun FilterDemo(filter: InputTransformation) {
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = filter,
modifier = demoTextFieldModifiers
@@ -139,7 +139,7 @@
filter = if (filter == null) InputTransformation.allCaps(Locale.current) else null
})
}
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = filter,
modifier = demoTextFieldModifiers
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2InScrollableDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldInScrollableDemo.kt
similarity index 97%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2InScrollableDemo.kt
rename to compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldInScrollableDemo.kt
index a664fcb..5a2224d 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2InScrollableDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldInScrollableDemo.kt
@@ -33,7 +33,7 @@
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.LocalTextStyle
@@ -59,7 +59,7 @@
@Preview(showBackground = true)
@Composable
-fun BasicTextField2InScrollableDemo() {
+fun BasicTextFieldInScrollableDemo() {
var scrollableType by remember { mutableStateOf(ScrollableType2.values().first()) }
Column(Modifier.windowInsetsPadding(WindowInsets.ime)) {
@@ -127,7 +127,7 @@
val state = rememberTextFieldState()
Row {
Text("$index", modifier = Modifier.padding(end = 8.dp))
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = LocalTextStyle.current,
modifier = demoTextFieldModifiers
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2LongTextDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldLongTextDemo.kt
similarity index 93%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2LongTextDemo.kt
rename to compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldLongTextDemo.kt
index a3ba95c..bf39fb9 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2LongTextDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldLongTextDemo.kt
@@ -20,7 +20,7 @@
import androidx.compose.foundation.demos.text.loremIpsumWords
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.imePadding
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material.Text
@@ -30,11 +30,11 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun BasicTextField2LongTextDemo() {
+fun BasicTextFieldLongTextDemo() {
val text = remember { TextFieldState(generateString(charCount = 100_000)) }
Column(Modifier.imePadding()) {
- BasicTextField2(
+ BasicTextField(
state = text,
modifier = Modifier
.weight(1f)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2OutputTransformationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt
similarity index 95%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2OutputTransformationDemos.kt
rename to compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt
index 4252b9e..718649c 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2OutputTransformationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt
@@ -24,7 +24,7 @@
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.foundation.text.input.TextFieldBuffer
@@ -59,7 +59,7 @@
import androidx.compose.ui.unit.dp
@Composable
-fun BasicTextField2OutputTransformationDemos() {
+fun BasicTextFieldOutputTransformationDemos() {
Column(
modifier = Modifier
.imePadding()
@@ -101,28 +101,28 @@
Checkbox(checked = prefixEnabled, onCheckedChange = { prefixEnabled = it })
Text("Prefix insertion ")
Text("\"", style = MaterialTheme.typography.caption)
- BasicTextField2(prefix, textStyle = MaterialTheme.typography.caption)
+ BasicTextField(prefix, textStyle = MaterialTheme.typography.caption)
Text("\"", style = MaterialTheme.typography.caption)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = middleWedge, onCheckedChange = { middleWedge = it })
Text("Middle insertion ")
Text("\"", style = MaterialTheme.typography.caption)
- BasicTextField2(middle, textStyle = MaterialTheme.typography.caption)
+ BasicTextField(middle, textStyle = MaterialTheme.typography.caption)
Text("\"", style = MaterialTheme.typography.caption)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = suffixEnabled, onCheckedChange = { suffixEnabled = it })
Text("Suffix insertion ")
Text("\"", style = MaterialTheme.typography.caption)
- BasicTextField2(suffix, textStyle = MaterialTheme.typography.caption)
+ BasicTextField(suffix, textStyle = MaterialTheme.typography.caption)
Text("\"", style = MaterialTheme.typography.caption)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = replacementEnabled, onCheckedChange = { replacementEnabled = it })
Text("Replacement ")
Text("s/abc/", style = MaterialTheme.typography.caption)
- BasicTextField2(replacement, textStyle = MaterialTheme.typography.caption)
+ BasicTextField(replacement, textStyle = MaterialTheme.typography.caption)
Text("/", style = MaterialTheme.typography.caption)
}
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -135,7 +135,7 @@
mutableStateOf({ null })
}
var isFirstFieldFocused by remember { mutableStateOf(false) }
- BasicTextField2(
+ BasicTextField(
state = text,
onTextLayout = { textLayoutResultProvider = it },
modifier = Modifier
@@ -174,7 +174,7 @@
contentDescription = null,
modifier = Modifier.alignBy { (it.measuredHeight * 0.75f).toInt() }
)
- BasicTextField2(
+ BasicTextField(
state = text,
modifier = Modifier
.alignByBaseline()
@@ -210,7 +210,7 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun PhoneNumberAsYouTypeDemo() {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = demoTextFieldModifiers,
outputTransformation = PhoneNumberOutputTransformation(pad = false),
@@ -222,7 +222,7 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun PhoneNumberFullTemplateDemo() {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = demoTextFieldModifiers,
// Monospace prevents the template from moving while characters are entered.
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/CursorDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/CursorDemos.kt
index 167383f6..7f57c446 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/CursorDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/CursorDemos.kt
@@ -25,7 +25,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.Button
import androidx.compose.material.Surface
@@ -45,14 +45,14 @@
import androidx.compose.ui.window.Dialog
@Composable
-fun TextField2CursorNotBlinkingInUnfocusedWindowDemo() {
+fun TextFieldCursorNotBlinkingInUnfocusedWindowDemo() {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
val textFieldDecoration = Modifier
.border(2.dp, Color.DarkGray, RoundedCornerShape(5.dp))
.padding(8.dp)
val textState = rememberTextFieldState("hello")
- BasicTextField2(textState, textFieldDecoration)
+ BasicTextField(textState, textFieldDecoration)
var showDialog by remember { mutableStateOf(false) }
Button(
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt
index 641cc59..6962c67 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt
@@ -27,7 +27,7 @@
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi
@@ -56,14 +56,14 @@
SimpleDecorationWithLabel()
TagLine(tag = "OutlinedTextField")
- OutlinedBasicTextField2()
+ OutlinedBasicTextField()
}
}
@Composable
fun SimpleDecorationWithLabel() {
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier,
textStyle = LocalTextStyle.current,
@@ -78,12 +78,12 @@
}
@Composable
-fun OutlinedBasicTextField2() {
+fun OutlinedBasicTextField() {
val state = remember { TextFieldState() }
val cursorColor by TextFieldDefaults
.outlinedTextFieldColors()
.cursorColor(isError = false)
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier,
textStyle = LocalTextStyle.current.copy(color = LocalContentColor.current),
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardActionsDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardActionsDemos.kt
index bdeaf20..4e47814 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardActionsDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardActionsDemos.kt
@@ -24,7 +24,7 @@
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActionScope
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
@@ -131,7 +131,7 @@
) {
TagLine(tag = "Ime Action: $imeAction, singleLine: $singleLine")
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
modifier = demoTextFieldModifiers,
state = state,
keyboardOptions = KeyboardOptions(
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
index 22599fe..446e6bc 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
@@ -29,7 +29,7 @@
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material.Button
@@ -43,6 +43,7 @@
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -60,6 +61,7 @@
item { Item(KeyboardType.NumberPassword) }
item { ShowKeyboardOnFocus(true) }
item { ShowKeyboardOnFocus(false) }
+ item { HintLocaleDemo(LocaleList("de")) }
}
}
@@ -76,7 +78,7 @@
text: String = ""
) {
val state = remember { TextFieldState(text) }
- BasicTextField2(
+ BasicTextField(
modifier = demoTextFieldModifiers,
state = state,
keyboardOptions = KeyboardOptions(
@@ -94,7 +96,7 @@
val state = remember { TextFieldState("") }
val focusRequester = remember { FocusRequester() }
- BasicTextField2(
+ BasicTextField(
modifier = demoTextFieldModifiers.focusRequester(focusRequester),
state = state,
keyboardOptions = KeyboardOptions(
@@ -107,6 +109,22 @@
}
}
+@Composable
+private fun HintLocaleDemo(localeList: LocaleList) {
+ Column {
+ TagLine(tag = "Hints IME Locale: $localeList")
+
+ val state = remember { TextFieldState("") }
+ BasicTextField(
+ modifier = demoTextFieldModifiers,
+ state = state,
+ keyboardOptions = KeyboardOptions(
+ hintLocales = localeList
+ )
+ )
+ }
+}
+
val demoTextFieldModifiers = Modifier
.fillMaxWidth()
.padding(6.dp)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt
index 434b903..d83f3ef 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt
@@ -46,7 +46,7 @@
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.verticalScroll
@@ -180,7 +180,7 @@
}
}
}
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = demoTextFieldModifiers,
textStyle = LocalTextStyle.current
@@ -253,7 +253,7 @@
}
}
) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = demoTextFieldModifiers,
textStyle = LocalTextStyle.current
@@ -430,7 +430,7 @@
" - The outermost one consumes everything that's passed to it.\n" +
" - The middle one only consumes image content.\n" +
" - The innermost one only consumes text content.\n" +
- " - BasicTextField2 that's nested the deepest would delegate whatever it receives " +
+ " - BasicTextField that's nested the deepest would delegate whatever it receives " +
"to all 3 parents in order of proximity.\n" +
" - Each node shows all the items it receives, not just what it consumes.\n\n" +
"ReceiveContent works with keyboard, paste, and drag/drop.\n" +
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ScrollDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ScrollDemos.kt
index f1bb452..7f6ebfb 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ScrollDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ScrollDemos.kt
@@ -27,7 +27,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
import androidx.compose.foundation.text.input.TextFieldState
@@ -95,7 +95,7 @@
)
)
}
- BasicTextField2(
+ BasicTextField(
state = state,
lineLimits = SingleLine,
textStyle = TextStyle(fontSize = 24.sp),
@@ -117,7 +117,7 @@
)
)
}
- BasicTextField2(
+ BasicTextField(
state = state,
lineLimits = SingleLine,
textStyle = TextStyle(fontSize = 24.sp)
@@ -137,7 +137,7 @@
)
)
}
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = TextStyle(fontSize = 24.sp),
modifier = Modifier.heightIn(max = 200.dp),
@@ -168,7 +168,7 @@
},
valueRange = 0f..scrollState.maxValue.toFloat()
)
- BasicTextField2(
+ BasicTextField(
state = state,
scrollState = scrollState,
textStyle = TextStyle(fontSize = 24.sp),
@@ -209,14 +209,14 @@
},
valueRange = 0f..scrollState.maxValue.toFloat()
)
- BasicTextField2(
+ BasicTextField(
state = state1,
scrollState = scrollState,
textStyle = TextStyle(fontSize = 24.sp),
modifier = Modifier.fillMaxWidth(),
lineLimits = SingleLine
)
- BasicTextField2(
+ BasicTextField(
state = state2,
scrollState = scrollState,
textStyle = TextStyle(fontSize = 24.sp),
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/TextFieldLineLimitsDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/TextFieldLineLimitsDemos.kt
index 859a1c0..1a36889 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/TextFieldLineLimitsDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/TextFieldLineLimitsDemos.kt
@@ -23,7 +23,7 @@
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.verticalScroll
@@ -64,7 +64,7 @@
fun DefaultLineLimits() {
Text("Default")
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
lineLimits = TextFieldLineLimits.Default,
textStyle = LocalTextStyle.current,
@@ -77,7 +77,7 @@
fun SingleLineLimits() {
Text("Single Line")
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
lineLimits = TextFieldLineLimits.SingleLine,
textStyle = LocalTextStyle.current,
@@ -119,7 +119,7 @@
maxLines = maxLines.coerceAtLeast(minLines)
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
lineLimits = TextFieldLineLimits.MultiLine(
minHeightInLines = minLines,
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextField2Samples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextField2Samples.kt
deleted file mode 100644
index 50710b7..0000000
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextField2Samples.kt
+++ /dev/null
@@ -1,431 +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.
- */
-
-@file:OptIn(ExperimentalFoundationApi::class, ExperimentalFoundationApi::class)
-@file:Suppress("UNUSED_PARAMETER", "unused", "LocalVariableName", "RedundantSuspendModifier")
-
-package androidx.compose.foundation.samples
-
-import androidx.annotation.Sampled
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.BasicTextField2
-import androidx.compose.foundation.text.input.InputTransformation
-import androidx.compose.foundation.text.input.TextFieldState
-import androidx.compose.foundation.text.input.byValue
-import androidx.compose.foundation.text.input.delete
-import androidx.compose.foundation.text.input.forEachChange
-import androidx.compose.foundation.text.input.forEachChangeReversed
-import androidx.compose.foundation.text.input.forEachTextValue
-import androidx.compose.foundation.text.input.insert
-import androidx.compose.foundation.text.input.rememberTextFieldState
-import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
-import androidx.compose.foundation.text.input.textAsFlow
-import androidx.compose.foundation.text.input.then
-import androidx.compose.material.Button
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Clear
-import androidx.compose.material.icons.filled.MailOutline
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.substring
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.debounce
-
-@Sampled
-fun BasicTextField2StateCompleteSample() {
- class SearchViewModel(
- val searchFieldState: TextFieldState = TextFieldState()
- ) {
- private val queryValidationRegex = """\w+""".toRegex()
-
- // Use derived state to avoid recomposing every time the text changes, and only recompose
- // when the input becomes valid or invalid.
- val isQueryValid by derivedStateOf {
- // This lambda will be re-executed every time inputState.text changes.
- searchFieldState.text.matches(queryValidationRegex)
- }
-
- var searchResults: List<String> by mutableStateOf(emptyList())
- private set
-
- /** Called while the view model is active, e.g. from a LaunchedEffect. */
- suspend fun run() {
- searchFieldState.forEachTextValue { queryText ->
- // Start a new search every time the user types something valid. If the previous
- // search is still being processed when the text is changed, it will be cancelled
- // and this code will run again with the latest query text.
- if (isQueryValid) {
- searchResults = performSearch(query = queryText)
- }
- }
- }
-
- fun clearQuery() {
- searchFieldState.setTextAndPlaceCursorAtEnd("")
- }
-
- private suspend fun performSearch(query: CharSequence): List<String> {
- TODO()
- }
- }
-
- @Composable
- fun SearchScreen(viewModel: SearchViewModel) {
- Column {
- Row {
- BasicTextField2(viewModel.searchFieldState)
- IconButton(onClick = { viewModel.clearQuery() }) {
- Icon(Icons.Default.Clear, contentDescription = "clear search query")
- }
- }
- if (!viewModel.isQueryValid) {
- Text("Invalid query", style = TextStyle(color = Color.Red))
- }
- LazyColumn {
- items(viewModel.searchResults) {
- TODO()
- }
- }
- }
- }
-}
-
-@Sampled
-fun BasicTextField2TextDerivedStateSample() {
- class ViewModel {
- private val inputValidationRegex = """\w+""".toRegex()
-
- val inputState = TextFieldState()
-
- // Use derived state to avoid recomposing every time the text changes, and only recompose
- // when the input becomes valid or invalid.
- val isInputValid by derivedStateOf {
- // This lambda will be re-executed every time inputState.text changes.
- inputState.text.matches(inputValidationRegex)
- }
- }
-
- @Composable
- fun Screen(viewModel: ViewModel) {
- Column {
- BasicTextField2(viewModel.inputState)
- if (!viewModel.isInputValid) {
- Text("Input is invalid.", style = TextStyle(color = Color.Red))
- }
- }
- }
-}
-
-@Sampled
-fun BasicTextField2StateEditSample() {
- val state = TextFieldState("hello world!")
- state.edit {
- // Insert a comma after "hello".
- insert(5, ",") // = "hello, world!"
-
- // Delete the exclamation mark.
- delete(12, 13) // = "hello, world"
-
- // Add a different name.
- append("Compose") // = "hello, Compose"
-
- // Say goodbye.
- replace(0, 5, "goodbye") // "goodbye, Compose"
-
- // Select the new name so the user can change it by just starting to type.
- selectCharsIn(TextRange(9, 16)) // "goodbye, ̲C̲o̲m̲p̲o̲s̲e"
- }
-}
-
-@Sampled
-@Composable
-fun BasicTextField2CustomInputTransformationSample() {
- val state = remember { TextFieldState() }
- BasicTextField2(state, inputTransformation = { _, new ->
- // A filter that always places newly-input text at the start of the string, after a
- // prompt character, like a shell.
- val promptChar = '>'
-
- fun CharSequence.countPrefix(char: Char): Int {
- var i = 0
- while (i < length && get(i) == char) i++
- return i
- }
-
- // Step one: Figure out the insertion point.
- val newPromptChars = new.asCharSequence().countPrefix(promptChar)
- val insertionPoint = if (newPromptChars == 0) 0 else 1
-
- // Step two: Ensure text is placed at the insertion point.
- if (new.changes.changeCount == 1) {
- val insertedRange = new.changes.getRange(0)
- val replacedRange = new.changes.getOriginalRange(0)
- if (!replacedRange.collapsed && insertedRange.collapsed) {
- // Text was deleted, delete forwards from insertion point.
- new.delete(insertionPoint, insertionPoint + replacedRange.length)
- }
- }
- // Else text was replaced or there were multiple changes - don't handle.
-
- // Step three: Ensure the prompt character is there.
- if (newPromptChars == 0) {
- new.insert(0, ">")
- }
-
- // Step four: Ensure the cursor is ready for the next input.
- new.placeCursorAfterCharAt(0)
- })
-}
-
-@Sampled
-@Composable
-fun BasicTextField2InputTransformationByValueReplaceSample() {
- val state = remember { TextFieldState() }
- BasicTextField2(
- state,
- // Convert tabs to spaces.
- inputTransformation = InputTransformation.byValue { _, proposed ->
- proposed.replace("""\t""".toRegex(), " ")
- }
- )
-}
-
-@Sampled
-@Composable
-fun BasicTextField2InputTransformationByValueChooseSample() {
- val state = remember { TextFieldState() }
- BasicTextField2(
- state,
- // Reject whitespace.
- inputTransformation = InputTransformation.byValue { current, proposed ->
- if ("""\s""".toRegex() in proposed) current else proposed
- }
- )
-}
-
-@Sampled
-fun BasicTextField2InputTransformationChainingSample() {
- val removeFirstEFilter = InputTransformation { _, new ->
- val index = new.asCharSequence().indexOf('e')
- if (index != -1) {
- new.replace(index, index + 1, "")
- }
- }
- val printECountFilter = InputTransformation { _, new ->
- println("found ${new.asCharSequence().count { it == 'e' }} 'e's in the string")
- }
-
- // Returns a filter that always prints 0 e's.
- removeFirstEFilter.then(printECountFilter)
-
- // Returns a filter that prints the number of e's before the first one is removed.
- printECountFilter.then(removeFirstEFilter)
-}
-
-@Sampled
-@Composable
-fun BasicTextField2ChangeIterationSample() {
- // Print a log message every time the text is changed.
- BasicTextField2(state = rememberTextFieldState(), inputTransformation = { _, new ->
- new.changes.forEachChange { sourceRange, replacedLength ->
- val newString = new.asCharSequence().substring(sourceRange)
- println("""$replacedLength characters were replaced with "$newString"""")
- }
- })
-}
-
-@Sampled
-@Composable
-fun BasicTextField2ChangeReverseIterationSample() {
- // Make a text field behave in "insert mode" – inserted text overwrites the text ahead of it
- // instead of being inserted.
- BasicTextField2(state = rememberTextFieldState(), inputTransformation = { _, new ->
- new.changes.forEachChangeReversed { range, originalRange ->
- if (!range.collapsed && originalRange.collapsed) {
- // New text was inserted, delete the text ahead of it.
- new.delete(
- range.end.coerceAtMost(new.length),
- (range.end + range.length).coerceAtMost(new.length)
- )
- }
- }
- })
-}
-
-@Sampled
-fun BasicTextField2ForEachTextValueSample() {
- class SearchViewModel {
- val searchFieldState = TextFieldState()
- var searchResults: List<String> by mutableStateOf(emptyList())
- private set
-
- /** Called while the view model is active, e.g. from a LaunchedEffect. */
- suspend fun run() {
- searchFieldState.forEachTextValue { queryText ->
- // Start a new search every time the user types something. If the previous search
- // is still being processed when the text is changed, it will be cancelled and this
- // code will run again with the latest query text.
- searchResults = performSearch(query = queryText)
- }
- }
-
- private suspend fun performSearch(query: CharSequence): List<String> {
- TODO()
- }
- }
-
- @Composable
- fun SearchScreen(viewModel: SearchViewModel) {
- Column {
- BasicTextField2(viewModel.searchFieldState)
- LazyColumn {
- items(viewModel.searchResults) {
- TODO()
- }
- }
- }
- }
-}
-
-@OptIn(FlowPreview::class)
-@Suppress("RedundantSuspendModifier")
-@Sampled
-fun BasicTextField2TextValuesSample() {
- class SearchViewModel {
- val searchFieldState = TextFieldState()
- var searchResults: List<String> by mutableStateOf(emptyList())
- private set
-
- /** Called while the view model is active, e.g. from a LaunchedEffect. */
- suspend fun run() {
- searchFieldState.textAsFlow()
- // Let fast typers get multiple keystrokes in before kicking off a search.
- .debounce(500)
- // collectLatest cancels the previous search if it's still running when there's a
- // new change.
- .collectLatest { queryText ->
- searchResults = performSearch(query = queryText)
- }
- }
-
- private suspend fun performSearch(query: CharSequence): List<String> {
- TODO()
- }
- }
-
- @Composable
- fun SearchScreen(viewModel: SearchViewModel) {
- Column {
- BasicTextField2(viewModel.searchFieldState)
- LazyColumn {
- items(viewModel.searchResults) {
- TODO()
- }
- }
- }
- }
-}
-
-@Sampled
-@Composable
-fun BasicTextField2UndoSample() {
- val state = rememberTextFieldState()
-
- Column(Modifier.padding(8.dp)) {
- Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
- Button(
- onClick = { state.undoState.undo() },
- enabled = state.undoState.canUndo
- ) {
- Text("Undo")
- }
-
- Button(
- onClick = { state.undoState.redo() },
- enabled = state.undoState.canRedo
- ) {
- Text("Redo")
- }
-
- Button(
- onClick = { state.undoState.clearHistory() },
- enabled = state.undoState.canUndo || state.undoState.canRedo
- ) {
- Text("Clear History")
- }
- }
-
- BasicTextField2(
- state = state,
- modifier = Modifier
- .fillMaxWidth()
- .border(1.dp, Color.LightGray, RoundedCornerShape(6.dp))
- .padding(8.dp),
- textStyle = TextStyle(fontSize = 16.sp)
- )
- }
-}
-
-@Sampled
-@Composable
-@OptIn(ExperimentalFoundationApi::class)
-fun BasicTextField2DecoratorSample() {
- val state = rememberTextFieldState("Hello, World!")
- BasicTextField2(
- state = state,
- decorator = { innerTextField ->
- // Because the decorator is used, the whole Row gets the same behaviour as the internal
- // input field would have otherwise. For example, there is no need to add a
- // `Modifier.clickable` to the Row anymore to bring the text field into focus when user
- // taps on a larger text field area which includes paddings and the icon areas.
- Row(
- Modifier
- .background(Color.LightGray, RoundedCornerShape(percent = 30))
- .padding(16.dp)
- ) {
- Icon(Icons.Default.MailOutline, contentDescription = "Mail Icon")
- Spacer(Modifier.width(16.dp))
- innerTextField()
- }
- }
- )
-}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
index d23c6354..85405eb5 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
@@ -14,39 +14,71 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalFoundationApi::class)
+@file:Suppress("UNUSED_PARAMETER", "unused", "LocalVariableName", "RedundantSuspendModifier")
+
package androidx.compose.foundation.samples
import androidx.annotation.Sampled
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+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.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.byValue
+import androidx.compose.foundation.text.input.delete
+import androidx.compose.foundation.text.input.forEachChange
+import androidx.compose.foundation.text.input.forEachChangeReversed
+import androidx.compose.foundation.text.input.forEachTextValue
+import androidx.compose.foundation.text.input.insert
+import androidx.compose.foundation.text.input.rememberTextFieldState
+import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
+import androidx.compose.foundation.text.input.textAsFlow
+import androidx.compose.foundation.text.input.then
import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.MailOutline
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.OffsetMapping
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.TransformedText
import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.text.substring
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.debounce
@Sampled
@Composable
@@ -80,7 +112,6 @@
@Sampled
@Composable
-@OptIn(ExperimentalFoundationApi::class)
fun PlaceholderBasicTextFieldSample() {
var value by rememberSaveable { mutableStateOf("initial value") }
Box {
@@ -96,7 +127,6 @@
@Sampled
@Composable
-@OptIn(ExperimentalFoundationApi::class)
fun TextFieldWithIconSample() {
var value by rememberSaveable { mutableStateOf("initial value") }
BasicTextField(
@@ -174,3 +204,363 @@
visualTransformation = creditCardTransformation
)
}
+
+@Sampled
+fun BasicTextFieldStateCompleteSample() {
+ class SearchViewModel(
+ val searchFieldState: TextFieldState = TextFieldState()
+ ) {
+ private val queryValidationRegex = """\w+""".toRegex()
+
+ // Use derived state to avoid recomposing every time the text changes, and only recompose
+ // when the input becomes valid or invalid.
+ val isQueryValid by derivedStateOf {
+ // This lambda will be re-executed every time inputState.text changes.
+ searchFieldState.text.matches(queryValidationRegex)
+ }
+
+ var searchResults: List<String> by mutableStateOf(emptyList())
+ private set
+
+ /** Called while the view model is active, e.g. from a LaunchedEffect. */
+ suspend fun run() {
+ searchFieldState.forEachTextValue { queryText ->
+ // Start a new search every time the user types something valid. If the previous
+ // search is still being processed when the text is changed, it will be cancelled
+ // and this code will run again with the latest query text.
+ if (isQueryValid) {
+ searchResults = performSearch(query = queryText)
+ }
+ }
+ }
+
+ fun clearQuery() {
+ searchFieldState.setTextAndPlaceCursorAtEnd("")
+ }
+
+ private suspend fun performSearch(query: CharSequence): List<String> {
+ TODO()
+ }
+ }
+
+ @Composable
+ fun SearchScreen(viewModel: SearchViewModel) {
+ Column {
+ Row {
+ BasicTextField(viewModel.searchFieldState)
+ IconButton(onClick = { viewModel.clearQuery() }) {
+ Icon(Icons.Default.Clear, contentDescription = "clear search query")
+ }
+ }
+ if (!viewModel.isQueryValid) {
+ Text("Invalid query", style = TextStyle(color = Color.Red))
+ }
+ LazyColumn {
+ items(viewModel.searchResults) {
+ TODO()
+ }
+ }
+ }
+ }
+}
+
+@Sampled
+fun BasicTextFieldTextDerivedStateSample() {
+ class ViewModel {
+ private val inputValidationRegex = """\w+""".toRegex()
+
+ val inputState = TextFieldState()
+
+ // Use derived state to avoid recomposing every time the text changes, and only recompose
+ // when the input becomes valid or invalid.
+ val isInputValid by derivedStateOf {
+ // This lambda will be re-executed every time inputState.text changes.
+ inputState.text.matches(inputValidationRegex)
+ }
+ }
+
+ @Composable
+ fun Screen(viewModel: ViewModel) {
+ Column {
+ BasicTextField(viewModel.inputState)
+ if (!viewModel.isInputValid) {
+ Text("Input is invalid.", style = TextStyle(color = Color.Red))
+ }
+ }
+ }
+}
+
+@Sampled
+fun BasicTextFieldStateEditSample() {
+ val state = TextFieldState("hello world!")
+ state.edit {
+ // Insert a comma after "hello".
+ insert(5, ",") // = "hello, world!"
+
+ // Delete the exclamation mark.
+ delete(12, 13) // = "hello, world"
+
+ // Add a different name.
+ append("Compose") // = "hello, Compose"
+
+ // Say goodbye.
+ replace(0, 5, "goodbye") // "goodbye, Compose"
+
+ // Select the new name so the user can change it by just starting to type.
+ selectCharsIn(TextRange(9, 16)) // "goodbye, ̲C̲o̲m̲p̲o̲s̲e"
+ }
+}
+
+@Sampled
+@Composable
+fun BasicTextFieldCustomInputTransformationSample() {
+ val state = remember { TextFieldState() }
+ BasicTextField(state, inputTransformation = { _, new ->
+ // A filter that always places newly-input text at the start of the string, after a
+ // prompt character, like a shell.
+ val promptChar = '>'
+
+ fun CharSequence.countPrefix(char: Char): Int {
+ var i = 0
+ while (i < length && get(i) == char) i++
+ return i
+ }
+
+ // Step one: Figure out the insertion point.
+ val newPromptChars = new.asCharSequence().countPrefix(promptChar)
+ val insertionPoint = if (newPromptChars == 0) 0 else 1
+
+ // Step two: Ensure text is placed at the insertion point.
+ if (new.changes.changeCount == 1) {
+ val insertedRange = new.changes.getRange(0)
+ val replacedRange = new.changes.getOriginalRange(0)
+ if (!replacedRange.collapsed && insertedRange.collapsed) {
+ // Text was deleted, delete forwards from insertion point.
+ new.delete(insertionPoint, insertionPoint + replacedRange.length)
+ }
+ }
+ // Else text was replaced or there were multiple changes - don't handle.
+
+ // Step three: Ensure the prompt character is there.
+ if (newPromptChars == 0) {
+ new.insert(0, ">")
+ }
+
+ // Step four: Ensure the cursor is ready for the next input.
+ new.placeCursorAfterCharAt(0)
+ })
+}
+
+@Sampled
+@Composable
+fun BasicTextFieldInputTransformationByValueReplaceSample() {
+ val state = remember { TextFieldState() }
+ BasicTextField(
+ state,
+ // Convert tabs to spaces.
+ inputTransformation = InputTransformation.byValue { _, proposed ->
+ proposed.replace("""\t""".toRegex(), " ")
+ }
+ )
+}
+
+@Sampled
+@Composable
+fun BasicTextFieldInputTransformationByValueChooseSample() {
+ val state = remember { TextFieldState() }
+ BasicTextField(
+ state,
+ // Reject whitespace.
+ inputTransformation = InputTransformation.byValue { current, proposed ->
+ if ("""\s""".toRegex() in proposed) current else proposed
+ }
+ )
+}
+
+@Sampled
+fun BasicTextFieldInputTransformationChainingSample() {
+ val removeFirstEFilter = InputTransformation { _, new ->
+ val index = new.asCharSequence().indexOf('e')
+ if (index != -1) {
+ new.replace(index, index + 1, "")
+ }
+ }
+ val printECountFilter = InputTransformation { _, new ->
+ println("found ${new.asCharSequence().count { it == 'e' }} 'e's in the string")
+ }
+
+ // Returns a filter that always prints 0 e's.
+ removeFirstEFilter.then(printECountFilter)
+
+ // Returns a filter that prints the number of e's before the first one is removed.
+ printECountFilter.then(removeFirstEFilter)
+}
+
+@Sampled
+@Composable
+fun BasicTextFieldChangeIterationSample() {
+ // Print a log message every time the text is changed.
+ BasicTextField(state = rememberTextFieldState(), inputTransformation = { _, new ->
+ new.changes.forEachChange { sourceRange, replacedLength ->
+ val newString = new.asCharSequence().substring(sourceRange)
+ println("""$replacedLength characters were replaced with "$newString"""")
+ }
+ })
+}
+
+@Sampled
+@Composable
+fun BasicTextFieldChangeReverseIterationSample() {
+ // Make a text field behave in "insert mode" – inserted text overwrites the text ahead of it
+ // instead of being inserted.
+ BasicTextField(state = rememberTextFieldState(), inputTransformation = { _, new ->
+ new.changes.forEachChangeReversed { range, originalRange ->
+ if (!range.collapsed && originalRange.collapsed) {
+ // New text was inserted, delete the text ahead of it.
+ new.delete(
+ range.end.coerceAtMost(new.length),
+ (range.end + range.length).coerceAtMost(new.length)
+ )
+ }
+ }
+ })
+}
+
+@Sampled
+fun BasicTextFieldForEachTextValueSample() {
+ class SearchViewModel {
+ val searchFieldState = TextFieldState()
+ var searchResults: List<String> by mutableStateOf(emptyList())
+ private set
+
+ /** Called while the view model is active, e.g. from a LaunchedEffect. */
+ suspend fun run() {
+ searchFieldState.forEachTextValue { queryText ->
+ // Start a new search every time the user types something. If the previous search
+ // is still being processed when the text is changed, it will be cancelled and this
+ // code will run again with the latest query text.
+ searchResults = performSearch(query = queryText)
+ }
+ }
+
+ private suspend fun performSearch(query: CharSequence): List<String> {
+ TODO()
+ }
+ }
+
+ @Composable
+ fun SearchScreen(viewModel: SearchViewModel) {
+ Column {
+ BasicTextField(viewModel.searchFieldState)
+ LazyColumn {
+ items(viewModel.searchResults) {
+ TODO()
+ }
+ }
+ }
+ }
+}
+
+@OptIn(FlowPreview::class)
+@Suppress("RedundantSuspendModifier")
+@Sampled
+fun BasicTextFieldTextValuesSample() {
+ class SearchViewModel {
+ val searchFieldState = TextFieldState()
+ var searchResults: List<String> by mutableStateOf(emptyList())
+ private set
+
+ /** Called while the view model is active, e.g. from a LaunchedEffect. */
+ suspend fun run() {
+ searchFieldState.textAsFlow()
+ // Let fast typers get multiple keystrokes in before kicking off a search.
+ .debounce(500)
+ // collectLatest cancels the previous search if it's still running when there's a
+ // new change.
+ .collectLatest { queryText ->
+ searchResults = performSearch(query = queryText)
+ }
+ }
+
+ private suspend fun performSearch(query: CharSequence): List<String> {
+ TODO()
+ }
+ }
+
+ @Composable
+ fun SearchScreen(viewModel: SearchViewModel) {
+ Column {
+ BasicTextField(viewModel.searchFieldState)
+ LazyColumn {
+ items(viewModel.searchResults) {
+ TODO()
+ }
+ }
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun BasicTextFieldUndoSample() {
+ val state = rememberTextFieldState()
+
+ Column(Modifier.padding(8.dp)) {
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ androidx.compose.material.Button(
+ onClick = { state.undoState.undo() },
+ enabled = state.undoState.canUndo
+ ) {
+ Text("Undo")
+ }
+
+ androidx.compose.material.Button(
+ onClick = { state.undoState.redo() },
+ enabled = state.undoState.canRedo
+ ) {
+ Text("Redo")
+ }
+
+ androidx.compose.material.Button(
+ onClick = { state.undoState.clearHistory() },
+ enabled = state.undoState.canUndo || state.undoState.canRedo
+ ) {
+ Text("Clear History")
+ }
+ }
+
+ BasicTextField(
+ state = state,
+ modifier = Modifier
+ .fillMaxWidth()
+ .border(1.dp, Color.LightGray, RoundedCornerShape(6.dp))
+ .padding(8.dp),
+ textStyle = TextStyle(fontSize = 16.sp)
+ )
+ }
+}
+
+@Sampled
+@Composable
+@OptIn(ExperimentalFoundationApi::class)
+fun BasicTextFieldDecoratorSample() {
+ val state = rememberTextFieldState("Hello, World!")
+ BasicTextField(
+ state = state,
+ decorator = { innerTextField ->
+ // Because the decorator is used, the whole Row gets the same behaviour as the internal
+ // input field would have otherwise. For example, there is no need to add a
+ // `Modifier.clickable` to the Row anymore to bring the text field into focus when user
+ // taps on a larger text field area which includes paddings and the icon areas.
+ Row(
+ Modifier
+ .background(Color.LightGray, RoundedCornerShape(percent = 30))
+ .padding(16.dp)
+ ) {
+ Icon(Icons.Default.MailOutline, contentDescription = "Mail Icon")
+ Spacer(Modifier.width(16.dp))
+ innerTextField()
+ }
+ }
+ )
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/StateSyncingModifier.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldValueSample.kt
similarity index 70%
rename from compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/StateSyncingModifier.kt
rename to compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldValueSample.kt
index e592ca7..40a14c9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/StateSyncingModifier.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldValueSample.kt
@@ -14,12 +14,18 @@
* limitations under the License.
*/
-package androidx.compose.foundation.text.input.internal
+package androidx.compose.foundation.samples
+import androidx.annotation.Sampled
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldCharSequence
import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusEventModifierNode
import androidx.compose.ui.focus.FocusState
@@ -27,34 +33,74 @@
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
+@Sampled
+@Composable
+fun BasicTextFieldWithValueOnValueChangeSample() {
+ var text by remember { mutableStateOf("") }
+ StringTextField(
+ value = text,
+ onValueChange = { text = it }
+ )
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun StringTextField(
+ value: String,
+ onValueChange: (String) -> Unit,
+ modifier: Modifier = Modifier,
+ // and other arguments you want to delegate
+) {
+ val state = remember {
+ TextFieldState(
+ initialText = value,
+ // Initialize the cursor to be at the end of the field.
+ initialSelectionInChars = TextRange(value.length)
+ )
+ }
+
+ // This is effectively a rememberUpdatedState, but it combines the updated state (text) with
+ // some state that is preserved across updates (selection).
+ var valueWithSelection by remember {
+ mutableStateOf(
+ TextFieldValue(
+ text = value,
+ selection = TextRange(value.length)
+ )
+ )
+ }
+ valueWithSelection = valueWithSelection.copy(text = value)
+
+ BasicTextField(
+ state = state,
+ modifier = modifier.then(StateSyncingModifier(
+ state = state,
+ value = valueWithSelection,
+ onValueChanged = {
+ // Don't fire the callback if only the selection/cursor changed.
+ if (it.text != valueWithSelection.text) {
+ onValueChange(it.text)
+ }
+ valueWithSelection = it
+ },
+ writeSelectionFromTextFieldValue = false
+ )),
+ // other arguments
+ )
+}
+
/**
* Synchronizes between [TextFieldState], immutable values, and value change callbacks for
- * [BasicTextField2] overloads that take a value+callback for state instead of taking a
- * [TextFieldState] directly. Effectively a fancy `rememberUpdatedState`.
- *
- * Only intended for use from [BasicTextField2].
+ * [BasicTextField] that may take a value+callback for state instead of taking a [TextFieldState]
+ * directly. Effectively a fancy `rememberUpdatedState`.
*
* @param writeSelectionFromTextFieldValue If true, [update] will synchronize the selection from the
* [TextFieldValue] to the [TextFieldState]. The text will be synchronized regardless.
*/
@OptIn(ExperimentalFoundationApi::class)
-internal fun Modifier.syncTextFieldState(
- state: TextFieldState,
- value: TextFieldValue,
- onValueChanged: (TextFieldValue) -> Unit,
- writeSelectionFromTextFieldValue: Boolean,
-): Modifier = this.then(
- StateSyncingModifier(
- state = state,
- value = value,
- onValueChanged = onValueChanged,
- writeSelectionFromTextFieldValue = writeSelectionFromTextFieldValue
- )
-)
-
-@OptIn(ExperimentalFoundationApi::class)
private class StateSyncingModifier(
private val state: TextFieldState,
private val value: TextFieldValue,
@@ -140,8 +186,9 @@
private fun updateState(value: TextFieldValue) {
state.edit {
- // Avoid registering a state change if the text isn't actually different.
- setTextIfChanged(value.text)
+ // Ideally avoid registering a state change if the text isn't actually different.
+ // Take a look at `setTextIfChanged` implementation in TextFieldBuffer
+ replace(0, length, value.text)
// The BasicTextField2(String) variant can't push a selection value, so ignore it.
if (writeSelectionFromTextFieldValue) {
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt
index 5ecc110..8b01a4e 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt
@@ -29,7 +29,7 @@
import androidx.compose.foundation.content.receiveContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
@@ -53,7 +53,7 @@
Image(bitmap = it, contentDescription = null)
}
}
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.receiveContent(setOf(MediaType.Image)) { transferableContent ->
if (!transferableContent.hasMediaType(MediaType.Image)) {
@@ -85,7 +85,7 @@
Image(bitmap = it, contentDescription = null)
}
}
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.background(
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableBoundsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableBoundsTest.kt
index 73e2677..0490fb6 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableBoundsTest.kt
@@ -24,7 +24,6 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.focus.FocusRequester
@@ -46,7 +45,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
+@OptIn(ExperimentalFoundationApi::class)
@MediumTest
@RunWith(AndroidJUnit4::class)
class FocusableBoundsTest {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt
index 38724ad..bf9e395 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt
@@ -79,7 +79,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
+@OptIn(ExperimentalFoundationApi::class)
@MediumTest
@RunWith(AndroidJUnit4::class)
class FocusableTest {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/OverscrollTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
index 826dfbbc..5a42438 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
@@ -25,15 +25,21 @@
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.testutils.assertPixelColor
+import androidx.compose.testutils.assertPixels
+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.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.toPixelMap
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -327,8 +333,8 @@
@OptIn(ExperimentalFoundationApi::class)
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
- fun overscrollIsNotClippingTheContentWhenPulled() {
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O, maxSdkVersion = Build.VERSION_CODES.R)
+ fun glowOverscroll_doesNotClip() {
lateinit var controller: AndroidEdgeEffectOverscrollEffect
val tag = "container"
rule.setContent {
@@ -336,21 +342,21 @@
controller = rememberOverscrollEffect() as AndroidEdgeEffectOverscrollEffect
Box(
Modifier
+ .fillMaxSize()
+ .wrapContentSize(Alignment.Center)
.background(Color.Red)
.testTag(tag)
) {
Box(
Modifier
- .padding(horizontal = 10.dp)
+ .padding(10.dp)
.size(10.dp)
- .clipScrollableContainer(Orientation.Vertical)
.overscroll(controller)
.drawBehind {
val extraOffset = 10.dp
.roundToPx()
.toFloat()
- // we fill the whole parent container so we can test that
- // there is no clipping
+ // Draw a green box over the entire red parent container
drawRect(
Color.Green,
Offset(-extraOffset, -extraOffset),
@@ -365,8 +371,14 @@
}
}
+ // Overscroll is not displayed, so the content should be entirely green (no clipping)
+ rule.onNodeWithTag(tag)
+ .captureToImage()
+ .assertPixels { Color.Green }
+
+ // Pull vertically down
rule.runOnIdle {
- val offset = Offset(0f, 5f)
+ val offset = Offset(x = 0f, y = 50f)
controller.applyToScroll(
offset,
source = NestedScrollSource.Drag
@@ -379,11 +391,595 @@
controller.invalidationEnabled = false
}
+ // We don't want to assert that the content is entirely green as the glow effect will
+ // change this, so instead we assert that no red from the parent box is visible.
rule.onNodeWithTag(tag)
.captureToImage()
- // if there is not clipping then the red parent is not visible
- // but we also don't want to assert that the bg is Green as some overscroll
- // effects can draw something else on top of this plain green background
+ .assertHasNoColor(Color.Red)
+ }
+
+ @OptIn(ExperimentalFoundationApi::class)
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ fun stretchOverscroll_doesNotClipCrossAxis_verticalOverscroll() {
+ lateinit var controller: AndroidEdgeEffectOverscrollEffect
+ val tag = "container"
+ rule.setContent {
+ Box {
+ controller = rememberOverscrollEffect() as AndroidEdgeEffectOverscrollEffect
+ Box(
+ Modifier
+ .fillMaxSize()
+ .wrapContentSize(Alignment.Center)
+ .background(Color.Red)
+ .testTag(tag)
+ ) {
+ Box(
+ Modifier
+ .padding(10.dp)
+ .size(10.dp)
+ // Stretch overscroll will apply the stretch to the surrounding canvas,
+ // so add a graphics layer to get a canvas sized to the content. The
+ // expected usage is for this to be clipScrollableContainer() or
+ // similar, since a container that shows overscroll should clip the
+ // main axis, but we don't use that here as we want to test the
+ // implicit clipping behavior.
+ .graphicsLayer()
+ .overscroll(controller)
+ .drawBehind {
+ val extraOffset = 10.dp
+ .roundToPx()
+ .toFloat()
+ // Draw a green box over the entire red parent container
+ drawRect(
+ Color.Green,
+ Offset(-extraOffset, -extraOffset),
+ size = Size(
+ size.width + extraOffset * 2,
+ size.height + extraOffset * 2
+ )
+ )
+ }
+ )
+ }
+ }
+ }
+
+ // Overscroll is not displayed, so the content should be entirely green (no clipping)
+ rule.onNodeWithTag(tag)
+ .captureToImage()
+ .assertPixels { Color.Green }
+
+ // Stretch vertically down
+ rule.runOnIdle {
+ val offset = Offset(x = 0f, y = 50f)
+ controller.applyToScroll(
+ offset,
+ source = NestedScrollSource.Drag
+ ) { Offset.Zero }
+ // we have to disable further invalidation requests as otherwise while the overscroll
+ // effect is considered active (as it is in a pulled state) this will infinitely
+ // schedule next invalidation right from the drawing. this will make our test infra
+ // to never be switched into idle state so this fill freeze instead of proceeding
+ // to the next step in the test.
+ controller.invalidationEnabled = false
+ }
+
+ // Overscroll should be clipped vertically (to prevent stretching transparent pixels
+ // outside the content), but not horizontally, so (roughly) the top and bottom third should
+ // be red, and the center should be green. Because the stretch effect will move this a bit,
+ // we instead roughly assert by splitting the bitmap into 9 sections and asserting each
+ // center pixel.
+ // +---+---+---+
+ // | R | R | R |
+ // +---+---+---+
+ // | G | G | G |
+ // +---+---+---+
+ // | R | R | R |
+ // +---+---+---+
+ with(rule.onNodeWithTag(tag).captureToImage().toPixelMap()) {
+ // Top left, top middle, top right should be red, as we clip vertically
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 1,
+ y = (height / 6) * 1
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 3,
+ y = (height / 6) * 1
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 5,
+ y = (height / 6) * 1
+ )
+ // Middle left, middle, middle right should be green, as we don't clip horizontally
+ assertPixelColor(
+ expected = Color.Green,
+ x = (width / 6) * 1,
+ y = (height / 6) * 3
+ )
+ assertPixelColor(
+ expected = Color.Green,
+ x = (width / 6) * 3,
+ y = (height / 6) * 3
+ )
+ assertPixelColor(
+ expected = Color.Green,
+ x = (width / 6) * 5,
+ y = (height / 6) * 3
+ )
+ // Bottom left, bottom middle, bottom right should be red, as we clip vertically
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 1,
+ y = (height / 6) * 5
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 3,
+ y = (height / 6) * 5
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 5,
+ y = (height / 6) * 5
+ )
+ }
+ }
+
+ @OptIn(ExperimentalFoundationApi::class)
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ fun stretchOverscroll_doesNotClipCrossAxis_horizontalOverscroll() {
+ lateinit var controller: AndroidEdgeEffectOverscrollEffect
+ val tag = "container"
+ rule.setContent {
+ Box {
+ controller = rememberOverscrollEffect() as AndroidEdgeEffectOverscrollEffect
+ Box(
+ Modifier
+ .fillMaxSize()
+ .wrapContentSize(Alignment.Center)
+ .background(Color.Red)
+ .testTag(tag)
+ ) {
+ Box(
+ Modifier
+ .padding(10.dp)
+ .size(10.dp)
+ // Stretch overscroll will apply the stretch to the surrounding canvas,
+ // so add a graphics layer to get a canvas sized to the content. The
+ // expected usage is for this to be clipScrollableContainer() or
+ // similar, since a container that shows overscroll should clip the
+ // main axis, but we don't use that here as we want to test the
+ // implicit clipping behavior.
+ .graphicsLayer()
+ .overscroll(controller)
+ .drawBehind {
+ val extraOffset = 10.dp
+ .roundToPx()
+ .toFloat()
+ // Draw a green box over the entire red parent container
+ drawRect(
+ Color.Green,
+ Offset(-extraOffset, -extraOffset),
+ size = Size(
+ size.width + extraOffset * 2,
+ size.height + extraOffset * 2
+ )
+ )
+ }
+ )
+ }
+ }
+ }
+
+ // Overscroll is not displayed, so the content should be entirely green (no clipping)
+ rule.onNodeWithTag(tag)
+ .captureToImage()
+ .assertPixels { Color.Green }
+
+ // Stretch horizontally right
+ rule.runOnIdle {
+ val offset = Offset(x = 50f, y = 0f)
+ controller.applyToScroll(
+ offset,
+ source = NestedScrollSource.Drag
+ ) { Offset.Zero }
+ // we have to disable further invalidation requests as otherwise while the overscroll
+ // effect is considered active (as it is in a pulled state) this will infinitely
+ // schedule next invalidation right from the drawing. this will make our test infra
+ // to never be switched into idle state so this fill freeze instead of proceeding
+ // to the next step in the test.
+ controller.invalidationEnabled = false
+ }
+
+ // Overscroll should be clipped horizontally (to prevent stretching transparent pixels
+ // outside the content), but not vertically, so (roughly) the left and right third should
+ // be red, and the center should be green. Because the stretch effect will move this a bit,
+ // we instead roughly assert by splitting the bitmap into 9 sections and asserting each
+ // center pixel.
+ // +---+---+---+
+ // | R | G | R |
+ // +---+---+---+
+ // | R | G | R |
+ // +---+---+---+
+ // | R | G | R |
+ // +---+---+---+
+ with(rule.onNodeWithTag(tag).captureToImage().toPixelMap()) {
+ // Top left, top middle, top right should be red, green, red
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 1,
+ y = (height / 6) * 1
+ )
+ assertPixelColor(
+ expected = Color.Green,
+ x = (width / 6) * 3,
+ y = (height / 6) * 1
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 5,
+ y = (height / 6) * 1
+ )
+ // Middle left, middle, middle right should be red, green, red
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 1,
+ y = (height / 6) * 3
+ )
+ assertPixelColor(
+ expected = Color.Green,
+ x = (width / 6) * 3,
+ y = (height / 6) * 3
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 5,
+ y = (height / 6) * 3
+ )
+ // Bottom left, bottom middle, bottom right should be red, green, red
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 1,
+ y = (height / 6) * 5
+ )
+ assertPixelColor(
+ expected = Color.Green,
+ x = (width / 6) * 3,
+ y = (height / 6) * 5
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 5,
+ y = (height / 6) * 5
+ )
+ }
+ }
+
+ @OptIn(ExperimentalFoundationApi::class)
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ fun stretchOverscroll_clipsBothAxes_overscrollInBothDirections() {
+ lateinit var controller: AndroidEdgeEffectOverscrollEffect
+ val tag = "container"
+ rule.setContent {
+ Box {
+ controller = rememberOverscrollEffect() as AndroidEdgeEffectOverscrollEffect
+ Box(
+ Modifier
+ .fillMaxSize()
+ .wrapContentSize(Alignment.Center)
+ .background(Color.Red)
+ .testTag(tag)
+ ) {
+ Box(
+ Modifier
+ .padding(10.dp)
+ .size(10.dp)
+ // Stretch overscroll will apply the stretch to the surrounding canvas,
+ // so add a graphics layer to get a canvas sized to the content. The
+ // expected usage is for this to be clipScrollableContainer() or
+ // similar, since a container that shows overscroll should clip the
+ // main axis, but we don't use that here as we want to test the
+ // implicit clipping behavior.
+ .graphicsLayer()
+ .overscroll(controller)
+ .drawBehind {
+ val extraOffset = 10.dp
+ .roundToPx()
+ .toFloat()
+ // Draw a green box over the entire red parent container
+ drawRect(
+ Color.Green,
+ Offset(-extraOffset, -extraOffset),
+ size = Size(
+ size.width + extraOffset * 2,
+ size.height + extraOffset * 2
+ )
+ )
+ }
+ )
+ }
+ }
+ }
+
+ // Overscroll is not displayed, so the content should be entirely green (no clipping)
+ rule.onNodeWithTag(tag)
+ .captureToImage()
+ .assertPixels { Color.Green }
+
+ // Stretch horizontally and vertically to the bottom right
+ rule.runOnIdle {
+ val offset = Offset(x = 50f, y = 50f)
+ controller.applyToScroll(
+ offset,
+ source = NestedScrollSource.Drag
+ ) { Offset.Zero }
+ // we have to disable further invalidation requests as otherwise while the overscroll
+ // effect is considered active (as it is in a pulled state) this will infinitely
+ // schedule next invalidation right from the drawing. this will make our test infra
+ // to never be switched into idle state so this fill freeze instead of proceeding
+ // to the next step in the test.
+ controller.invalidationEnabled = false
+ }
+
+ // Overscroll should be clipped horizontally and vertically to prevent stretching
+ // transparent pixels outside the content, so only the center area should be green.
+ // Because the stretch effect will move this a bit, we instead roughly assert by
+ // splitting the bitmap into 9 sections and asserting each center pixel.
+ // +---+---+---+
+ // | R | R | R |
+ // +---+---+---+
+ // | R | G | R |
+ // +---+---+---+
+ // | R | R | R |
+ // +---+---+---+
+ with(rule.onNodeWithTag(tag).captureToImage().toPixelMap()) {
+ // Top left, top middle, top right should be red
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 1,
+ y = (height / 6) * 1
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 3,
+ y = (height / 6) * 1
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 5,
+ y = (height / 6) * 1
+ )
+ // Middle left, middle, middle right should be red, green, red
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 1,
+ y = (height / 6) * 3
+ )
+ assertPixelColor(
+ expected = Color.Green,
+ x = (width / 6) * 3,
+ y = (height / 6) * 3
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 5,
+ y = (height / 6) * 3
+ )
+ // Bottom left, bottom middle, bottom right should be red
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 1,
+ y = (height / 6) * 5
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 3,
+ y = (height / 6) * 5
+ )
+ assertPixelColor(
+ expected = Color.Red,
+ x = (width / 6) * 5,
+ y = (height / 6) * 5
+ )
+ }
+ }
+
+ /**
+ * Similar to the tests above, but instead of asserting overall clipping behavior in all axes,
+ * this is a specific regression test for b/313463733 to make sure that the stretch isn't
+ * 'starting' from out of bound pixels, as this will either cause these pixels to become visible
+ * when stretching down, or if there are no pixels (transparent) there, this will cause any
+ * background underneath the content to become visible.
+ */
+ @OptIn(ExperimentalFoundationApi::class)
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ fun stretchOverscroll_doesNotIncludeUnclippedPixels_verticalOverscroll() {
+ lateinit var controller: AndroidEdgeEffectOverscrollEffect
+ val tag = "container"
+ rule.setContent {
+ Box {
+ controller = rememberOverscrollEffect() as AndroidEdgeEffectOverscrollEffect
+ Box(
+ Modifier
+ .fillMaxSize()
+ .wrapContentSize(Alignment.Center)
+ .background(Color.Red)
+ .testTag(tag)
+ ) {
+ Box(
+ Modifier
+ .size(10.dp)
+ // Stretch overscroll will apply the stretch to the surrounding canvas,
+ // so add a graphics layer to get a canvas sized to the content. The
+ // expected usage is for this to be clipScrollableContainer() or
+ // similar, since a container that shows overscroll should clip the
+ // main axis, but we don't use that here as we want to test the
+ // implicit clipping behavior.
+ .graphicsLayer()
+ .overscroll(controller)
+ // This green background will be drawn fully occluding the red
+ // background of the parent box with the same size
+ .background(Color.Green)
+ )
+ }
+ }
+ }
+
+ // Overscroll is not displayed, so the content should be entirely green
+ rule.onNodeWithTag(tag)
+ .captureToImage()
+ .assertPixels { Color.Green }
+
+ // Stretch vertically down
+ rule.runOnIdle {
+ val offset = Offset(x = 0f, y = 50f)
+ controller.applyToScroll(
+ offset,
+ source = NestedScrollSource.Drag
+ ) { Offset.Zero }
+ // we have to disable further invalidation requests as otherwise while the overscroll
+ // effect is considered active (as it is in a pulled state) this will infinitely
+ // schedule next invalidation right from the drawing. this will make our test infra
+ // to never be switched into idle state so this fill freeze instead of proceeding
+ // to the next step in the test.
+ controller.invalidationEnabled = false
+ }
+
+ // We don't want to assert that the content is entirely green as the stretch effect will
+ // change this a bit, so instead we assert that no red from the parent box is visible.
+ rule.onNodeWithTag(tag)
+ .captureToImage()
+ .assertHasNoColor(Color.Red)
+ }
+
+ @OptIn(ExperimentalFoundationApi::class)
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ fun stretchOverscroll_doesNotIncludeUnclippedPixels_horizontalOverscroll() {
+ lateinit var controller: AndroidEdgeEffectOverscrollEffect
+ val tag = "container"
+ rule.setContent {
+ Box {
+ controller = rememberOverscrollEffect() as AndroidEdgeEffectOverscrollEffect
+ Box(
+ Modifier
+ .fillMaxSize()
+ .wrapContentSize(Alignment.Center)
+ .background(Color.Red)
+ .testTag(tag)
+ ) {
+ Box(
+ Modifier
+ .size(10.dp)
+ // Stretch overscroll will apply the stretch to the surrounding canvas,
+ // so add a graphics layer to get a canvas sized to the content. The
+ // expected usage is for this to be clipScrollableContainer() or
+ // similar, since a container that shows overscroll should clip the
+ // main axis, but we don't use that here as we want to test the
+ // implicit clipping behavior.
+ .graphicsLayer()
+ .overscroll(controller)
+ // This green background will be drawn fully occluding the red
+ // background of the parent box with the same size
+ .background(Color.Green)
+ )
+ }
+ }
+ }
+
+ // Overscroll is not displayed, so the content should be entirely green
+ rule.onNodeWithTag(tag)
+ .captureToImage()
+ .assertPixels { Color.Green }
+
+ // Stretch horizontally right
+ rule.runOnIdle {
+ val offset = Offset(x = 50f, y = 0f)
+ controller.applyToScroll(
+ offset,
+ source = NestedScrollSource.Drag
+ ) { Offset.Zero }
+ // we have to disable further invalidation requests as otherwise while the overscroll
+ // effect is considered active (as it is in a pulled state) this will infinitely
+ // schedule next invalidation right from the drawing. this will make our test infra
+ // to never be switched into idle state so this fill freeze instead of proceeding
+ // to the next step in the test.
+ controller.invalidationEnabled = false
+ }
+
+ // We don't want to assert that the content is entirely green as the stretch effect will
+ // change this a bit, so instead we assert that no red from the parent box is visible.
+ rule.onNodeWithTag(tag)
+ .captureToImage()
+ .assertHasNoColor(Color.Red)
+ }
+
+ @OptIn(ExperimentalFoundationApi::class)
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ fun stretchOverscroll_doesNotIncludeUnclippedPixels_overscrollInBothDirections() {
+ lateinit var controller: AndroidEdgeEffectOverscrollEffect
+ val tag = "container"
+ rule.setContent {
+ Box {
+ controller = rememberOverscrollEffect() as AndroidEdgeEffectOverscrollEffect
+ Box(
+ Modifier
+ .fillMaxSize()
+ .wrapContentSize(Alignment.Center)
+ .background(Color.Red)
+ .testTag(tag)
+ ) {
+ Box(
+ Modifier
+ .size(10.dp)
+ // Stretch overscroll will apply the stretch to the surrounding canvas,
+ // so add a graphics layer to get a canvas sized to the content. The
+ // expected usage is for this to be clipScrollableContainer() or
+ // similar, since a container that shows overscroll should clip the
+ // main axis, but we don't use that here as we want to test the
+ // implicit clipping behavior.
+ .graphicsLayer()
+ .overscroll(controller)
+ // This green background will be drawn fully occluding the red
+ // background of the parent box with the same size
+ .background(Color.Green)
+ )
+ }
+ }
+ }
+
+ // Overscroll is not displayed, so the content should be entirely green
+ rule.onNodeWithTag(tag)
+ .captureToImage()
+ .assertPixels { Color.Green }
+
+ // Stretch horizontally and vertically to the bottom right
+ rule.runOnIdle {
+ val offset = Offset(x = 50f, y = 50f)
+ controller.applyToScroll(
+ offset,
+ source = NestedScrollSource.Drag
+ ) { Offset.Zero }
+ // we have to disable further invalidation requests as otherwise while the overscroll
+ // effect is considered active (as it is in a pulled state) this will infinitely
+ // schedule next invalidation right from the drawing. this will make our test infra
+ // to never be switched into idle state so this fill freeze instead of proceeding
+ // to the next step in the test.
+ controller.invalidationEnabled = false
+ }
+
+ // We don't want to assert that the content is entirely green as the stretch effect will
+ // change this a bit, so instead we assert that no red from the parent box is visible.
+ rule.onNodeWithTag(tag)
+ .captureToImage()
.assertHasNoColor(Color.Red)
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
index cc9b072..1ee4284 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
@@ -326,7 +326,7 @@
}
@Test
- fun prefetchAndCancelItemWithCustomExecutor() {
+ fun prefetchItemWithCustomExecutor() {
val itemProvider = itemProvider({ 1 }) { index ->
Box(
Modifier
@@ -342,21 +342,14 @@
}
}
- val handle = rule.runOnIdle {
+ rule.runOnIdle {
prefetchState.schedulePrefetch(0, Constraints.fixed(50, 50))
}
assertThat(executor.requests).hasSize(1)
- assertThat(executor.requests[0].isValid).isTrue()
// Default PrefetchExecutor behavior should be overridden
rule.onNodeWithTag("0").assertDoesNotExist()
-
- rule.runOnIdle {
- handle.cancel()
- }
-
- assertThat(executor.requests[0].isValid).isFalse()
}
@Test
@@ -583,11 +576,11 @@
private class RecordingPrefetchExecutor : PrefetchExecutor {
- private val _requests: MutableList<PrefetchExecutor.Request> = mutableListOf()
- val requests: List<PrefetchExecutor.Request> = _requests
+ private val _requests: MutableList<PrefetchRequest> = mutableListOf()
+ val requests: List<PrefetchRequest> = _requests
- override fun requestPrefetch(request: PrefetchExecutor.Request) {
- _requests.add(request)
+ override fun requestPrefetch(prefetchRequest: PrefetchRequest) {
+ _requests.add(prefetchRequest)
}
}
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
index e629ece..94c9612 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
@@ -70,6 +70,7 @@
var pagerSize: Int = 0
var placed = mutableSetOf<Int>()
var focused = mutableSetOf<Int>()
+ var focusRequesters = mutableMapOf<Int, FocusRequester>()
var pageSize: Int = 0
lateinit var focusManager: FocusManager
lateinit var initialFocusedItem: FocusRequester
@@ -180,6 +181,7 @@
internal fun Page(index: Int, initialFocusedItemIndex: Int = 0) {
val focusRequester = FocusRequester().also {
if (index == initialFocusedItemIndex) initialFocusedItem = it
+ focusRequesters[index] = it
}
Box(modifier = Modifier
.focusRequester(focusRequester)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
index d4314e9..80950e8 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
@@ -17,11 +17,15 @@
package androidx.compose.foundation.pager
import android.view.accessibility.AccessibilityNodeProvider
-import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsMatcher
@@ -37,7 +41,6 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-@OptIn(ExperimentalFoundationApi::class)
@RunWith(Parameterized::class)
class PagerAccessibilityTest(config: ParamConfig) : BasePagerTest(config = config) {
@@ -133,7 +136,7 @@
}
@Test
- fun focusScroll_forwardAndBackward_shouldGoToPage_pageShouldBeCorrectlyPlaced() {
+ fun focusScroll_forwardAndBackward_pageIsFocusable_fullPage_shouldScrollFullPage() {
// Arrange
createPager(pageCount = { DefaultPageCount })
rule.runOnUiThread { initialFocusedItem.requestFocus() }
@@ -159,6 +162,128 @@
}
@Test
+ fun focusScroll_forwardAndBackward_pageIsFocusable_fixedSizedPage_shouldScrollFullPage() {
+ // Arrange
+ createPager(
+ modifier = Modifier.size(210.dp), // make sure one page is halfway shown
+ pageCount = { DefaultPageCount },
+ pageSize = { PageSize.Fixed(50.dp) })
+ val lastVisibleItem = pagerState.layoutInfo.visiblePagesInfo.last().index
+ rule.runOnUiThread { focusRequesters[lastVisibleItem - 1]?.requestFocus() }
+ rule.waitForIdle()
+
+ // Act: move forward
+ rule.runOnUiThread { focusManager.moveFocus(FocusDirection.Next) }
+
+ // Assert
+ rule.runOnIdle {
+ assertThat(pagerState.currentPage).isEqualTo(1) // current page moved by 1
+ assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
+ }
+
+ // move focus back to the first visible item
+ rule.runOnUiThread { focusRequesters[pagerState.firstVisiblePage]?.requestFocus() }
+ rule.waitForIdle()
+
+ // Act: move backward
+ rule.runOnUiThread { focusManager.moveFocus(FocusDirection.Previous) }
+
+ // Assert
+ rule.runOnIdle {
+ assertThat(pagerState.currentPage).isEqualTo(0)
+ assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
+ }
+ }
+
+ @Test
+ fun focusScroll_forwardAndBackward_pageContentIsFocusable_fullPage_shouldScrollFullPage() {
+ // Arrange
+ createPager(pageCount = { DefaultPageCount }, pageContent = { page ->
+ val focusRequester = FocusRequester().also {
+ if (page == 0) initialFocusedItem = it
+ }
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Box(
+ modifier = Modifier
+ .size(30.dp)
+ .focusRequester(focusRequester)
+ .focusable()
+ )
+ }
+ })
+ rule.runOnUiThread { initialFocusedItem.requestFocus() }
+ rule.waitForIdle()
+
+ // Act: move forward
+ rule.runOnUiThread { focusManager.moveFocus(FocusDirection.Next) }
+
+ // Assert
+ rule.runOnIdle {
+ assertThat(pagerState.currentPage).isEqualTo(1)
+ assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
+ }
+
+ // Act: move backward
+ rule.runOnUiThread { focusManager.moveFocus(FocusDirection.Previous) }
+
+ // Assert
+ rule.runOnIdle {
+ assertThat(pagerState.currentPage).isEqualTo(0)
+ assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
+ }
+ }
+
+ @Test
+ fun focusScroll_forwardAndBackward_pageContentIsFocusable_fixedSizePage_shouldScrollFullPage() {
+ // Arrange
+ createPager(
+ modifier = Modifier.size(210.dp), // make sure one page is halfway shown
+ pageCount = { DefaultPageCount },
+ pageSize = { PageSize.Fixed(50.dp) },
+ pageContent = { page ->
+ val focusRequester = FocusRequester().also {
+ focusRequesters[page] = it
+ }
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ // focus bounds is smaller than page itself
+ Box(
+ modifier = Modifier
+ .size(30.dp)
+ .focusRequester(focusRequester)
+ .focusable()
+ )
+ }
+ })
+ val lastVisibleItem = pagerState.layoutInfo.visiblePagesInfo.last().index
+ rule.runOnUiThread { focusRequesters[lastVisibleItem - 1]?.requestFocus() }
+
+ // Act: move forward
+ val resultForward = rule.runOnUiThread { focusManager.moveFocus(FocusDirection.Next) }
+ assertThat(resultForward).isTrue() // focus moved
+
+ // Assert
+ rule.runOnIdle {
+ assertThat(pagerState.currentPage).isEqualTo(1) // current page moved by 1
+ assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
+ }
+
+ rule.runOnUiThread { focusManager.clearFocus(true) } // reset focus
+
+ // move focus back to the first visible item
+ rule.runOnUiThread { focusRequesters[pagerState.firstVisiblePage]?.requestFocus() }
+
+ // Act: move backward
+ val resultBackward = rule.runOnUiThread { focusManager.moveFocus(FocusDirection.Previous) }
+ assertThat(resultBackward).isTrue() // focus moved
+
+ // Assert
+ rule.runOnIdle {
+ assertThat(pagerState.currentPage).isEqualTo(0)
+ assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
+ }
+ }
+
+ @Test
fun userScrollEnabledIsOff_fillPages_focusScroll_shouldNotMovePages() {
// Arrange
val initialPage = 5
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt
index 74dde03..882810d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt
@@ -17,7 +17,6 @@
package androidx.compose.foundation.pager
import androidx.compose.foundation.AutoTestFrameClock
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
@@ -29,156 +28,184 @@
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
-import org.junit.Before
import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-@OptIn(ExperimentalFoundationApi::class)
@LargeTest
-@RunWith(Parameterized::class)
-class PagerOffscreenPageLimitPlacingTest(
- val config: ParamConfig
-) : BasePagerTest(config) {
+class PagerOffscreenPageLimitPlacingTest : SingleParamBasePagerTest() {
- @Before
- fun setUp() {
- rule.mainClock.autoAdvance = false
+ private suspend fun resetTestCase(initialPage: Int = 0) {
+ withContext(Dispatchers.Main + AutoTestFrameClock()) {
+ pagerState.scrollToPage(initialPage)
+ }
+ placed.clear()
}
@Test
- fun offscreenPageLimitIsUsed_shouldPlaceMoreItemsThanVisibleOnesAsWeScroll() {
+ fun offscreenPageLimitIsUsed_shouldPlaceMoreItemsThanVisibleOnesAsWeScroll() = with(rule) {
// Arrange
- createPager(
- pageCount = { DefaultPageCount },
- modifier = Modifier.fillMaxSize(),
- outOfBoundsPageCount = 1
- )
- val delta = pagerSize * 1.4f * scrollForwardSign
-
- repeat(DefaultAnimationRepetition) {
- // Act
- runAndWaitForPageSettling {
- onPager().performTouchInput {
- swipeWithVelocityAcrossMainAxis(0f, delta)
- }
- }
-
- // Next page was placed
- rule.runOnIdle {
- Truth.assertThat(placed).contains(
- (pagerState.currentPage + 1)
- .coerceAtMost(DefaultPageCount - 1)
- )
- }
+ setContent {
+ ParameterizedPager(
+ pageCount = { DefaultPageCount },
+ modifier = Modifier.fillMaxSize(),
+ outOfBoundsPageCount = 1,
+ orientation = it.orientation,
+ pageSpacing = it.pageSpacing,
+ contentPadding = it.mainAxisContentPadding
+ )
}
- confirmPageIsInCorrectPosition(pagerState.currentPage)
+ forEachParameter(ParamsToTest) { param ->
+ runBlocking {
+ val delta = pagerSize * 1.4f * param.scrollForwardSign
+
+ repeat(DefaultAnimationRepetition) {
+ // Act
+ onPager().performTouchInput {
+ with(param) {
+ swipeWithVelocityAcrossMainAxis(0f, delta)
+ }
+ }
+
+ // Next page was placed
+ rule.runOnIdle {
+ Truth.assertThat(placed).contains(
+ (pagerState.currentPage + 1)
+ .coerceAtMost(DefaultPageCount - 1)
+ )
+ }
+ }
+
+ param.confirmPageIsInCorrectPosition(pagerState.currentPage)
+ resetTestCase()
+ }
+ }
}
@Test
- fun offscreenPageLimitIsUsed_shouldPlaceMoreItemsThanVisibleOnes() {
+ fun offscreenPageLimitIsUsed_shouldPlaceMoreItemsThanVisibleOnes() = with(rule) {
// Arrange
val initialIndex = 5
// Act
- createPager(
- initialPage = initialIndex,
- pageCount = { DefaultPageCount },
- modifier = Modifier.fillMaxSize(),
- outOfBoundsPageCount = 2
- )
- val firstVisible = pagerState.layoutInfo.visiblePagesInfo.first().index
- val lastVisible = pagerState.layoutInfo.visiblePagesInfo.last().index
- // Assert
- rule.runOnIdle {
- Truth.assertThat(placed).contains(firstVisible - 2)
- Truth.assertThat(placed).contains(firstVisible - 1)
- Truth.assertThat(placed).contains(lastVisible + 1)
- Truth.assertThat(placed).contains(lastVisible + 2)
+ setContent {
+ ParameterizedPager(
+ initialPage = initialIndex,
+ pageCount = { DefaultPageCount },
+ modifier = Modifier.fillMaxSize(),
+ outOfBoundsPageCount = 2,
+ orientation = it.orientation,
+ pageSpacing = it.pageSpacing,
+ contentPadding = it.mainAxisContentPadding
+ )
}
- confirmPageIsInCorrectPosition(initialIndex, firstVisible - 2)
- confirmPageIsInCorrectPosition(initialIndex, firstVisible - 1)
- confirmPageIsInCorrectPosition(initialIndex, lastVisible + 1)
- confirmPageIsInCorrectPosition(initialIndex, lastVisible + 2)
+
+ forEachParameter(ParamsToTest) { param ->
+ val firstVisible = pagerState.layoutInfo.visiblePagesInfo.first().index
+ val lastVisible = pagerState.layoutInfo.visiblePagesInfo.last().index
+ // Assert
+ runOnIdle {
+ Truth.assertThat(placed).contains(firstVisible - 2)
+ Truth.assertThat(placed).contains(firstVisible - 1)
+ Truth.assertThat(placed).contains(lastVisible + 1)
+ Truth.assertThat(placed).contains(lastVisible + 2)
+ }
+ param.confirmPageIsInCorrectPosition(initialIndex, firstVisible - 2)
+ param.confirmPageIsInCorrectPosition(initialIndex, firstVisible - 1)
+ param.confirmPageIsInCorrectPosition(initialIndex, lastVisible + 1)
+ param.confirmPageIsInCorrectPosition(initialIndex, lastVisible + 2)
+ runBlocking { resetTestCase(5) }
+ }
}
@Test
- fun offscreenPageLimitIsNotUsed_shouldNotPlaceMoreItemsThanVisibleOnes() {
+ fun offscreenPageLimitIsNotUsed_shouldNotPlaceMoreItemsThanVisibleOnes() = with(rule) {
// Arrange
-
// Act
- createPager(
- initialPage = 5,
- pageCount = { DefaultPageCount },
- modifier = Modifier.fillMaxSize(),
- outOfBoundsPageCount = 0
- )
+ setContent {
+ ParameterizedPager(
+ initialPage = 5,
+ pageCount = { DefaultPageCount },
+ modifier = Modifier.fillMaxSize(),
+ outOfBoundsPageCount = 0,
+ orientation = it.orientation,
+ pageSpacing = it.pageSpacing,
+ contentPadding = it.mainAxisContentPadding
+ )
+ }
- // Assert
- val firstVisible = pagerState.layoutInfo.visiblePagesInfo.first().index
- val lastVisible = pagerState.layoutInfo.visiblePagesInfo.last().index
- Truth.assertThat(placed).doesNotContain(firstVisible - 1)
- Truth.assertThat(placed).contains(5)
- Truth.assertThat(placed).doesNotContain(lastVisible + 1)
- confirmPageIsInCorrectPosition(5)
+ forEachParameter(ParamsToTest) { param ->
+ // Assert
+ val firstVisible = pagerState.layoutInfo.visiblePagesInfo.first().index
+ val lastVisible = pagerState.layoutInfo.visiblePagesInfo.last().index
+ Truth.assertThat(placed).doesNotContain(firstVisible - 1)
+ Truth.assertThat(placed).contains(5)
+ Truth.assertThat(placed).doesNotContain(lastVisible + 1)
+ param.confirmPageIsInCorrectPosition(5)
+ runBlocking { resetTestCase(5) }
+ }
}
@Test
- fun offsetPageLimitIsUsed_visiblePagesDidNotChange_shouldNotRemeasure() {
+ fun offsetPageLimitIsUsed_visiblePagesDidNotChange_shouldNotRemeasure() = with(rule) {
val pageSizePx = 100
val pageSizeDp = with(rule.density) { pageSizePx.toDp() }
val delta = (pageSizePx / 3f).roundToInt()
val initialIndex = 0
- createPager(
- initialPage = initialIndex,
- pageCount = { DefaultPageCount },
- modifier = Modifier.size(pageSizeDp * 1.5f),
- pageSize = { PageSize.Fixed(pageSizeDp) },
- outOfBoundsPageCount = 2
- )
-
- val lastVisible = pagerState.layoutInfo.visiblePagesInfo.last().index
- // Assert
- rule.runOnIdle {
- Truth.assertThat(placed).contains(lastVisible + 1)
- Truth.assertThat(placed).contains(lastVisible + 2)
+ setContent {
+ ParameterizedPager(
+ initialPage = initialIndex,
+ pageCount = { DefaultPageCount },
+ modifier = Modifier.size(pageSizeDp * 1.5f),
+ pageSize = PageSize.Fixed(pageSizeDp),
+ outOfBoundsPageCount = 2,
+ orientation = it.orientation,
+ pageSpacing = it.pageSpacing,
+ contentPadding = it.mainAxisContentPadding
+ )
}
- val previousNumberOfRemeasurementPasses = pagerState.layoutWithMeasurement
- runBlocking {
- withContext(Dispatchers.Main + AutoTestFrameClock()) {
- // small enough scroll to not cause any new items to be composed or
- // old ones disposed.
- pagerState.scrollBy(delta.toFloat())
- }
+
+ forEachParameter(ParamsToTest) { param ->
+ val lastVisible = pagerState.layoutInfo.visiblePagesInfo.last().index
+ // Assert
rule.runOnIdle {
- Truth.assertThat(pagerState.firstVisiblePageOffset).isEqualTo(delta)
- Truth.assertThat(pagerState.layoutWithMeasurement)
- .isEqualTo(previousNumberOfRemeasurementPasses)
+ Truth.assertThat(placed).contains(lastVisible + 1)
+ Truth.assertThat(placed).contains(lastVisible + 2)
}
- confirmPageIsInCorrectPosition(
- pagerState.currentPage,
- lastVisible + 1,
- pagerState.currentPageOffsetFraction
- )
- confirmPageIsInCorrectPosition(
- pagerState.currentPage,
- lastVisible + 2,
- pagerState.currentPageOffsetFraction
- )
+ val previousNumberOfRemeasurementPasses = pagerState.layoutWithMeasurement
+ runBlocking {
+ withContext(Dispatchers.Main + AutoTestFrameClock()) {
+ // small enough scroll to not cause any new items to be composed or
+ // old ones disposed.
+ pagerState.scrollBy(delta.toFloat())
+ }
+ rule.runOnIdle {
+ Truth.assertThat(pagerState.firstVisiblePageOffset).isEqualTo(delta)
+ Truth.assertThat(pagerState.layoutWithMeasurement)
+ .isEqualTo(previousNumberOfRemeasurementPasses)
+ }
+ param.confirmPageIsInCorrectPosition(
+ pagerState.currentPage,
+ lastVisible + 1,
+ pagerState.currentPageOffsetFraction
+ )
+ param.confirmPageIsInCorrectPosition(
+ pagerState.currentPage,
+ lastVisible + 2,
+ pagerState.currentPageOffsetFraction
+ )
+ resetTestCase(initialIndex)
+ }
}
}
companion object {
- @JvmStatic
- @Parameterized.Parameters(name = "{0}")
- fun params() = mutableListOf<ParamConfig>().apply {
+ val ParamsToTest = mutableListOf<SingleParamConfig>().apply {
for (orientation in TestOrientation) {
for (pageSpacing in TestPageSpacing) {
for (contentPadding in testContentPaddings(orientation)) {
add(
- ParamConfig(
+ SingleParamConfig(
orientation = orientation,
pageSpacing = pageSpacing,
mainAxisContentPadding = contentPadding
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerSnapPositionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerSnapPositionTest.kt
index 91fae3c..a5fbf1c 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerSnapPositionTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerSnapPositionTest.kt
@@ -17,516 +17,23 @@
package androidx.compose.foundation.pager
import androidx.compose.foundation.AutoTestFrameClock
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.snapping.MinFlingVelocityDp
-import androidx.compose.foundation.gestures.snapping.SnapPosition
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.unit.Density
import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertTrue
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-@OptIn(ExperimentalFoundationApi::class)
@LargeTest
-@RunWith(Parameterized::class)
-class PagerSnapPositionTest(val config: ParamConfig) : BasePagerTest(config) {
+class PagerSnapPositionTest : SingleParamBasePagerTest() {
@Test
- fun scrollToPage_shouldPlacePagesCorrectly() = runBlocking {
- // Arrange
- createPager(modifier = Modifier.fillMaxSize())
-
- // Act and Assert
- repeat(DefaultAnimationRepetition) {
- assertThat(pagerState.currentPage).isEqualTo(it)
- val nextPage = pagerState.currentPage + 1
- withContext(Dispatchers.Main + AutoTestFrameClock()) {
- pagerState.scrollToPage(nextPage)
- }
- rule.mainClock.advanceTimeUntil { pagerState.currentPage == nextPage }
- confirmPageIsInCorrectPosition(pagerState.currentPage)
- }
- }
-
- @SdkSuppress(maxSdkVersion = 32) // b/269176638
- @Test
- fun scrollToPage_usedOffset_shouldPlacePagesCorrectly() = runBlocking {
- // Arrange
-
- suspend fun scrollToPageWithOffset(page: Int, offset: Float) {
- withContext(Dispatchers.Main + AutoTestFrameClock()) {
- pagerState.scrollToPage(page, offset)
- }
- }
-
- // Arrange
- createPager(modifier = Modifier.fillMaxSize())
-
- // Act
- scrollToPageWithOffset(10, 0.5f)
-
- // Assert
- confirmPageIsInCorrectPosition(pagerState.currentPage, 10, pageOffset = 0.5f)
-
- // Act
- scrollToPageWithOffset(4, 0.2f)
-
- // Assert
- confirmPageIsInCorrectPosition(pagerState.currentPage, 4, pageOffset = 0.2f)
-
- // Act
- scrollToPageWithOffset(12, -0.4f)
-
- // Assert
- confirmPageIsInCorrectPosition(pagerState.currentPage, 12, pageOffset = -0.4f)
-
- // Act
- scrollToPageWithOffset(DefaultPageCount - 1, 0.5f)
-
- // Assert
- confirmPageIsInCorrectPosition(pagerState.currentPage, DefaultPageCount - 1)
-
- // Act
- scrollToPageWithOffset(0, -0.5f)
-
- // Assert
- confirmPageIsInCorrectPosition(pagerState.currentPage, 0)
- }
-
- @Test
- fun animateScrollToPage_shouldPlacePagesCorrectly() = runBlocking {
- // Arrange
-
- createPager(modifier = Modifier.fillMaxSize())
-
- // Act and Assert
- repeat(DefaultAnimationRepetition) {
- assertThat(pagerState.currentPage).isEqualTo(it)
- val nextPage = pagerState.currentPage + 1
- withContext(Dispatchers.Main + AutoTestFrameClock()) {
- pagerState.animateScrollToPage(nextPage)
- }
- rule.waitForIdle()
- assertTrue { pagerState.currentPage == nextPage }
- confirmPageIsInCorrectPosition(pagerState.currentPage)
- }
- }
-
- @Test
- fun animateScrollToPage_usedOffset_shouldPlacePagesCorrectly() = runBlocking {
- // Arrange
-
- suspend fun animateScrollToPageWithOffset(page: Int, offset: Float) {
- withContext(Dispatchers.Main + AutoTestFrameClock()) {
- pagerState.animateScrollToPage(page, offset)
- }
- rule.waitForIdle()
- }
-
- // Arrange
- createPager(modifier = Modifier.fillMaxSize())
-
- // Act
- animateScrollToPageWithOffset(10, 0.49f)
-
- // Assert
- confirmPageIsInCorrectPosition(pagerState.currentPage, 10, pageOffset = 0.49f)
-
- // Act
- animateScrollToPageWithOffset(4, 0.2f)
-
- // Assert
- confirmPageIsInCorrectPosition(pagerState.currentPage, 4, pageOffset = 0.2f)
-
- // Act
- animateScrollToPageWithOffset(12, -0.4f)
-
- // Assert
- confirmPageIsInCorrectPosition(pagerState.currentPage, 12, pageOffset = -0.4f)
-
- // Act
- animateScrollToPageWithOffset(DefaultPageCount - 1, 0.5f)
-
- // Assert
- confirmPageIsInCorrectPosition(pagerState.currentPage, DefaultPageCount - 1)
-
- // Act
- animateScrollToPageWithOffset(0, -0.5f)
-
- // Assert
- confirmPageIsInCorrectPosition(pagerState.currentPage, 0)
- }
-
- @Test
- fun animateScrollToPage_moveToSamePageWithOffset_shouldScroll() = runBlocking {
- // Arrange
- createPager(initialPage = 5, modifier = Modifier.fillMaxSize())
-
- // Act
- assertThat(pagerState.currentPage).isEqualTo(5)
-
- withContext(Dispatchers.Main + AutoTestFrameClock()) {
- pagerState.animateScrollToPage(5, 0.4f)
- }
-
- // Assert
- rule.runOnIdle { assertThat(pagerState.currentPage).isEqualTo(5) }
- rule.runOnIdle { assertThat(pagerState.currentPageOffsetFraction).isWithin(0.01f).of(0.4f) }
- }
-
- @Test
- fun currentPage_shouldChangeWhenClosestPageToSnappedPositionChanges() {
- // Arrange
-
- createPager(modifier = Modifier.fillMaxSize())
- var previousCurrentPage = pagerState.currentPage
-
- // Act
- // Move less than half an item
- val firstDelta = (pagerSize * 0.4f) * scrollForwardSign
- onPager().performTouchInput {
- down(layoutStart)
- if (vertical) {
- moveBy(Offset(0f, firstDelta))
- } else {
- moveBy(Offset(firstDelta, 0f))
- }
- }
-
- // Assert
- rule.runOnIdle {
- assertThat(pagerState.currentPage).isEqualTo(previousCurrentPage)
- }
- // Release pointer
- onPager().performTouchInput { up() }
-
- rule.runOnIdle {
- previousCurrentPage = pagerState.currentPage
- }
- confirmPageIsInCorrectPosition(pagerState.currentPage)
-
- // Arrange
- // Pass closest to snap position threshold (over half an item)
- val secondDelta = (pagerSize * 0.6f) * scrollForwardSign
-
- // Act
- onPager().performTouchInput {
- down(layoutStart)
- if (vertical) {
- moveBy(Offset(0f, secondDelta))
- } else {
- moveBy(Offset(secondDelta, 0f))
- }
- }
-
- // Assert
- rule.runOnIdle {
- assertThat(pagerState.currentPage).isEqualTo(previousCurrentPage + 1)
- }
-
- onPager().performTouchInput { up() }
- rule.waitForIdle()
- confirmPageIsInCorrectPosition(pagerState.currentPage)
- }
-
- @Test
- fun targetPage_performScrollBelowMinThreshold_shouldNotShowNextPage() {
- // Arrange
- createPager(
- modifier = Modifier.fillMaxSize(),
- snappingPage = PagerSnapDistance.atMost(3)
- )
- rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
-
- rule.mainClock.autoAdvance = false
- // Act
- // Moving less than threshold
- val forwardDelta =
- scrollForwardSign.toFloat() * with(rule.density) { DefaultPositionThreshold.toPx() / 2 }
-
- var previousTargetPage = pagerState.targetPage
-
- onPager().performTouchInput {
- down(layoutStart)
- moveBy(Offset(forwardDelta, forwardDelta))
- }
-
- // Assert
- assertThat(pagerState.targetPage).isEqualTo(previousTargetPage)
-
- // Reset
- rule.mainClock.autoAdvance = true
- onPager().performTouchInput { up() }
- rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
-
- // Act
- // Moving more than threshold
- val backwardDelta = scrollForwardSign.toFloat() * with(rule.density) {
- -DefaultPositionThreshold.toPx() / 2
- }
-
- previousTargetPage = pagerState.targetPage
-
- onPager().performTouchInput {
- down(layoutStart)
- moveBy(Offset(backwardDelta, backwardDelta))
- }
-
- // Assert
- assertThat(pagerState.targetPage).isEqualTo(previousTargetPage)
- }
-
- @Test
- fun targetPage_performScroll_shouldShowNextPage() {
- // Arrange
- createPager(
- modifier = Modifier.fillMaxSize(),
- snappingPage = PagerSnapDistance.atMost(3)
- )
- rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
-
- rule.mainClock.autoAdvance = false
- // Act
- // Moving forward
- val forwardDelta = pagerSize * 0.4f * scrollForwardSign.toFloat()
- onPager().performTouchInput {
- down(layoutStart)
- moveBy(Offset(forwardDelta, forwardDelta))
- }
-
- // Assert
- rule.runOnIdle {
- assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage + 1)
- assertThat(pagerState.targetPage).isNotEqualTo(pagerState.currentPage)
- }
-
- // Reset
- rule.mainClock.autoAdvance = true
- onPager().performTouchInput { up() }
- rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
- rule.runOnIdle {
- scope.launch {
- pagerState.scrollToPage(5)
- }
- }
-
- rule.mainClock.autoAdvance = false
- // Act
- // Moving backward
- val backwardDelta = -pagerSize * 0.4f * scrollForwardSign.toFloat()
- onPager().performTouchInput {
- down(layoutEnd)
- moveBy(Offset(backwardDelta, backwardDelta))
- }
-
- // Assert
- rule.runOnIdle {
- assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage - 1)
- assertThat(pagerState.targetPage).isNotEqualTo(pagerState.currentPage)
- }
-
- rule.mainClock.autoAdvance = true
- onPager().performTouchInput { up() }
- rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
- }
-
- @Test
- fun targetPage_performingFling_shouldGoToPredictedPage() {
- // Arrange
-
- createPager(
- modifier = Modifier.fillMaxSize(),
- snappingPage = PagerSnapDistance.atMost(3)
- )
- rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
-
- rule.mainClock.autoAdvance = false
- // Act
- // Moving forward
- var previousTarget = pagerState.targetPage
- val forwardDelta = pagerSize * scrollForwardSign.toFloat()
- onPager().performTouchInput {
- swipeWithVelocityAcrossMainAxis(20000f, forwardDelta)
- }
- rule.mainClock.advanceTimeUntil { pagerState.targetPage != previousTarget }
- var flingOriginIndex = pagerState.firstVisiblePage
- // Assert
- assertThat(pagerState.targetPage).isEqualTo(flingOriginIndex + 3)
- assertThat(pagerState.targetPage).isNotEqualTo(pagerState.currentPage)
-
- rule.mainClock.autoAdvance = true
- rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
- rule.mainClock.autoAdvance = false
- // Act
- // Moving backward
- previousTarget = pagerState.targetPage
- val backwardDelta = -pagerSize * scrollForwardSign.toFloat()
- onPager().performTouchInput {
- swipeWithVelocityAcrossMainAxis(20000f, backwardDelta)
- }
- rule.mainClock.advanceTimeUntil { pagerState.targetPage != previousTarget }
-
- // Assert
- flingOriginIndex = pagerState.firstVisiblePage + 1
- assertThat(pagerState.targetPage).isEqualTo(flingOriginIndex - 3)
- assertThat(pagerState.targetPage).isNotEqualTo(pagerState.currentPage)
-
- rule.mainClock.autoAdvance = true
- rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
- }
-
- @Test
- fun targetPage_valueAfterScrollingAfterMidpoint() {
- createPager(initialPage = 5, modifier = Modifier.fillMaxSize())
-
- var previousCurrentPage = pagerState.currentPage
-
- val forwardDelta = (pagerSize * 0.7f) * scrollForwardSign
- onPager().performTouchInput {
- down(layoutStart)
- if (vertical) {
- moveBy(Offset(0f, forwardDelta))
- } else {
- moveBy(Offset(forwardDelta, 0f))
- }
- }
-
- rule.runOnIdle {
- assertThat(pagerState.currentPage).isNotEqualTo(previousCurrentPage)
- assertThat(pagerState.targetPage).isEqualTo(previousCurrentPage + 1)
- }
-
- onPager().performTouchInput { up() }
-
- rule.runOnIdle {
- previousCurrentPage = pagerState.currentPage
- }
-
- val backwardDelta = (pagerSize * 0.7f) * scrollForwardSign * -1
- onPager().performTouchInput {
- down(layoutEnd)
- if (vertical) {
- moveBy(Offset(0f, backwardDelta))
- } else {
- moveBy(Offset(backwardDelta, 0f))
- }
- }
-
- rule.runOnIdle {
- assertThat(pagerState.currentPage).isNotEqualTo(previousCurrentPage)
- assertThat(pagerState.targetPage).isEqualTo(previousCurrentPage - 1)
- }
-
- onPager().performTouchInput { up() }
- }
-
- @Test
- fun targetPage_valueAfterScrollingForwardAndBackward() {
- createPager(initialPage = 5, modifier = Modifier.fillMaxSize())
-
- val startCurrentPage = pagerState.currentPage
-
- val forwardDelta = (pagerSize * 0.8f) * scrollForwardSign
- onPager().performTouchInput {
- down(layoutStart)
- if (vertical) {
- moveBy(Offset(0f, forwardDelta))
- } else {
- moveBy(Offset(forwardDelta, 0f))
- }
- }
-
- rule.runOnIdle {
- assertThat(pagerState.currentPage).isNotEqualTo(startCurrentPage)
- assertThat(pagerState.targetPage).isEqualTo(startCurrentPage + 1)
- }
-
- val backwardDelta = (pagerSize * 0.2f) * scrollForwardSign * -1
- onPager().performTouchInput {
- if (vertical) {
- moveBy(Offset(0f, backwardDelta))
- } else {
- moveBy(Offset(backwardDelta, 0f))
- }
- }
-
- rule.runOnIdle {
- assertThat(pagerState.currentPage).isNotEqualTo(startCurrentPage)
- assertThat(pagerState.targetPage).isEqualTo(startCurrentPage)
- }
-
- onPager().performTouchInput { up() }
- }
-
- @Test
- fun currentPageOffset_shouldReflectScrollingOfCurrentPage() {
- // Arrange
- createPager(initialPage = DefaultPageCount / 2, modifier = Modifier.fillMaxSize())
-
- // No offset initially
- rule.runOnIdle {
- assertThat(pagerState.currentPageOffsetFraction).isWithin(0.01f).of(0f)
- }
-
- // Act
- // Moving forward
- onPager().performTouchInput {
- down(layoutStart)
- if (vertical) {
- moveBy(Offset(0f, scrollForwardSign * pagerSize / 4f))
- } else {
- moveBy(Offset(scrollForwardSign * pagerSize / 4f, 0f))
- }
- }
-
- rule.runOnIdle {
- assertThat(pagerState.currentPageOffsetFraction).isWithin(0.1f).of(0.25f)
- }
-
- onPager().performTouchInput { up() }
- rule.waitForIdle()
-
- // Reset
- rule.runOnIdle {
- scope.launch {
- pagerState.scrollToPage(DefaultPageCount / 2)
- }
- }
-
- // No offset initially (again)
- rule.runOnIdle {
- assertThat(pagerState.currentPageOffsetFraction).isWithin(0.01f).of(0f)
- }
-
- // Act
- // Moving backward
- onPager().performTouchInput {
- down(layoutStart)
- if (vertical) {
- moveBy(Offset(0f, -scrollForwardSign * pagerSize / 4f))
- } else {
- moveBy(Offset(-scrollForwardSign * pagerSize / 4f, 0f))
- }
- }
-
- rule.runOnIdle {
- assertThat(pagerState.currentPageOffsetFraction).isWithin(0.1f).of(-0.25f)
- }
- }
-
- @Test
- fun snapPosition_shouldNotInfluenceMaxScroll() {
+ fun snapPosition_shouldNotInfluenceMaxScroll() = with(rule) {
val PageSize = object : PageSize {
override fun Density.calculateMainAxisPageSize(
availableSpace: Int,
@@ -535,36 +42,43 @@
return (availableSpace + pageSpacing) / 2
}
}
- createPager(modifier = Modifier.fillMaxSize(), pageSize = { PageSize })
+ setContent {
+ ParameterizedPager(
+ modifier = Modifier.fillMaxSize(),
+ pageSize = PageSize,
+ orientation = it.orientation,
+ snapPosition = it.snapPosition.first
+ )
+ }
- // Reset
- rule.runOnIdle {
- scope.launch {
- pagerState.scrollToPage(DefaultPageCount - 2)
+ forEachParameter(ParamsToTest) { param ->
+ runBlocking { resetTestCase(DefaultPageCount - 2) }
+ val velocity = with(density) { 2 * MinFlingVelocityDp.roundToPx() }.toFloat()
+ val forwardDelta = (pageSize) * param.scrollForwardSign
+ onPager().performTouchInput {
+ with(param) {
+ swipeWithVelocityAcrossMainAxis(velocity, forwardDelta.toFloat())
+ }
+ }
+
+ runOnIdle {
+ assertThat(pagerState.canScrollForward).isFalse()
}
}
- rule.waitForIdle()
- val velocity = with(rule.density) { 2 * MinFlingVelocityDp.roundToPx() }.toFloat()
- val forwardDelta = (pageSize) * scrollForwardSign
- onPager().performTouchInput {
- swipeWithVelocityAcrossMainAxis(velocity, forwardDelta.toFloat())
- }
+ }
- rule.runOnIdle {
- assertThat(pagerState.canScrollForward).isFalse()
+ private suspend fun resetTestCase(initialPage: Int = 0) {
+ withContext(Dispatchers.Main + AutoTestFrameClock()) {
+ pagerState.scrollToPage(initialPage)
}
}
companion object {
- @JvmStatic
- @Parameterized.Parameters(name = "{0}")
- fun params() = mutableListOf<ParamConfig>().apply {
+ val ParamsToTest = mutableListOf<SingleParamConfig>().apply {
for (orientation in TestOrientation) {
for (snapPosition in TestSnapPosition) {
- // skip start since it's being tested in PagerStateTest already.
- if (snapPosition.first == SnapPosition.Start) continue
add(
- ParamConfig(
+ SingleParamConfig(
orientation = orientation,
snapPosition = snapPosition
)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateTest.kt
index 40a42b2..0b35a76 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateTest.kt
@@ -58,7 +58,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -93,7 +94,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -148,7 +150,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -183,7 +186,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -215,7 +219,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -273,7 +278,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -308,7 +314,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -345,7 +352,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -383,7 +391,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -414,7 +423,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -450,7 +460,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
rule.forEachParameter(PagerStateTestParams) { param ->
@@ -516,7 +527,8 @@
orientation = config.orientation,
layoutDirection = config.layoutDirection,
reverseLayout = config.reverseLayout,
- snappingPage = snapDistance
+ snappingPage = snapDistance,
+ snapPosition = config.snapPosition.first
)
}
@@ -575,7 +587,8 @@
orientation = config.orientation,
layoutDirection = config.layoutDirection,
reverseLayout = config.reverseLayout,
- snappingPage = snapDistance
+ snappingPage = snapDistance,
+ snapPosition = config.snapPosition.first
)
}
rule.forEachParameter(PagerStateTestParams) { param ->
@@ -638,7 +651,8 @@
orientation = config.orientation,
layoutDirection = config.layoutDirection,
reverseLayout = config.reverseLayout,
- snappingPage = snapDistance
+ snappingPage = snapDistance,
+ snapPosition = config.snapPosition.first
)
}
rule.forEachParameter(PagerStateTestParams) { param ->
@@ -746,7 +760,8 @@
layoutDirection = config.layoutDirection,
reverseLayout = config.reverseLayout,
pageCount = { 100 },
- flingBehavior = myCustomFling
+ flingBehavior = myCustomFling,
+ snapPosition = config.snapPosition.first
)
}
rule.forEachParameter(PagerStateTestParams) { param ->
@@ -832,7 +847,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -901,7 +917,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
rule.forEachParameter(PagerStateTestParams) {
@@ -954,7 +971,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -1010,7 +1028,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -1062,6 +1081,7 @@
orientation = config.orientation,
layoutDirection = config.layoutDirection,
reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first,
additionalContent = {
LaunchedEffect(key1 = pagerState.settledPage) {
settledPageChanges++
@@ -1074,7 +1094,6 @@
// Settle page changed once for first composition
rule.runOnIdle {
assertThat(pagerState.settledPage).isEqualTo(pagerState.currentPage)
- assertTrue { settledPageChanges == 1 }
}
settledPageChanges = 0
@@ -1115,6 +1134,7 @@
orientation = config.orientation,
layoutDirection = config.layoutDirection,
reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first,
additionalContent = {
LaunchedEffect(key1 = pagerState.settledPage) {
settledPageChanges++
@@ -1165,7 +1185,8 @@
modifier = Modifier.fillMaxSize(),
orientation = config.orientation,
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -1240,7 +1261,8 @@
}
},
layoutDirection = config.layoutDirection,
- reverseLayout = config.reverseLayout
+ reverseLayout = config.reverseLayout,
+ snapPosition = config.snapPosition.first
)
}
@@ -1267,10 +1289,22 @@
withContext(Dispatchers.Main + AutoTestFrameClock()) {
pagerState.scrollToPage(initialPage)
}
+ placed.clear()
}
companion object {
- val PagerStateTestParams = mutableListOf<SingleParamConfig>().apply {
+ val PagerStateTestParams = mutableSetOf<SingleParamConfig>().apply {
+ for (orientation in TestOrientation) {
+ for (snapPosition in TestSnapPosition) {
+ add(
+ SingleParamConfig(
+ orientation = orientation,
+ snapPosition = snapPosition
+ )
+ )
+ }
+ }
+
for (orientation in TestOrientation) {
for (reverseLayout in TestReverseLayout) {
for (layoutDirection in TestLayoutDirection) {
@@ -1284,6 +1318,6 @@
}
}
}
- }
+ }.toList()
}
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextHoverTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextHoverTest.kt
index 7b72c88..b3e5f13 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextHoverTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextHoverTest.kt
@@ -57,48 +57,72 @@
@Test
fun whenSelectableText_andDefaultIcon_inBoxWithDefaultIcon_textIconIsUsed() =
runSelectableTest(
- selectionContainerIcon = null,
+ selectionContainerIconModifier = Modifier,
expectedSelectionContainerIcon = TYPE_DEFAULT,
- textIcon = null,
+ textIconModifier = Modifier,
expectedTextIcon = TYPE_TEXT
)
@Test
- fun whenSelectableText_andSetIcon_inBoxWithDefaultIcon_setIconIsUsed() =
+ fun whenSelectableText_andSetIcon_inBoxWithDefaultIcon_textIconIsUsed() =
runSelectableTest(
- selectionContainerIcon = null,
+ selectionContainerIconModifier = Modifier,
expectedSelectionContainerIcon = TYPE_DEFAULT,
- textIcon = PointerIcon.Crosshair,
+ textIconModifier = Modifier.pointerHoverIcon(PointerIcon.Crosshair),
+ expectedTextIcon = TYPE_TEXT
+ )
+
+ @Test
+ fun whenSelectableText_andSetIcon_withOverride_inBoxWithDefaultIcon_setIconIsUsed() =
+ runSelectableTest(
+ selectionContainerIconModifier = Modifier,
+ expectedSelectionContainerIcon = TYPE_DEFAULT,
+ textIconModifier = Modifier.pointerHoverIcon(
+ icon = PointerIcon.Crosshair,
+ overrideDescendants = true
+ ),
expectedTextIcon = TYPE_CROSSHAIR
)
@Test
fun whenSelectableText_andDefaultIcon_inBoxWithSetIcon_textIconIsUsed() =
runSelectableTest(
- selectionContainerIcon = PointerIcon.Hand,
+ selectionContainerIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
expectedSelectionContainerIcon = TYPE_HAND,
- textIcon = null,
+ textIconModifier = Modifier,
expectedTextIcon = TYPE_TEXT
)
@Test
- fun whenSelectableText_andSetIcon_inBoxWithSetIcon_setIconIsUsed() =
+ fun whenSelectableText_andSetIcon_inBoxWithSetIcon_textIconIsUsed() =
runSelectableTest(
- selectionContainerIcon = PointerIcon.Hand,
+ selectionContainerIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
expectedSelectionContainerIcon = TYPE_HAND,
- textIcon = PointerIcon.Crosshair,
+ textIconModifier = Modifier.pointerHoverIcon(PointerIcon.Crosshair),
+ expectedTextIcon = TYPE_TEXT
+ )
+
+ @Test
+ fun whenSelectableText_andSetIcon_withOverride_inBoxWithSetIcon_setIconIsUsed() =
+ runSelectableTest(
+ selectionContainerIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
+ expectedSelectionContainerIcon = TYPE_HAND,
+ textIconModifier = Modifier.pointerHoverIcon(
+ icon = PointerIcon.Crosshair,
+ overrideDescendants = true
+ ),
expectedTextIcon = TYPE_CROSSHAIR
)
private fun runSelectableTest(
- selectionContainerIcon: PointerIcon?,
+ selectionContainerIconModifier: Modifier,
expectedSelectionContainerIcon: Int,
- textIcon: PointerIcon?,
+ textIconModifier: Modifier,
expectedTextIcon: Int,
) = runTest(
- selectionContainerIcon,
+ selectionContainerIconModifier,
expectedSelectionContainerIcon,
- textIcon,
+ textIconModifier,
expectedTextIcon,
) { containerTag: String, textTag: String, boxModifier: Modifier, textModifier: Modifier ->
SelectionContainer {
@@ -122,48 +146,72 @@
@Test
fun whenNonSelectableText_andDefaultIcon_inBoxWithDefaultIcon_textIconIsUsed() =
runNonSelectableTest(
- selectionContainerIcon = null,
+ selectionContainerIconModifier = Modifier,
expectedSelectionContainerIcon = TYPE_DEFAULT,
- textIcon = null,
+ textIconModifier = Modifier,
expectedTextIcon = TYPE_DEFAULT
)
@Test
fun whenNonSelectableText_andSetIcon_inBoxWithDefaultIcon_setIconIsUsed() =
runNonSelectableTest(
- selectionContainerIcon = null,
+ selectionContainerIconModifier = Modifier,
expectedSelectionContainerIcon = TYPE_DEFAULT,
- textIcon = PointerIcon.Crosshair,
+ textIconModifier = Modifier.pointerHoverIcon(PointerIcon.Crosshair),
+ expectedTextIcon = TYPE_CROSSHAIR
+ )
+
+ @Test
+ fun whenNonSelectableText_andSetIcon_withOverride_inBoxWithDefaultIcon_setIconIsUsed() =
+ runNonSelectableTest(
+ selectionContainerIconModifier = Modifier,
+ expectedSelectionContainerIcon = TYPE_DEFAULT,
+ textIconModifier = Modifier.pointerHoverIcon(
+ icon = PointerIcon.Crosshair,
+ overrideDescendants = true
+ ),
expectedTextIcon = TYPE_CROSSHAIR
)
@Test
fun whenNonSelectableText_andDefaultIcon_inBoxWithSetIcon_textIconIsUsed() =
runNonSelectableTest(
- selectionContainerIcon = PointerIcon.Hand,
+ selectionContainerIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
expectedSelectionContainerIcon = TYPE_HAND,
- textIcon = null,
+ textIconModifier = Modifier,
expectedTextIcon = TYPE_HAND
)
@Test
fun whenNonSelectableText_andSetIcon_inBoxWithSetIcon_setIconIsUsed() =
runNonSelectableTest(
- selectionContainerIcon = PointerIcon.Hand,
+ selectionContainerIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
expectedSelectionContainerIcon = TYPE_HAND,
- textIcon = PointerIcon.Crosshair,
+ textIconModifier = Modifier.pointerHoverIcon(PointerIcon.Crosshair),
+ expectedTextIcon = TYPE_CROSSHAIR
+ )
+
+ @Test
+ fun whenNonSelectableText_andSetIcon_withOverride_inBoxWithSetIcon_setIconIsUsed() =
+ runNonSelectableTest(
+ selectionContainerIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
+ expectedSelectionContainerIcon = TYPE_HAND,
+ textIconModifier = Modifier.pointerHoverIcon(
+ icon = PointerIcon.Crosshair,
+ overrideDescendants = true
+ ),
expectedTextIcon = TYPE_CROSSHAIR
)
private fun runNonSelectableTest(
- selectionContainerIcon: PointerIcon?,
+ selectionContainerIconModifier: Modifier,
expectedSelectionContainerIcon: Int,
- textIcon: PointerIcon?,
+ textIconModifier: Modifier,
expectedTextIcon: Int,
) = runTest(
- selectionContainerIcon,
+ selectionContainerIconModifier,
expectedSelectionContainerIcon,
- textIcon,
+ textIconModifier,
expectedTextIcon,
) { containerTag: String, textTag: String, boxModifier: Modifier, textModifier: Modifier ->
Box(
@@ -185,48 +233,72 @@
@Test
fun whenDisabledSelectionText_andDefaultIcon_inBoxWithDefaultIcon_textIconIsUsed() =
runDisabledSelectionText(
- selectionContainerIcon = null,
+ selectionContainerIconModifier = Modifier,
expectedSelectionContainerIcon = TYPE_DEFAULT,
- textIcon = null,
+ textIconModifier = Modifier,
expectedTextIcon = TYPE_DEFAULT
)
@Test
fun whenDisabledSelectionText_andSetIcon_inBoxWithDefaultIcon_setIconIsUsed() =
runDisabledSelectionText(
- selectionContainerIcon = null,
+ selectionContainerIconModifier = Modifier,
expectedSelectionContainerIcon = TYPE_DEFAULT,
- textIcon = PointerIcon.Crosshair,
+ textIconModifier = Modifier.pointerHoverIcon(PointerIcon.Crosshair),
+ expectedTextIcon = TYPE_CROSSHAIR
+ )
+
+ @Test
+ fun whenDisabledSelectionText_andSetIcon_withOverride_inBoxWithDefaultIcon_setIconIsUsed() =
+ runDisabledSelectionText(
+ selectionContainerIconModifier = Modifier,
+ expectedSelectionContainerIcon = TYPE_DEFAULT,
+ textIconModifier = Modifier.pointerHoverIcon(
+ icon = PointerIcon.Crosshair,
+ overrideDescendants = true
+ ),
expectedTextIcon = TYPE_CROSSHAIR
)
@Test
fun whenDisabledSelectionText_andDefaultIcon_inBoxWithSetIcon_textIconIsUsed() =
runDisabledSelectionText(
- selectionContainerIcon = PointerIcon.Hand,
+ selectionContainerIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
expectedSelectionContainerIcon = TYPE_HAND,
- textIcon = null,
+ textIconModifier = Modifier,
expectedTextIcon = TYPE_HAND
)
@Test
fun whenDisabledSelectionText_andSetIcon_inBoxWithSetIcon_setIconIsUsed() =
runDisabledSelectionText(
- selectionContainerIcon = PointerIcon.Hand,
+ selectionContainerIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
expectedSelectionContainerIcon = TYPE_HAND,
- textIcon = PointerIcon.Crosshair,
+ textIconModifier = Modifier.pointerHoverIcon(PointerIcon.Crosshair),
+ expectedTextIcon = TYPE_CROSSHAIR
+ )
+
+ @Test
+ fun whenDisabledSelectionText_andSetIcon_withOverride_inBoxWithSetIcon_setIconIsUsed() =
+ runDisabledSelectionText(
+ selectionContainerIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
+ expectedSelectionContainerIcon = TYPE_HAND,
+ textIconModifier = Modifier.pointerHoverIcon(
+ icon = PointerIcon.Crosshair,
+ overrideDescendants = true
+ ),
expectedTextIcon = TYPE_CROSSHAIR
)
private fun runDisabledSelectionText(
- selectionContainerIcon: PointerIcon?,
+ selectionContainerIconModifier: Modifier,
expectedSelectionContainerIcon: Int,
- textIcon: PointerIcon?,
+ textIconModifier: Modifier,
expectedTextIcon: Int,
) = runTest(
- selectionContainerIcon,
+ selectionContainerIconModifier,
expectedSelectionContainerIcon,
- textIcon,
+ textIconModifier,
expectedTextIcon,
) { containerTag: String, textTag: String, boxModifier: Modifier, textModifier: Modifier ->
SelectionContainer {
@@ -250,9 +322,9 @@
}
private fun runTest(
- selectionContainerIcon: PointerIcon?,
+ selectionContainerIconModifier: Modifier,
expectedSelectionContainerIcon: Int,
- textIcon: PointerIcon?,
+ textIconModifier: Modifier,
expectedTextIcon: Int,
contentBlock: @Composable (
containerTag: String,
@@ -264,15 +336,12 @@
val selectionContainerTag = "container"
val textTag = "text"
- fun testPointerHoverIcon(icon: PointerIcon?): Modifier =
- if (icon == null) Modifier else Modifier.pointerHoverIcon(icon)
-
setContent {
contentBlock(
selectionContainerTag,
textTag,
- testPointerHoverIcon(selectionContainerIcon),
- testPointerHoverIcon(textIcon),
+ selectionContainerIconModifier,
+ textIconModifier,
)
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHoverTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHoverTest.kt
index 22a4b66..ee5806e 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHoverTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHoverTest.kt
@@ -57,54 +57,73 @@
@Test
fun whenDefaultIcon_inBoxWithDefaultIcon_textIconIsUsed() = runTest(
- boxIcon = null,
+ boxIconModifier = Modifier,
expectedBoxIcon = TYPE_DEFAULT,
- textFieldIcon = null,
+ textFieldIconModifier = Modifier,
expectedTextIcon = TYPE_TEXT
)
@Test
- fun whenSetIcon_inBoxWithDefaultIcon_setIconIsUsed() = runTest(
- boxIcon = null,
+ fun whenSetIcon_inBoxWithDefaultIcon_textIconIsUsed() = runTest(
+ boxIconModifier = Modifier,
expectedBoxIcon = TYPE_DEFAULT,
- textFieldIcon = PointerIcon.Crosshair,
+ textFieldIconModifier = Modifier.pointerHoverIcon(PointerIcon.Crosshair),
+ expectedTextIcon = TYPE_TEXT
+ )
+
+ @Test
+ fun whenSetIcon_withOverride_inBoxWithDefaultIcon_setIconIsUsed() = runTest(
+ boxIconModifier = Modifier,
+ expectedBoxIcon = TYPE_DEFAULT,
+ textFieldIconModifier = Modifier.pointerHoverIcon(
+ icon = PointerIcon.Crosshair,
+ overrideDescendants = true
+ ),
expectedTextIcon = TYPE_CROSSHAIR
)
@Test
fun whenDefaultIcon_inBoxWithSetIcon_textIconIsUsed() = runTest(
- boxIcon = PointerIcon.Hand,
+ boxIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
expectedBoxIcon = TYPE_HAND,
- textFieldIcon = null,
+ textFieldIconModifier = Modifier,
expectedTextIcon = TYPE_TEXT
)
@Test
- fun whenSetIcon_inBoxWithSetIcon_setIconIsUsed() = runTest(
- boxIcon = PointerIcon.Hand,
+ fun whenSetIcon_inBoxWithSetIcon_textIconIsUsed() = runTest(
+ boxIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
expectedBoxIcon = TYPE_HAND,
- textFieldIcon = PointerIcon.Crosshair,
+ textFieldIconModifier = Modifier.pointerHoverIcon(PointerIcon.Crosshair),
+ expectedTextIcon = TYPE_TEXT
+ )
+
+ @Test
+ fun whenSetIcon_withOverride_inBoxWithSetIcon_setIconIsUsed() = runTest(
+ boxIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
+ expectedBoxIcon = TYPE_HAND,
+ textFieldIconModifier = Modifier.pointerHoverIcon(
+ icon = PointerIcon.Crosshair,
+ overrideDescendants = true
+ ),
expectedTextIcon = TYPE_CROSSHAIR
)
private fun runTest(
- boxIcon: PointerIcon?,
+ boxIconModifier: Modifier,
expectedBoxIcon: Int,
- textFieldIcon: PointerIcon?,
+ textFieldIconModifier: Modifier,
expectedTextIcon: Int,
) = with(PointerIconTestScope(rule)) {
val boxTag = "myParentIcon"
val textFieldTag = "myCoreTextField"
- fun Modifier.testPointerHoverIcon(icon: PointerIcon?): Modifier =
- if (icon == null) this else this.pointerHoverIcon(icon)
-
var value by mutableStateOf(TextFieldValue("initial text"))
setContent {
Box(
modifier = Modifier
.requiredSize(200.dp)
- .testPointerHoverIcon(boxIcon)
+ .then(boxIconModifier)
.border(BorderStroke(2.dp, SolidColor(Color.Red)))
.testTag(boxTag)
) {
@@ -113,7 +132,7 @@
onValueChange = { value = it },
modifier = Modifier
.requiredSize(50.dp)
- .testPointerHoverIcon(textFieldIcon)
+ .then(textFieldIconModifier)
.testTag(textFieldTag)
)
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt
index a9b6fe6..601de6e 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt
@@ -18,7 +18,6 @@
import android.os.Build
import androidx.compose.foundation.layout.Column
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
@@ -46,7 +45,6 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-@ExperimentalComposeUiApi
@LargeTest
@RunWith(Parameterized::class)
class DefaultKeyboardActionsTest(param: Param) {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldInteractionsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldInteractionsTest.kt
index 1970055..ba7a6ac 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldInteractionsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextFieldInteractionsTest.kt
@@ -41,7 +41,6 @@
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicSecureTextFieldTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicSecureTextFieldTest.kt
index d47bf31..1d1e730 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicSecureTextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicSecureTextFieldTest.kt
@@ -41,7 +41,6 @@
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
-import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performKeyInput
@@ -388,174 +387,6 @@
}
@Test
- fun stringValue_updatesFieldText_whenTextChangedFromCode_whileUnfocused() {
- var text by mutableStateOf("hello")
- inputMethodInterceptor.setContent {
- BasicSecureTextField(
- value = text,
- onValueChange = { text = it },
- modifier = Modifier.testTag(Tag)
- )
- }
-
- rule.runOnIdle {
- text = "world"
- }
- // Auto-advance is disabled.
- rule.mainClock.advanceTimeByFrame()
-
- assertThat(
- rule.onNodeWithTag(Tag).fetchSemanticsNode().config[SemanticsProperties.EditableText]
- .text
- ).isEqualTo("world")
- }
-
- @Test
- fun stringValue_doesNotUpdateField_whenTextChangedFromCode_whileFocused() {
- var text by mutableStateOf("hello")
- inputMethodInterceptor.setContent {
- BasicSecureTextField(
- value = text,
- onValueChange = { text = it },
- modifier = Modifier.testTag(Tag)
- )
- }
- requestFocus(Tag)
-
- rule.runOnIdle {
- text = "world"
- }
-
- rule.onNodeWithTag(Tag).assertTextEquals("hello")
- }
-
- @Test
- fun stringValue_doesNotInvokeCallback_onFocus() {
- var text by mutableStateOf("")
- var onValueChangedCount = 0
- inputMethodInterceptor.setContent {
- BasicSecureTextField(
- value = text,
- onValueChange = {
- text = it
- onValueChangedCount++
- },
- modifier = Modifier.testTag(Tag)
- )
- }
- assertThat(onValueChangedCount).isEqualTo(0)
-
- requestFocus(Tag)
-
- rule.runOnIdle {
- assertThat(onValueChangedCount).isEqualTo(0)
- }
- }
-
- @Test
- fun stringValue_doesNotInvokeCallback_whenOnlySelectionChanged() {
- var text by mutableStateOf("")
- var onValueChangedCount = 0
- inputMethodInterceptor.setContent {
- BasicSecureTextField(
- value = text,
- onValueChange = {
- text = it
- onValueChangedCount++
- },
- modifier = Modifier.testTag(Tag)
- )
- }
- requestFocus(Tag)
- assertThat(onValueChangedCount).isEqualTo(0)
-
- // Act: wiggle the cursor around a bit.
- rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
- rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(5))
-
- rule.runOnIdle {
- assertThat(onValueChangedCount).isEqualTo(0)
- }
- }
-
- @Test
- fun stringValue_doesNotInvokeCallback_whenOnlyCompositionChanged() {
- var text by mutableStateOf("")
- var onValueChangedCount = 0
- inputMethodInterceptor.setContent {
- BasicSecureTextField(
- value = text,
- onValueChange = {
- text = it
- onValueChangedCount++
- },
- modifier = Modifier.testTag(Tag)
- )
- }
- requestFocus(Tag)
- assertThat(onValueChangedCount).isEqualTo(0)
-
- // Act: wiggle the composition around a bit
- inputMethodInterceptor.withInputConnection { setComposingRegion(0, 0) }
- inputMethodInterceptor.withInputConnection { setComposingRegion(3, 5) }
-
- rule.runOnIdle {
- assertThat(onValueChangedCount).isEqualTo(0)
- }
- }
-
- @Test
- fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileUnfocused() {
- var text by mutableStateOf("")
- var onValueChangedCount = 0
- inputMethodInterceptor.setContent {
- BasicSecureTextField(
- value = text,
- onValueChange = {
- text = it
- onValueChangedCount++
- },
- modifier = Modifier.testTag(Tag)
- )
- }
- assertThat(onValueChangedCount).isEqualTo(0)
-
- rule.runOnIdle {
- text = "hello"
- }
-
- rule.runOnIdle {
- assertThat(onValueChangedCount).isEqualTo(0)
- }
- }
-
- @Test
- fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileFocused() {
- var text by mutableStateOf("")
- var onValueChangedCount = 0
- inputMethodInterceptor.setContent {
- BasicSecureTextField(
- value = text,
- onValueChange = {
- text = it
- onValueChangedCount++
- },
- modifier = Modifier.testTag(Tag)
- )
- }
- assertThat(onValueChangedCount).isEqualTo(0)
- requestFocus(Tag)
-
- rule.runOnIdle {
- text = "hello"
- }
-
- rule.runOnIdle {
- assertThat(onValueChangedCount).isEqualTo(0)
- }
- }
-
- @Test
fun inputMethod_doesNotRestart_inResponseToKeyEvents() {
val state = TextFieldState("hello", initialSelectionInChars = TextRange(5))
inputMethodInterceptor.setContent {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2DrawPhaseToggleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldDrawPhaseToggleTest.kt
similarity index 96%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2DrawPhaseToggleTest.kt
rename to compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldDrawPhaseToggleTest.kt
index 9d2205a..4a8dfe9 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2DrawPhaseToggleTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldDrawPhaseToggleTest.kt
@@ -19,7 +19,7 @@
import android.os.Build
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.TEST_FONT_FAMILY
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -45,7 +45,7 @@
import org.junit.Test
@OptIn(ExperimentalFoundationApi::class)
-class BasicTextField2DrawPhaseToggleTest {
+class BasicTextFieldDrawPhaseToggleTest {
@get:Rule
val rule = createComposeRule()
@@ -64,7 +64,7 @@
state = TextFieldState("abc")
var color by mutableStateOf(Color.Red)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle.copy(color = color),
modifier = Modifier.background(Color.White)
@@ -94,7 +94,7 @@
Brush.linearGradient(listOf(Color.Red, Color.Blue), end = Offset(20f, 20f))
)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle.copy(brush = brush),
// use brush also for background to get rid of weird antialiasing edges
@@ -125,7 +125,7 @@
state = TextFieldState("abc")
var shadow by mutableStateOf<Shadow?>(null)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle.copy(color = Color.White, shadow = shadow),
modifier = Modifier.background(Color.White)
@@ -155,7 +155,7 @@
state = TextFieldState("abc")
var textDecoration by mutableStateOf(TextDecoration.None)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle.copy(
textDecoration = textDecoration
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldImeSelectionChangesTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldImeSelectionChangesTest.kt
new file mode 100644
index 0000000..3a7e1f3
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldImeSelectionChangesTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.input
+
+import android.view.KeyEvent
+import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.ACTION_UP
+import android.view.View
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.requestFocus
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This class tests different ways of updating selection in TextFieldState and asserts that
+ * IME gets updated for all of them.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+internal class BasicTextFieldImeSelectionChangesTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ @get:Rule
+ val immRule = ComposeInputMethodManagerTestRule()
+
+ private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+ private val Tag = "BasicTextField"
+
+ private val imm = FakeInputMethodManager()
+
+ @Before
+ fun setUp() {
+ immRule.setFactory { imm }
+ }
+
+ @Test
+ fun perform_performContextMenuActionSelectAll() {
+ val state = TextFieldState("Hello")
+ inputMethodInterceptor.setTextFieldTestContent {
+ BasicTextField(state = state, modifier = Modifier.testTag(Tag))
+ }
+ rule.onNodeWithTag(Tag).requestFocus()
+
+ inputMethodInterceptor.withInputConnection {
+ performContextMenuAction(android.R.id.selectAll)
+ }
+
+ imm.expectCall("updateSelection(0, 5, -1, -1)")
+ }
+
+ @Test
+ fun perform_setSelection() {
+ val state = TextFieldState("Hello")
+ inputMethodInterceptor.setTextFieldTestContent {
+ BasicTextField(state = state, modifier = Modifier.testTag(Tag))
+ }
+ rule.onNodeWithTag(Tag).requestFocus()
+
+ inputMethodInterceptor.withInputConnection {
+ setSelection(1, 3)
+ }
+
+ imm.expectCall("updateSelection(1, 3, -1, -1)")
+ }
+
+ @Test
+ fun perform_sendKeyEvent() {
+ val state = TextFieldState("Hello")
+ lateinit var view: View
+ inputMethodInterceptor.setTextFieldTestContent {
+ view = LocalView.current
+ BasicTextField(state = state, modifier = Modifier.testTag(Tag))
+ }
+ rule.onNodeWithTag(Tag).performClick()
+
+ inputMethodInterceptor.withInputConnection {
+ // we have to use view.dispatchKeyEvent instead of InputConnection#sendKeyEvent
+ // since the test overrides inputMethodManager, effectively disabling the KeyEvent
+ // chain.
+
+ // move to start
+ view.dispatchKeyEvent(
+ KeyEvent(
+ /* downTime = */ 0,
+ /* eventTime = */ 0,
+ /* action = */ ACTION_DOWN,
+ /* code = */ KeyEvent.KEYCODE_DPAD_LEFT,
+ /* repeat = */ 0,
+ /* metaState = */ KeyEvent.META_CTRL_ON
+ )
+ )
+ view.dispatchKeyEvent(
+ KeyEvent(
+ /* downTime = */ 0,
+ /* eventTime = */ 0,
+ /* action = */ ACTION_UP,
+ /* code = */ KeyEvent.KEYCODE_DPAD_LEFT,
+ /* repeat = */ 0,
+ /* metaState = */ KeyEvent.META_CTRL_ON
+ )
+ )
+
+ // select from start to end
+ view.dispatchKeyEvent(
+ KeyEvent(
+ /* downTime = */ 0,
+ /* eventTime = */ 0,
+ /* action = */ ACTION_DOWN,
+ /* code = */ KeyEvent.KEYCODE_DPAD_RIGHT,
+ /* repeat = */ 0,
+ /* metaState = */ KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON
+ )
+ )
+ view.dispatchKeyEvent(
+ KeyEvent(
+ /* downTime = */ 0,
+ /* eventTime = */ 0,
+ /* action = */ ACTION_UP,
+ /* code = */ KeyEvent.KEYCODE_DPAD_RIGHT,
+ /* repeat = */ 0,
+ /* metaState = */ KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON
+ )
+ )
+ }
+
+ imm.expectCall("updateSelection(0, 0, -1, -1)")
+ imm.expectCall("updateSelection(0, 5, -1, -1)")
+ }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2ImmIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldImmIntegrationTest.kt
similarity index 77%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2ImmIntegrationTest.kt
rename to compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldImmIntegrationTest.kt
index 5e68e3b..e69a16e 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2ImmIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldImmIntegrationTest.kt
@@ -21,7 +21,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -43,9 +43,11 @@
import androidx.compose.ui.test.requestFocus
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
+import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -59,7 +61,7 @@
)
@SmallTest
@RunWith(AndroidJUnit4::class)
-internal class BasicTextField2ImmIntegrationTest {
+internal class BasicTextFieldImmIntegrationTest {
@get:Rule
val rule = createComposeRule()
@@ -69,7 +71,7 @@
private val inputMethodInterceptor = InputMethodInterceptor(rule)
- private val Tag = "BasicTextField2"
+ private val Tag = "BasicTextField"
private val imm = FakeInputMethodManager()
@Before
@@ -81,7 +83,7 @@
fun becomesTextEditor_whenFocusGained() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(state, Modifier.testTag(Tag))
+ BasicTextField(state, Modifier.testTag(Tag))
}
requestFocus(Tag)
@@ -98,7 +100,7 @@
lateinit var focusManager: FocusManager
inputMethodInterceptor.setTextFieldTestContent {
focusManager = LocalFocusManager.current
- BasicTextField2(state, Modifier.testTag(Tag))
+ BasicTextField(state, Modifier.testTag(Tag))
}
requestFocus(Tag)
rule.runOnIdle {
@@ -108,11 +110,29 @@
}
@Test
+ fun doesNotStopBeingTextEditor_whenWindowFocusLost() {
+ val state = TextFieldState()
+ var windowFocus by mutableStateOf(true)
+ inputMethodInterceptor.setContent {
+ CompositionLocalProvider(LocalWindowInfo provides object : WindowInfo {
+ override val isWindowFocused: Boolean get() = windowFocus
+ }) {
+ BasicTextField(state, Modifier.testTag(Tag))
+ }
+ }
+ requestFocus(Tag)
+ rule.runOnIdle {
+ windowFocus = false
+ }
+ inputMethodInterceptor.assertSessionActive()
+ }
+
+ @Test
fun stopsBeingTextEditor_whenChangedToReadOnly() {
val state = TextFieldState()
var readOnly by mutableStateOf(false)
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(state, Modifier.testTag(Tag), readOnly = readOnly)
+ BasicTextField(state, Modifier.testTag(Tag), readOnly = readOnly)
}
requestFocus(Tag)
inputMethodInterceptor.assertSessionActive()
@@ -127,7 +147,7 @@
val state = TextFieldState()
var enabled by mutableStateOf(true)
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(state, Modifier.testTag(Tag), enabled = enabled)
+ BasicTextField(state, Modifier.testTag(Tag), enabled = enabled)
}
requestFocus(Tag)
inputMethodInterceptor.assertSessionActive()
@@ -142,8 +162,8 @@
val state1 = TextFieldState()
val state2 = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(state1, Modifier.testTag(Tag + 1))
- BasicTextField2(state2, Modifier.testTag(Tag + 2))
+ BasicTextField(state1, Modifier.testTag(Tag + 1))
+ BasicTextField(state2, Modifier.testTag(Tag + 2))
}
requestFocus(Tag + 1)
@@ -163,7 +183,7 @@
var compose by mutableStateOf(true)
inputMethodInterceptor.setTextFieldTestContent {
if (compose) {
- BasicTextField2(state, Modifier.testTag(Tag))
+ BasicTextField(state, Modifier.testTag(Tag))
}
}
requestFocus(Tag)
@@ -180,7 +200,7 @@
val state2 = TextFieldState()
var state by mutableStateOf(state1)
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(state, Modifier.testTag(Tag))
+ BasicTextField(state, Modifier.testTag(Tag))
}
requestFocus(Tag)
@@ -197,7 +217,7 @@
fun immUpdated_whenFilterChangesText_fromInputConnection() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
inputTransformation = { _, new ->
@@ -229,7 +249,7 @@
fun immUpdated_whenFilterChangesText_fromKeyEvent() {
val state = TextFieldState()
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
inputTransformation = { _, new ->
@@ -255,7 +275,7 @@
fun immUpdated_whenFilterChangesSelection_fromInputConnection() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
inputTransformation = { _, new -> new.selectAll() }
@@ -277,7 +297,7 @@
fun immUpdated_whenEditChangesText() {
val state = TextFieldState()
inputMethodInterceptor.setContent {
- BasicTextField2(state, Modifier.testTag(Tag))
+ BasicTextField(state, Modifier.testTag(Tag))
}
requestFocus(Tag)
rule.runOnIdle {
@@ -299,7 +319,7 @@
fun immUpdated_whenEditChangesSelection() {
val state = TextFieldState("hello", initialSelectionInChars = TextRange(0))
inputMethodInterceptor.setContent {
- BasicTextField2(state, Modifier.testTag(Tag))
+ BasicTextField(state, Modifier.testTag(Tag))
}
requestFocus(Tag)
rule.runOnIdle {
@@ -320,7 +340,7 @@
fun immUpdated_whenEditChangesTextAndSelection() {
val state = TextFieldState()
inputMethodInterceptor.setContent {
- BasicTextField2(state, Modifier.testTag(Tag))
+ BasicTextField(state, Modifier.testTag(Tag))
}
requestFocus(Tag)
rule.runOnIdle {
@@ -340,10 +360,45 @@
}
@Test
+ fun immNotRestarted_whenEditsComeFromIME() {
+ // We are not expecting the IME to restart itself when changes are coming from the IME.
+ // Following edits are simulated as if they are coming from IME through InputConnection.
+ // We expect calls to `updateSelection` but never `restartInput`
+ val state = TextFieldState("Hello")
+ inputMethodInterceptor.setContent {
+ BasicTextField(state, Modifier.testTag(Tag))
+ }
+ requestFocus(Tag)
+ inputMethodInterceptor.withInputConnection {
+ beginBatchEdit()
+ commitText(" World", 1)
+ endBatchEdit()
+ }
+
+ rule.runOnIdle {
+ imm.expectCall("updateSelection(11, 11, -1, -1)")
+ imm.expectNoMoreCalls()
+ assertThat(state.text.toString()).isEqualTo("Hello World")
+ }
+
+ inputMethodInterceptor.withInputConnection {
+ beginBatchEdit()
+ // Remove the " World" that we added in the previous edit.
+ deleteSurroundingText(6, 0)
+ endBatchEdit()
+ }
+
+ rule.runOnIdle {
+ imm.expectCall("updateSelection(5, 5, -1, -1)")
+ imm.expectNoMoreCalls()
+ }
+ }
+
+ @Test
fun immNotRestarted_whenKeyboardIsConfiguredAsPassword() {
val state = TextFieldState()
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
@@ -366,7 +421,7 @@
fun immNotRestarted_whenKeyboardIsConfiguredAsPassword_fromTransformation() {
val state = TextFieldState()
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
inputTransformation = object : InputTransformation {
@@ -397,6 +452,24 @@
}
}
+ @SdkSuppress(minSdkVersion = 24)
+ @Test
+ fun setHintLocales() {
+ val state = TextFieldState()
+ inputMethodInterceptor.setContent {
+ BasicTextField(
+ state = state,
+ modifier = Modifier.testTag(Tag),
+ keyboardOptions = KeyboardOptions(hintLocales = LocaleList("tr"))
+ )
+ }
+ requestFocus(Tag)
+
+ inputMethodInterceptor.withEditorInfo {
+ assertThat(hintLocales?.toLanguageTags()).isEqualTo("tr")
+ }
+ }
+
private fun requestFocus(tag: String) =
rule.onNodeWithTag(tag).requestFocus()
}
@@ -412,7 +485,10 @@
CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
Row {
// Extra focusable that takes initial focus when focus is cleared.
- Box(Modifier.size(10.dp).focusable())
+ Box(
+ Modifier
+ .size(10.dp)
+ .focusable())
Box { content() }
}
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2SemanticsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldSemanticsTest.kt
similarity index 95%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2SemanticsTest.kt
rename to compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldSemanticsTest.kt
index cd6f9db..b440120 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2SemanticsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldSemanticsTest.kt
@@ -19,7 +19,7 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicText
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.Handle
import androidx.compose.foundation.text.KeyboardOptions
@@ -70,7 +70,7 @@
@OptIn(ExperimentalFoundationApi::class)
@LargeTest
@RunWith(AndroidJUnit4::class)
-class BasicTextField2SemanticsTest : FocusedWindowTest {
+class BasicTextFieldSemanticsTest : FocusedWindowTest {
@get:Rule
val rule = createComposeRule()
@@ -79,7 +79,7 @@
@Test
fun defaultSemantics() {
rule.setContent {
- BasicTextField2(
+ BasicTextField(
modifier = Modifier.testTag(Tag),
state = remember { TextFieldState() },
decorator = {
@@ -122,7 +122,7 @@
var enabled by mutableStateOf(true)
rule.setContent {
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
enabled = enabled
@@ -143,7 +143,7 @@
fun semantics_setTextAction() {
val state = TextFieldState()
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -163,7 +163,7 @@
fun semantics_performSetTextAction_whenReadOnly() {
val state = TextFieldState("", initialSelectionInChars = TextRange(1))
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
readOnly = true
@@ -180,7 +180,7 @@
fun semantics_setTextAction_appliesFilter() {
val state = TextFieldState()
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
inputTransformation = { _, changes ->
@@ -206,7 +206,7 @@
fun semantics_performTextInputAction() {
val state = TextFieldState("Hello", initialSelectionInChars = TextRange(1))
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -226,7 +226,7 @@
fun semantics_performTextInputAction_whenReadOnly() {
val state = TextFieldState("", initialSelectionInChars = TextRange(1))
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
readOnly = true
@@ -243,7 +243,7 @@
fun semantics_performTextInputAction_appliesFilter() {
val state = TextFieldState("Hello", initialSelectionInChars = TextRange(1))
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
inputTransformation = { _, changes ->
@@ -267,7 +267,7 @@
fun semantics_clickAction() {
rule.setContent {
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -284,7 +284,7 @@
fun semantics_imeOption() {
rule.setContent {
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)
@@ -298,7 +298,7 @@
fun contentSemanticsAreSet_inTheFirstComposition() {
val state = TextFieldState("hello")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -311,7 +311,7 @@
fun contentSemanticsAreSet_afterRecomposition() {
val state = TextFieldState("hello")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -328,7 +328,7 @@
fun selectionSemanticsAreSet_inTheFirstComposition() {
val state = TextFieldState("hello", initialSelectionInChars = TextRange(2))
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -344,7 +344,7 @@
fun selectionSemanticsAreSet_afterRecomposition() {
val state = TextFieldState("hello", initialSelectionInChars = TextRange.Zero)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -370,7 +370,7 @@
fun inputSelection_changesSelectionState() {
val state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -391,7 +391,7 @@
fun inputSelection_changesSelectionState_appliesFilter() {
val state = TextFieldState("hello", initialSelectionInChars = TextRange(5))
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
inputTransformation = { _, changes ->
@@ -411,7 +411,7 @@
fun textLayoutResultSemanticsAreSet_inTheFirstComposition() {
val state = TextFieldState("hello")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.testTag(Tag)
@@ -427,7 +427,7 @@
fun textLayoutResultSemanticsAreUpdated_afterRecomposition() {
val state = TextFieldState()
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.testTag(Tag)
@@ -446,7 +446,7 @@
val state2 = TextFieldState("world", initialSelectionInChars = TextRange(2))
var chosenState by mutableStateOf(true)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = if (chosenState) state1 else state2,
modifier = Modifier.testTag(Tag)
)
@@ -471,7 +471,7 @@
var enabled by mutableStateOf(false)
var readOnly by mutableStateOf(false)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
enabled = enabled,
@@ -503,7 +503,7 @@
val clipboardManager = FakeClipboardManager("Hello")
rule.setContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -526,7 +526,7 @@
val clipboardManager = FakeClipboardManager("Hello")
rule.setContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
inputTransformation = { _, changes ->
@@ -557,7 +557,7 @@
val clipboardManager = FakeClipboardManager()
rule.setContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -576,7 +576,7 @@
fun semantics_copy_disabled_whenSelectionCollapsed() {
val state = TextFieldState("Hello World!")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -592,7 +592,7 @@
val clipboardManager = FakeClipboardManager()
rule.setContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
inputTransformation = { original, changes ->
@@ -619,7 +619,7 @@
val clipboardManager = FakeClipboardManager()
rule.setContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -642,7 +642,7 @@
val clipboardManager = FakeClipboardManager()
rule.setContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
inputTransformation = { _, changes ->
@@ -667,7 +667,7 @@
var enabled by mutableStateOf(false)
var readOnly by mutableStateOf(false)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
enabled = enabled,
@@ -698,7 +698,7 @@
var enabled by mutableStateOf(true)
var readOnly by mutableStateOf(false)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
enabled = enabled,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2Test.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt
similarity index 86%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2Test.kt
rename to compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt
index 10548f9..745c93d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextField2Test.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt
@@ -30,7 +30,7 @@
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardHelper
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.TEST_FONT_FAMILY
@@ -113,7 +113,7 @@
@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
@LargeTest
@RunWith(AndroidJUnit4::class)
-internal class BasicTextField2Test {
+internal class BasicTextFieldTest {
@get:Rule
val rule = createComposeRule()
@@ -122,7 +122,7 @@
private val inputMethodInterceptor = InputMethodInterceptor(rule)
- private val Tag = "BasicTextField2"
+ private val Tag = "BasicTextField"
private val imm = FakeInputMethodManager()
@@ -131,7 +131,7 @@
var textLayoutResult: (() -> TextLayoutResult?)? = null
inputMethodInterceptor.setTextFieldTestContent {
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.fillMaxSize(),
onTextLayout = { textLayoutResult = it }
@@ -148,7 +148,7 @@
fun textFieldState_textChange_updatesState() {
val state = TextFieldState("Hello ", TextRange(Int.MAX_VALUE))
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.fillMaxSize()
@@ -167,7 +167,7 @@
fun textFieldState_textChange_updatesSemantics() {
val state = TextFieldState("Hello ", TextRange(Int.MAX_VALUE))
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.fillMaxSize()
@@ -181,26 +181,6 @@
assertTextSelection(TextRange("Hello World!".length))
}
- @Test
- fun stringValue_textChange_updatesState() {
- var state by mutableStateOf("Hello ")
- inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
- value = state,
- onValueChange = { state = it },
- modifier = Modifier
- .fillMaxSize()
- .testTag(Tag)
- )
- }
-
- rule.onNodeWithTag(Tag).performTextInput("World!")
-
- rule.runOnIdle {
- assertThat(state).isEqualTo("Hello World!")
- }
- }
-
/**
* This is a goal that we set for ourselves. Only updating the editing buffer should not cause
* BasicTextField to recompose.
@@ -211,7 +191,7 @@
var compositionCount = 0
inputMethodInterceptor.setTextFieldTestContent {
compositionCount++
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.fillMaxSize()
@@ -235,7 +215,7 @@
var textLayoutResultState: (() -> TextLayoutResult?)? by mutableStateOf(null)
val textLayoutResults = mutableListOf<TextLayoutResult?>()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.fillMaxSize()
@@ -268,7 +248,7 @@
var textLayoutResultState: (() -> TextLayoutResult?)? by mutableStateOf(null)
val textLayoutResults = mutableListOf<TextLayoutResult?>()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.fillMaxSize()
@@ -304,7 +284,7 @@
CompositionLocalProvider(LocalWindowInfo provides object : WindowInfo {
override val isWindowFocused = true
}) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.fillMaxSize()
@@ -333,7 +313,7 @@
fun textField_focus_showsSoftwareKeyboard() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.fillMaxSize()
@@ -351,7 +331,7 @@
fun textField_focus_doesNotShowSoftwareKeyboard_ifDisabled() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
enabled = false,
modifier = Modifier
@@ -370,7 +350,7 @@
fun textField_focus_doesNotShowSoftwareKeyboard_ifReadOnly() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
readOnly = true,
modifier = Modifier
@@ -390,7 +370,7 @@
val state = TextFieldState()
val focusRequester = FocusRequester()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
keyboardOptions = KeyboardOptions(shouldShowKeyboardOnFocus = false),
modifier = Modifier
@@ -413,7 +393,7 @@
val state = TextFieldState()
val focusRequester = FocusRequester()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
keyboardOptions = KeyboardOptions(shouldShowKeyboardOnFocus = false),
modifier = Modifier
@@ -441,7 +421,7 @@
keyboardHelper.initialize()
if (toggle) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag("TextField")
)
@@ -473,7 +453,7 @@
Modifier
.size(10.dp)
.focusable())
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag("TextField")
)
@@ -499,7 +479,7 @@
var toggleState by mutableStateOf(true)
val state by derivedStateOf { if (toggleState) state1 else state2 }
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
enabled = true,
modifier = Modifier
@@ -520,7 +500,7 @@
var toggleState by mutableStateOf(true)
val state by derivedStateOf { if (toggleState) state1 else state2 }
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
enabled = true,
modifier = Modifier
@@ -546,7 +526,7 @@
fun textField_passesKeyboardOptionsThrough() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
// We don't need to test all combinations here, that is tested in EditorInfoTest.
@@ -570,7 +550,7 @@
fun textField_appliesFilter_toInputConnection() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = RejectAllTextFilter,
modifier = Modifier.testTag(Tag)
@@ -586,7 +566,7 @@
fun textField_appliesFilter_toSetTextSemanticsAction() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = RejectAllTextFilter,
modifier = Modifier.testTag(Tag)
@@ -601,7 +581,7 @@
fun textField_appliesFilter_toInsertTextSemanticsAction() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = RejectAllTextFilter,
modifier = Modifier.testTag(Tag)
@@ -616,7 +596,7 @@
fun textField_appliesFilter_toKeyEvents() {
val state = TextFieldState()
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = RejectAllTextFilter,
modifier = Modifier.testTag(Tag)
@@ -632,7 +612,7 @@
val state = TextFieldState()
var filter by mutableStateOf<InputTransformation?>(null)
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = filter,
modifier = Modifier.testTag(Tag)
@@ -659,7 +639,7 @@
val state = TextFieldState()
var filter by mutableStateOf<InputTransformation?>(null)
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = filter,
modifier = Modifier.testTag(Tag)
@@ -685,7 +665,7 @@
val state = TextFieldState()
var filter by mutableStateOf<InputTransformation?>(null)
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = filter,
modifier = Modifier.testTag(Tag)
@@ -711,7 +691,7 @@
val state = TextFieldState()
var filter by mutableStateOf<InputTransformation?>(null)
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = filter,
modifier = Modifier.testTag(Tag)
@@ -737,7 +717,7 @@
val state = TextFieldState()
lateinit var changes: ChangeList
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = { _, new ->
if (new.changes.changeCount > 0) {
@@ -763,7 +743,7 @@
val state = TextFieldState()
lateinit var changes: ChangeList
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = { _, new ->
if (new.changes.changeCount > 0) {
@@ -789,7 +769,7 @@
val state = TextFieldState("hello")
lateinit var changes: ChangeList
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = { _, new ->
if (new.changes.changeCount > 0) {
@@ -821,7 +801,7 @@
val state = TextFieldState("hello")
lateinit var changes: ChangeList
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = { _, new ->
if (new.changes.changeCount > 0) {
@@ -852,7 +832,7 @@
val state = TextFieldState()
lateinit var changes: ChangeList
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = { _, new ->
if (new.changes.changeCount > 0) {
@@ -878,7 +858,7 @@
val state = TextFieldState("hello")
lateinit var changes: ChangeList
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = { _, new ->
if (new.changes.changeCount > 0) {
@@ -904,7 +884,7 @@
val state = TextFieldState()
lateinit var changes: ChangeList
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
inputTransformation = { _, new ->
if (new.changes.changeCount > 0) {
@@ -933,7 +913,7 @@
)
)
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier.testTag(Tag),
inputTransformation = filter,
@@ -951,7 +931,7 @@
fun textField_filterKeyboardOptions_mergedWithParams() {
val filter = KeyboardOptionsFilter(KeyboardOptions(imeAction = ImeAction.Previous))
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier.testTag(Tag),
inputTransformation = filter,
@@ -970,7 +950,7 @@
fun textField_filterKeyboardOptions_overriddenByParams() {
val filter = KeyboardOptionsFilter(KeyboardOptions(imeAction = ImeAction.Previous))
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier.testTag(Tag),
inputTransformation = filter,
@@ -998,7 +978,7 @@
CompositionLocalProvider(LocalWindowInfo provides object : WindowInfo {
override val isWindowFocused = true
}) {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier.testTag(Tag),
inputTransformation = filter,
@@ -1033,7 +1013,7 @@
CompositionLocalProvider(
LocalSoftwareKeyboardController provides testKeyboardController
) {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier.testTag(Tag)
)
@@ -1053,7 +1033,7 @@
@Test
fun swipingThroughTextField_doesNotGainFocus() {
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier.testTag(Tag)
)
@@ -1075,7 +1055,7 @@
.height(100.dp)
.verticalScroll(scrollState)
) {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier.testTag(Tag)
)
@@ -1095,7 +1075,7 @@
val fontSize = 20.sp
inputMethodInterceptor.setTextFieldTestContent {
CompositionLocalProvider(LocalDensity provides density) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = TextStyle(
fontFamily = TEST_FONT_FAMILY,
@@ -1116,169 +1096,6 @@
assertThat(secondSize.height).isEqualTo(firstSize.height * 2)
}
- @Test
- fun stringValue_updatesFieldText_whenTextChangedFromCode_whileUnfocused() {
- var text by mutableStateOf("hello")
- inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
- value = text,
- onValueChange = { text = it },
- modifier = Modifier.testTag(Tag)
- )
- }
-
- rule.runOnIdle {
- text = "world"
- }
-
- rule.onNodeWithTag(Tag).assertTextEquals("world")
- }
-
- @Test
- fun stringValue_doesNotUpdateField_whenTextChangedFromCode_whileFocused() {
- var text by mutableStateOf("hello")
- inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
- value = text,
- onValueChange = { text = it },
- modifier = Modifier.testTag(Tag)
- )
- }
- requestFocus(Tag)
-
- rule.runOnIdle {
- text = "world"
- }
-
- rule.onNodeWithTag(Tag).assertTextEquals("hello")
- }
-
- @Test
- fun stringValue_doesNotInvokeCallback_onFocus() {
- var text by mutableStateOf("")
- var onValueChangedCount = 0
- inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
- value = text,
- onValueChange = {
- text = it
- onValueChangedCount++
- },
- modifier = Modifier.testTag(Tag)
- )
- }
- assertThat(onValueChangedCount).isEqualTo(0)
-
- requestFocus(Tag)
-
- rule.runOnIdle {
- assertThat(onValueChangedCount).isEqualTo(0)
- }
- }
-
- @Test
- fun stringValue_doesNotInvokeCallback_whenOnlySelectionChanged() {
- var text by mutableStateOf("")
- var onValueChangedCount = 0
- inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
- value = text,
- onValueChange = {
- text = it
- onValueChangedCount++
- },
- modifier = Modifier.testTag(Tag)
- )
- }
- requestFocus(Tag)
- assertThat(onValueChangedCount).isEqualTo(0)
-
- // Act: wiggle the cursor around a bit.
- rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
- rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(5))
-
- rule.runOnIdle {
- assertThat(onValueChangedCount).isEqualTo(0)
- }
- }
-
- @Test
- fun stringValue_doesNotInvokeCallback_whenOnlyCompositionChanged() {
- var text by mutableStateOf("")
- var onValueChangedCount = 0
- inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
- value = text,
- onValueChange = {
- text = it
- onValueChangedCount++
- },
- modifier = Modifier.testTag(Tag)
- )
- }
- requestFocus(Tag)
- assertThat(onValueChangedCount).isEqualTo(0)
-
- // Act: wiggle the composition around a bit
- inputMethodInterceptor.withInputConnection { setComposingRegion(0, 0) }
- inputMethodInterceptor.withInputConnection { setComposingRegion(3, 5) }
-
- rule.runOnIdle {
- assertThat(onValueChangedCount).isEqualTo(0)
- }
- }
-
- @Test
- fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileUnfocused() {
- var text by mutableStateOf("")
- var onValueChangedCount = 0
- inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
- value = text,
- onValueChange = {
- text = it
- onValueChangedCount++
- },
- modifier = Modifier.testTag(Tag)
- )
- }
- assertThat(onValueChangedCount).isEqualTo(0)
-
- rule.runOnIdle {
- text = "hello"
- }
-
- rule.runOnIdle {
- assertThat(onValueChangedCount).isEqualTo(0)
- }
- }
-
- @Test
- fun stringValue_doesNotInvokeCallback_whenTextChangedFromCode_whileFocused() {
- var text by mutableStateOf("")
- var onValueChangedCount = 0
- inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
- value = text,
- onValueChange = {
- text = it
- onValueChangedCount++
- },
- modifier = Modifier.testTag(Tag)
- )
- }
- assertThat(onValueChangedCount).isEqualTo(0)
- requestFocus(Tag)
-
- rule.runOnIdle {
- text = "hello"
- }
-
- rule.runOnIdle {
- assertThat(onValueChangedCount).isEqualTo(0)
- }
- }
-
// Regression test for b/311834126
@Test
fun whenPastingTextThatIncreasesEndOffset_noCrashAndCursorAtEndOfPastedText() {
@@ -1300,7 +1117,7 @@
inputMethodInterceptor.setTextFieldTestContent {
tfs = rememberTextFieldState(shortText)
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = tfs,
modifier = Modifier.testTag(Tag),
)
@@ -1325,7 +1142,7 @@
immRule.setFactory { imm }
val state = TextFieldState("Hello")
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -1349,7 +1166,7 @@
val state = TextFieldState("Hello", initialSelectionInChars = TextRange(0, 2))
inputMethodInterceptor.setTextFieldTestContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -1374,7 +1191,7 @@
val state = TextFieldState("Hello", initialSelectionInChars = TextRange(0, 2))
inputMethodInterceptor.setTextFieldTestContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -1398,7 +1215,7 @@
val state = TextFieldState("Hello", initialSelectionInChars = TextRange(0, 4))
inputMethodInterceptor.setTextFieldTestContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag)
)
@@ -1430,7 +1247,7 @@
)
rule.setContent {
CompositionLocalProvider(LocalDensity provides density) {
- BasicTextField2(
+ BasicTextField(
modifier = Modifier.testTag(Tag),
state = rememberTextFieldState("A"),
textStyle = textStyle,
@@ -1464,7 +1281,7 @@
).width
CompositionLocalProvider(LocalDensity provides density) {
- BasicTextField2(
+ BasicTextField(
modifier = Modifier
.testTag(Tag)
.width(defaultWidth.dp / 2),
@@ -1500,7 +1317,7 @@
).width
CompositionLocalProvider(LocalDensity provides density) {
- BasicTextField2(
+ BasicTextField(
modifier = Modifier
.testTag(Tag)
.width(defaultWidth.dp * 2),
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt
index a223de4..b1dee54 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt
@@ -22,7 +22,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
@@ -52,7 +52,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,14 +64,14 @@
@get:Rule
val rule = createComposeRule()
- private val Tag = "BasicTextField2"
+ private val Tag = "BasicTextField"
private val DecorationTag = "DecorationBox"
@Test
fun focusIsAppliedOnDecoratedComposable() {
val state = TextFieldState()
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
decorator = { innerTextField ->
@@ -99,7 +98,7 @@
fun semanticsAreAppliedOnDecoratedComposable() {
val state = TextFieldState("hello")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
decorator = { innerTextField ->
@@ -126,7 +125,7 @@
fun clickGestureIsAppliedOnDecoratedComposable() {
val state = TextFieldState("hello")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
decorator = { innerTextField ->
@@ -156,7 +155,7 @@
fun nonPlacedInnerTextField_stillAcceptsTextInput() {
val state = TextFieldState()
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
decorator = {
@@ -185,7 +184,7 @@
fun nonPlacedInnerTextField_stillAcceptsKeyInput() {
val state = TextFieldState()
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
decorator = {
@@ -220,7 +219,7 @@
val state = TextFieldState()
var decorationBoxConstraints: Constraints? = null
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.fillMaxSize().testTag(Tag),
decorator = {
@@ -245,13 +244,12 @@
.isEqualTo(decorationBoxConstraints?.maxHeight)
}
- @Ignore // TODO(halilibo): enable when pointerInput gestures are enabled
@Test
fun longClickGestureIsAppliedOnDecoratedComposable() {
- // create a decorated BasicTextField2
+ // create a decorated BasicTextField
val state = TextFieldState("hello")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
decorator = { innerTextField ->
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HeightInLinesModifierTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HeightInLinesModifierTest.kt
index 7d021df..18636bf 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HeightInLinesModifierTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HeightInLinesModifierTest.kt
@@ -23,7 +23,7 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.requiredWidth
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.TEST_FONT
import androidx.compose.foundation.text.heightInLines
import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
@@ -344,7 +344,7 @@
onGlobalHeightPositioned(it.size.height)
}
) {
- BasicTextField2(
+ BasicTextField(
state = remember { TextFieldState(text) },
textStyle = textStyle,
lineLimits = lineLimits,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
index 558b4ff..8595f21 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,8 @@
import kotlinx.coroutines.awaitCancellation
/**
- * Helper class for testing integration of BasicTextField and BasicTextField2 with the platform IME.
+ * Helper class for testing integration of BasicTextField and Legacy BasicTextField with the
+ * platform IME.
*/
class InputMethodInterceptor(private val rule: ComposeContentTestRule) {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt
index e05003a..7d8c095 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt
@@ -17,7 +17,7 @@
package androidx.compose.foundation.text.input
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.internal.CodepointTransformation
import androidx.compose.foundation.text.input.internal.mask
import androidx.compose.foundation.text.selection.fetchTextLayoutResult
@@ -60,14 +60,14 @@
private val inputMethodInterceptor = InputMethodInterceptor(rule)
- private val Tag = "BasicTextField2"
+ private val Tag = "BasicTextField"
@Test
fun textField_rendersTheResultOf_codepointTransformation() {
val state = TextFieldState()
state.setTextAndPlaceCursorAtEnd("Hello")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
codepointTransformation = { _, codepoint -> codepoint + 1 },
modifier = Modifier.testTag(Tag)
@@ -82,7 +82,7 @@
val state = TextFieldState()
state.setTextAndPlaceCursorAtEnd("Hello")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
codepointTransformation = { index, codepoint ->
if (index % 2 == 0) codepoint + 1 else codepoint - 1
@@ -101,7 +101,7 @@
state.setTextAndPlaceCursorAtEnd("Hello")
var codepointTransformation: CodepointTransformation? by mutableStateOf(null)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
codepointTransformation = codepointTransformation,
modifier = Modifier.testTag(Tag)
@@ -121,7 +121,7 @@
state.setTextAndPlaceCursorAtEnd("Hello")
var mask by mutableStateOf('-')
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
codepointTransformation = CodepointTransformation.mask(mask),
modifier = Modifier.testTag(Tag)
@@ -143,7 +143,7 @@
CodepointTransformation.mask('*')
)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
codepointTransformation = codepointTransformation,
modifier = Modifier.testTag(Tag)
@@ -162,7 +162,7 @@
val state = TextFieldState()
state.setTextAndPlaceCursorAtEnd("Hello")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
codepointTransformation = CodepointTransformation.mask('*'),
modifier = Modifier.testTag(Tag)
@@ -180,7 +180,7 @@
val state = TextFieldState()
state.setTextAndPlaceCursorAtEnd("Hello\nWorld")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
lineLimits = TextFieldLineLimits.SingleLine,
modifier = Modifier.testTag(Tag)
@@ -197,7 +197,7 @@
val state = TextFieldState()
state.setTextAndPlaceCursorAtEnd("Hello\rWorld")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
lineLimits = TextFieldLineLimits.SingleLine,
modifier = Modifier.testTag(Tag)
@@ -212,7 +212,7 @@
val state = TextFieldState()
state.setTextAndPlaceCursorAtEnd("Hello\nWorld")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
lineLimits = TextFieldLineLimits.SingleLine,
codepointTransformation = { _, codepoint -> codepoint },
@@ -227,7 +227,7 @@
fun surrogateToNonSurrogate_singleCodepoint_isTransformed() {
val state = TextFieldState(SingleSurrogateCodepointString)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithNonSurrogate
@@ -241,7 +241,7 @@
fun surrogateToNonSurrogate_multipleCodepoints_areTransformed() {
val state = TextFieldState(SingleSurrogateCodepointString + SingleSurrogateCodepointString)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithNonSurrogate
@@ -255,7 +255,7 @@
fun surrogateToNonSurrogate_withNonSurrogates_areTransformed() {
val state = TextFieldState("a${SingleSurrogateCodepointString}b")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithNonSurrogate
@@ -269,7 +269,7 @@
fun nonSurrogateToSurrogate_singleCodepoint_isTransformed() {
val state = TextFieldState("a")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithSurrogate
@@ -283,7 +283,7 @@
fun nonSurrogateToSurrogate_multipleCodepoints_areTransformed() {
val state = TextFieldState("ab")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithSurrogate
@@ -297,7 +297,7 @@
fun nonSurrogateToSurrogate_withNonSurrogates_areTransformed() {
val state = TextFieldState("abc")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = { i, codepoint ->
@@ -313,7 +313,7 @@
fun surrogateToNonSurrogate_singleCodepoint_selectionIsMappedAroundCodepoint() {
val state = TextFieldState(SingleSurrogateCodepointString)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithNonSurrogate
@@ -333,7 +333,7 @@
fun nonSurrogateToSurrogate_singleCodepoint_selectionIsMappedAroundCodepoint() {
val state = TextFieldState("a")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithSurrogate
@@ -359,7 +359,7 @@
fun multipleCodepoints_selectionIsMappedAroundCodepoints() {
val state = TextFieldState("a${SingleSurrogateCodepointString}c")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = { i, codepoint ->
@@ -414,7 +414,7 @@
fun cursorTraversal_withArrowKeys() {
val state = TextFieldState("a${SingleSurrogateCodepointString}c")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = { i, codepoint ->
@@ -449,7 +449,7 @@
fun expandSelectionForward_withArrowKeys() {
val state = TextFieldState("a${SingleSurrogateCodepointString}c")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = { i, codepoint ->
@@ -491,7 +491,7 @@
fun expandSelectionBackward_withArrowKeys() {
val state = TextFieldState("a${SingleSurrogateCodepointString}c")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = { i, codepoint ->
@@ -533,7 +533,7 @@
fun insertNonSurrogates_intoSurrogateMask_fromKeyEvents() {
val state = TextFieldState("a$SingleSurrogateCodepointString")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithSurrogate
@@ -560,7 +560,7 @@
fun insertNonSurrogates_intoNonSurrogateMask_fromKeyEvents() {
val state = TextFieldState("a$SingleSurrogateCodepointString")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithNonSurrogate
@@ -587,7 +587,7 @@
fun insertText_intoSurrogateMask_fromSemantics() {
val state = TextFieldState("a$SingleSurrogateCodepointString")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithSurrogate
@@ -613,7 +613,7 @@
fun insertNonSurrogates_intoNonSurrogateMask_fromSemantics() {
val state = TextFieldState("a$SingleSurrogateCodepointString")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithNonSurrogate
@@ -639,7 +639,7 @@
fun insertText_intoSurrogateMask_fromIme() {
val state = TextFieldState("a$SingleSurrogateCodepointString")
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithSurrogate
@@ -670,7 +670,7 @@
fun insertText_intoNonSurrogateMask_fromIme() {
val state = TextFieldState("a$SingleSurrogateCodepointString")
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithNonSurrogate
@@ -701,7 +701,7 @@
fun removeNonSurrogate_fromNonSurrogateMask_usingKeyEvents_mixedInput() {
val state = TextFieldState("${SingleSurrogateCodepointString.repeat(2)}aa")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithNonSurrogate
@@ -725,7 +725,7 @@
fun removeSurrogate_fromNonSurrogateMask_usingKeyEvents_mixedInput() {
val state = TextFieldState("aa${SingleSurrogateCodepointString.repeat(2)}")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithNonSurrogate
@@ -749,7 +749,7 @@
fun removeNonSurrogate_fromSurrogateMask_usingKeyEvents_mixedInput() {
val state = TextFieldState("a${SingleSurrogateCodepointString.repeat(2)}a")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithSurrogate
@@ -773,7 +773,7 @@
fun removeSurrogate_fromSurrogateMask_usingKeyEvents_mixedInput() {
val state = TextFieldState("aa${SingleSurrogateCodepointString.repeat(2)}")
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.testTag(Tag),
codepointTransformation = MaskWithSurrogate
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt
index fbb0eab..805c7ca 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt
@@ -26,7 +26,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.DefaultCursorThickness
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.TEST_FONT_FAMILY
@@ -81,13 +81,11 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.toOffset
-import androidx.test.filters.FlakyTest
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
import kotlin.math.ceil
import kotlin.math.floor
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -175,7 +173,7 @@
fun textFieldFocused_cursorRendered() {
state = TextFieldState()
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -199,7 +197,7 @@
state = TextFieldState()
rule.setTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier.width(30.dp),
@@ -225,7 +223,7 @@
fun textFieldFocused_cursorRendered_rtlText_ltrLayout() {
state = TextFieldState("\u05D0\u05D1\u05D2", TextRange(3))
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -249,7 +247,7 @@
state = TextFieldState("\u05D0\u05D1\u05D2", TextRange(3))
rule.setTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier.width(50.dp),
@@ -274,7 +272,7 @@
fun textFieldCursorAtTheEnd_coercedIntoView() {
state = TextFieldState("hello", TextRange(5))
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier.width(50.dp),
@@ -298,7 +296,7 @@
state = TextFieldState("\u05D0\u05D1\u05D2", TextRange(3))
rule.setTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier.width(30.dp),
@@ -322,7 +320,7 @@
fun textFieldFocused_cursorWithBrush() {
state = TextFieldState()
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle.copy(fontSize = textStyle.fontSize * 2),
modifier = Modifier
@@ -363,7 +361,7 @@
fun cursorBlinkingAnimation() {
state = TextFieldState()
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -402,7 +400,7 @@
state = TextFieldState()
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -437,7 +435,7 @@
fun cursorUnsetColor_noCursor() {
state = TextFieldState("hello", initialSelectionInChars = TextRange(2))
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -473,11 +471,10 @@
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
- @FlakyTest(bugId = 303503435)
fun cursorNotBlinking_whileTyping() {
state = TextFieldState("test", initialSelectionInChars = TextRange(4))
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier.width(100.dp),
@@ -490,8 +487,6 @@
// cursor visible first 500 ms
rule.mainClock.advanceTimeBy(500)
- // TODO(b/170298051) check here that cursor is visible when we have a way to control
- // cursor position when sending a text
// change text field value
rule.onNode(hasSetTextAction())
@@ -507,11 +502,10 @@
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
- @FlakyTest(bugId = 303903824)
fun selectionChanges_cursorNotBlinking() {
state = TextFieldState("test", initialSelectionInChars = TextRange(2))
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -526,9 +520,6 @@
rule.mainClock.advanceTimeBy(500)
rule.mainClock.advanceTimeByFrame()
- // TODO(b/170298051) check here that cursor is visible when we have a way to control
- // cursor position when sending a text
-
rule.onNode(hasSetTextAction())
.performTextInputSelection(TextRange(0))
@@ -547,7 +538,7 @@
state = TextFieldState()
rule.setTestContent {
Box(Modifier.padding(boxPadding)) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -583,7 +574,7 @@
CompositionLocalProvider(
LocalTextSelectionColors provides TextSelectionColors(Color.Blue, Color.Blue)
) {
- BasicTextField2(
+ BasicTextField(
state = state,
// make sure that background is not obstructing selection
textStyle = textStyle.copy(
@@ -613,7 +604,7 @@
state = TextFieldState("test")
rule.setTestContent {
Column {
- BasicTextField2(
+ BasicTextField(
state = state,
// make sure that background is not obstructing selection
textStyle = textStyle,
@@ -658,7 +649,7 @@
fun readOnly_cursorIsNotDrawn() {
state = TextFieldState("test", initialSelectionInChars = TextRange(4))
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -684,7 +675,7 @@
var readOnly by mutableStateOf(true)
state = TextFieldState("test", initialSelectionInChars = TextRange(4))
rule.setTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -724,7 +715,7 @@
rule.setTestContent {
CompositionLocalProvider(LocalWindowInfo provides createWindowInfo(focusWindow.value)) {
Box(Modifier.padding(boxPadding)) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -767,7 +758,7 @@
rule.setTestContent {
CompositionLocalProvider(LocalWindowInfo provides createWindowInfo(focusWindow.value)) {
Box(Modifier.padding(boxPadding)) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -820,7 +811,7 @@
selectionColor
)
) {
- BasicTextField2(
+ BasicTextField(
state = state,
// make sure that background is not obstructing selection
textStyle = textStyle.copy(background = Color.Unspecified),
@@ -844,7 +835,6 @@
.assertContainsColor(selectionColor)
}
- @Ignore("b/305799612")
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun textField_textDragging_cursorRendered() {
@@ -852,7 +842,7 @@
var view: View? = null
rule.setTestContent {
view = LocalView.current
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -883,7 +873,6 @@
.assertCursor(cursorTopCenterInLtr)
}
- @Ignore("b/305799612")
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun textField_textDragging_cursorDisappearsAfterTimeout() {
@@ -891,7 +880,7 @@
var view: View? = null
rule.setTestContent {
view = LocalView.current
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -928,7 +917,6 @@
)
}
- @Ignore("b/305799612")
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun textField_textDragging_cursorDoesNotDisappearWhileMoving() {
@@ -936,7 +924,7 @@
var view: View? = null
rule.setTestContent {
view = LocalView.current
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -978,7 +966,6 @@
.assertCursor(cursorTopCenterInLtr)
}
- @Ignore("b/305799612")
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun textField_textDragging_noWindowFocus_cursorRendered() {
@@ -987,7 +974,7 @@
rule.setContent {
Box(Modifier.padding(boxPadding)) {
view = LocalView.current
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier,
@@ -1074,7 +1061,7 @@
state = TextFieldState("hello world", TextRange(5))
rule.setTestContent {
Box(Modifier.padding(boxPadding)) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = textStyle,
modifier = textFieldModifier.layout { measurable, constraints ->
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt
index bdb9a23..4eb4162 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt
@@ -34,7 +34,7 @@
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.TEST_FONT_FAMILY
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.State
@@ -248,7 +248,7 @@
}
})
) {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
lineLimits = TextFieldLineLimits.SingleLine,
@@ -321,7 +321,7 @@
}
})
) {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
lineLimits = TextFieldLineLimits.SingleLine,
@@ -500,7 +500,7 @@
}
) {
isHovered = interactionSource?.collectIsHoveredAsState()
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = mergedStyle,
lineLimits = TextFieldLineLimits.SingleLine,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldFocusTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldFocusTest.kt
index fbb40e8..12d9098 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldFocusTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldFocusTest.kt
@@ -28,7 +28,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.text.BasicText
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
@@ -78,7 +78,7 @@
private fun TextFieldApp(dataList: List<FocusTestData>) {
for (data in dataList) {
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.focusRequester(data.focusRequester)
@@ -134,7 +134,7 @@
val tag = "textField"
rule.setContent {
val state = remember { TextFieldState() }
- BasicTextField2(
+ BasicTextField(
state = state,
enabled = enabled.value,
modifier = Modifier
@@ -237,7 +237,7 @@
focusRequester.requestFocus()
}
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier.focusRequester(focusRequester)
)
@@ -395,7 +395,7 @@
}
Row {
TestFocusableElement(id = "left")
- TestBasicTextField2(id = "1", requestFocus = true)
+ TestBasicTextField(id = "1", requestFocus = true)
TestFocusableElement(id = "right")
}
Row(horizontalArrangement = Arrangement.Center) {
@@ -426,7 +426,7 @@
}
@Composable
- private fun TestBasicTextField2(
+ private fun TestBasicTextField(
id: String,
requestFocus: Boolean = false
) {
@@ -439,7 +439,7 @@
}
val modifier = if (requestFocus) Modifier.focusRequester(focusRequester) else Modifier
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = modifier
.testTag("test-text-field-$id")
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt
index 89dc380..a51d72f 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt
@@ -20,7 +20,7 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.text.BasicSecureTextField
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.TEST_FONT_FAMILY
import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
@@ -717,7 +717,7 @@
LocalClipboardManager provides clipboardManager,
) {
if (!secure) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = TextStyle(
fontFamily = TEST_FONT_FAMILY,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyboardActionsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyboardActionsTest.kt
index a0463d2..7d10104 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyboardActionsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyboardActionsTest.kt
@@ -22,7 +22,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.KeyboardActionScope
import androidx.compose.foundation.text.KeyboardActions
@@ -74,7 +74,7 @@
fun textField_performsImeAction_viaSemantics() {
var called = false
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
keyboardActions = KeyboardActions {
@@ -92,7 +92,7 @@
fun textField_performsImeAction_viaInputConnection() {
var called = false
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
keyboardActions = KeyboardActions {
@@ -113,7 +113,7 @@
fun textField_performsUnexpectedImeAction_fromInputConnection() {
var calledFor: ImeAction? = null
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
keyboardActions = KeyboardActionsAll {
@@ -139,7 +139,7 @@
.size(1.dp)
.focusable()
.testTag("box1"))
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
)
@@ -165,7 +165,7 @@
.size(1.dp)
.focusable()
.testTag("box1"))
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Previous)
)
@@ -190,7 +190,7 @@
CompositionLocalProvider(
LocalSoftwareKeyboardController provides testKeyboardController
) {
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)
)
@@ -212,7 +212,7 @@
.size(1.dp)
.focusable()
.testTag("box1"))
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActionsAll {
@@ -242,7 +242,7 @@
.size(1.dp)
.focusable()
.testTag("box1"))
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActionsAll {
@@ -266,7 +266,7 @@
fun textField_performsGo_whenReceivedImeActionIsGo() {
var called = false
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardActions = KeyboardActions(onGo = {
called = true
@@ -286,7 +286,7 @@
fun textField_doesNotPerformGo_whenReceivedImeActionIsNotGo() {
var called = false
inputMethodInterceptor.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardActions = KeyboardActions(onGo = {
called = true
@@ -309,7 +309,7 @@
val actions2 = KeyboardActionsAll { lastCaller = 2 }
var keyboardActions by mutableStateOf(actions1)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = keyboardActions
@@ -331,7 +331,7 @@
fun textField_singleLinePressEnter_triggersPassedImeAction() {
var calledFor: ImeAction? = null
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Go),
keyboardActions = KeyboardActionsAll {
@@ -353,7 +353,7 @@
fun textField_multiLinePressEnter_doesNotTriggerPassedImeAction() {
var calledFor: ImeAction? = null
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Go),
keyboardActions = KeyboardActionsAll {
@@ -380,7 +380,7 @@
.size(1.dp)
.focusable()
.testTag("box1"))
- BasicTextField2(
+ BasicTextField(
state = TextFieldState(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
lineLimits = SingleLine
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt
index edcb3d1..0a31082 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt
@@ -17,7 +17,7 @@
package androidx.compose.foundation.text.input
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.TEST_FONT_FAMILY
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
@@ -51,13 +51,13 @@
private val inputMethodInterceptor = InputMethodInterceptor(rule)
- private val Tag = "BasicTextField2"
+ private val Tag = "BasicTextField"
@Test
fun clickingAroundReplacement_movesCursorToEdgesOfReplacement() {
val text = TextFieldState("zaz", initialSelectionInChars = TextRange(0))
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = text,
modifier = Modifier.testTag(Tag),
textStyle = TextStyle(
@@ -105,7 +105,7 @@
val replacement = "bbbb\nbb"
val indexOfA = text.text.indexOf('a')
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = text,
modifier = Modifier.testTag(Tag),
textStyle = TextStyle(
@@ -144,7 +144,7 @@
fun clickingAroundReplacement_movesCursorToEdgesOfInsertion() {
val text = TextFieldState("zz", initialSelectionInChars = TextRange(0))
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = text,
modifier = Modifier.testTag(Tag),
textStyle = TextStyle(
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt
index 02a9b08..4ca73f57 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt
@@ -17,7 +17,7 @@
package androidx.compose.foundation.text.input
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.selection.fetchTextLayoutResult
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
@@ -48,13 +48,13 @@
private val inputMethodInterceptor = InputMethodInterceptor(rule)
- private val Tag = "BasicTextField2"
+ private val Tag = "BasicTextField"
@Test
fun replacement_visualText() {
val text = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
inputMethodInterceptor.setContent {
- BasicTextField2(state = text,
+ BasicTextField(state = text,
modifier = Modifier.testTag(Tag),
outputTransformation = {
replace(1, 3, "efg") // "aefgd"
@@ -69,7 +69,7 @@
fun replacement_cursorMovement_leftToRight_byCharacter_stateOffsets() {
val text = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
inputMethodInterceptor.setContent {
- BasicTextField2(state = text,
+ BasicTextField(state = text,
modifier = Modifier.testTag(Tag),
outputTransformation = {
replace(1, 3, "efg") // "aefgd"
@@ -91,7 +91,7 @@
fun replacement_cursorMovement_rightToLeft_byCharacter_stateOffsets() {
val text = TextFieldState("abcd", initialSelectionInChars = TextRange(4))
inputMethodInterceptor.setContent {
- BasicTextField2(state = text,
+ BasicTextField(state = text,
modifier = Modifier.testTag(Tag),
outputTransformation = {
replace(1, 3, "efg") // "aefgd"
@@ -113,7 +113,7 @@
fun replacement_cursorMovement_leftToRight_byCharacter_semanticsOffsets() {
val text = TextFieldState("abcd", initialSelectionInChars = TextRange(0))
inputMethodInterceptor.setContent {
- BasicTextField2(state = text,
+ BasicTextField(state = text,
modifier = Modifier.testTag(Tag),
outputTransformation = {
replace(1, 3, "efg") // "aefgd"
@@ -135,7 +135,7 @@
fun replacement_cursorMovement_rightToLeft_byCharacter_semanticsOffsets() {
val text = TextFieldState("abcd", initialSelectionInChars = TextRange(4))
inputMethodInterceptor.setContent {
- BasicTextField2(state = text,
+ BasicTextField(state = text,
modifier = Modifier.testTag(Tag),
outputTransformation = {
replace(1, 3, "efg") // "aefgd"
@@ -157,7 +157,7 @@
fun insert_visualText() {
val text = TextFieldState("ab", initialSelectionInChars = TextRange(0))
inputMethodInterceptor.setContent {
- BasicTextField2(state = text,
+ BasicTextField(state = text,
modifier = Modifier.testTag(Tag),
outputTransformation = {
insert(1, "efg") // "aefgb"
@@ -172,7 +172,7 @@
fun insert_cursorMovement_leftToRight_byCharacter_stateOffsets() {
val text = TextFieldState("ab", initialSelectionInChars = TextRange(0))
inputMethodInterceptor.setContent {
- BasicTextField2(state = text,
+ BasicTextField(state = text,
modifier = Modifier.testTag(Tag),
outputTransformation = {
insert(1, "efg") // "aefgb"
@@ -194,7 +194,7 @@
fun insert_cursorMovement_rightToLeft_byCharacter_stateOffsets() {
val text = TextFieldState("ab", initialSelectionInChars = TextRange(2))
inputMethodInterceptor.setContent {
- BasicTextField2(state = text,
+ BasicTextField(state = text,
modifier = Modifier.testTag(Tag),
outputTransformation = {
insert(1, "efg") // "aefgb"
@@ -216,7 +216,7 @@
fun insert_cursorMovement_leftToRight_byCharacter_semanticsOffsets() {
val text = TextFieldState("ab", initialSelectionInChars = TextRange(0))
inputMethodInterceptor.setContent {
- BasicTextField2(state = text,
+ BasicTextField(state = text,
modifier = Modifier.testTag(Tag),
outputTransformation = {
insert(1, "efg") // "aefgb"
@@ -238,7 +238,7 @@
fun insert_cursorMovement_rightToLeft_byCharacter_semanticsOffsets() {
val text = TextFieldState("ab", initialSelectionInChars = TextRange(2))
inputMethodInterceptor.setContent {
- BasicTextField2(state = text,
+ BasicTextField(state = text,
modifier = Modifier.testTag(Tag),
outputTransformation = {
insert(1, "efg") // "aefgb"
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt
index 317ea68..695108a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt
@@ -31,7 +31,7 @@
import androidx.compose.foundation.content.receiveContent
import androidx.compose.foundation.draganddrop.dragAndDropTarget
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.internal.selection.FakeClipboardManager
import androidx.compose.foundation.text.selection.FakeTextToolbar
import androidx.compose.runtime.CompositionLocalProvider
@@ -62,7 +62,7 @@
import org.junit.runner.RunWith
/**
- * Tests InputConnection#commitContent calls from BasicTextField2 to receiveContent modifier.
+ * Tests InputConnection#commitContent calls from BasicTextField to receiveContent modifier.
*/
@MediumTest
@RunWith(AndroidJUnit4::class)
@@ -74,13 +74,13 @@
private val inputMethodInterceptor = InputMethodInterceptor(rule)
- private val tag = "BasicTextField2"
+ private val tag = "BasicTextField"
@SdkSuppress(minSdkVersion = 25)
@Test
fun commitContentReturnsFalse_whenNoReceiveContentConfigured() {
inputMethodInterceptor.setContent {
- BasicTextField2(state = rememberTextFieldState(), modifier = Modifier.testTag(tag))
+ BasicTextField(state = rememberTextFieldState(), modifier = Modifier.testTag(tag))
}
rule.onNodeWithTag(tag).requestFocus()
inputMethodInterceptor.withInputConnection {
@@ -98,7 +98,7 @@
@Test
fun preformPrivateCommandReturnsFalse_whenNoReceiveContentConfigured() {
inputMethodInterceptor.setContent {
- BasicTextField2(state = rememberTextFieldState(), modifier = Modifier.testTag(tag))
+ BasicTextField(state = rememberTextFieldState(), modifier = Modifier.testTag(tag))
}
rule.onNodeWithTag(tag).requestFocus()
inputMethodInterceptor.onIdle { editorInfo, inputConnection ->
@@ -120,7 +120,7 @@
@Test
fun singleReceiveContent_configuresEditorInfo() {
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
@@ -137,7 +137,7 @@
@Test
fun singleReceiveContent_duplicateMediaTypes_appliedUniquely() {
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
@@ -168,7 +168,7 @@
fun multiReceiveContent_mergesMediaTypes() {
inputMethodInterceptor.setContent {
Box(modifier = Modifier.receiveContent(setOf(MediaType.Text)) { null }) {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
@@ -194,7 +194,7 @@
Box(modifier = Modifier.receiveContent(
setOf(MediaType.Text, MediaType.Image)
) { null }) {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
@@ -225,7 +225,7 @@
}
})
) {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
@@ -249,7 +249,7 @@
fun singleReceiveContent_isCalledAfterCommitContent() {
var transferableContent: TransferableContent? = null
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
@@ -295,7 +295,7 @@
fun singleReceiveContent_permissionIsRequested() {
var transferableContent: TransferableContent? = null
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
@@ -334,7 +334,7 @@
var childTransferableContent: TransferableContent? = null
var parentTransferableContent: TransferableContent? = null
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
@@ -379,7 +379,7 @@
var childTransferableContent: TransferableContent? = null
var parentTransferableContent: TransferableContent? = null
inputMethodInterceptor.setContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
@@ -418,7 +418,7 @@
lateinit var transferableContent: TransferableContent
rule.setContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
@@ -451,7 +451,7 @@
val state = TextFieldState()
rule.setContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.testTag(tag)
@@ -490,7 +490,7 @@
rule.setContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.testTag(tag)
@@ -544,7 +544,7 @@
LocalClipboardManager provides clipboardManager,
LocalTextToolbar provides textToolbar
) {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
modifier = Modifier
.testTag(tag)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt
index 106eb45..a632694 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt
@@ -29,7 +29,7 @@
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
@@ -553,7 +553,7 @@
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
- BasicTextField2(
+ BasicTextField(
state,
scrollState = scrollState,
lineLimits = SingleLine,
@@ -602,7 +602,7 @@
val state = TextFieldState("aaaaaaaaaa")
val scrollState = ScrollState(0)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state,
scrollState = scrollState,
lineLimits = SingleLine,
@@ -632,7 +632,7 @@
val state = TextFieldState("a\na\na\na\n", initialSelectionInChars = TextRange(0))
val scrollState = ScrollState(Int.MAX_VALUE)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state,
scrollState = scrollState,
lineLimits = MultiLine(maxHeightInLines = 1),
@@ -655,7 +655,7 @@
val state = TextFieldState("a\na\na\na\n")
val scrollState = ScrollState(0)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state,
scrollState = scrollState,
lineLimits = MultiLine(maxHeightInLines = 1),
@@ -681,7 +681,7 @@
val state = TextFieldState("a\na\na\na\n")
val scrollState = ScrollState(Int.MAX_VALUE)
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state,
scrollState = scrollState,
lineLimits = MultiLine(maxHeightInLines = 1),
@@ -720,7 +720,7 @@
.border(1.dp, Color.Red)
.verticalScroll(scrollState)
) {
- BasicTextField2(
+ BasicTextField(
state,
// The field should never scroll internally.
lineLimits = MultiLine(maxHeightInLines = Int.MAX_VALUE),
@@ -802,7 +802,7 @@
lineLimits: TextFieldLineLimits
) {
testScope = rememberCoroutineScope()
- BasicTextField2(
+ BasicTextField(
state = state,
scrollState = scrollState,
lineLimits = lineLimits,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldSingleLineHeightTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldSingleLineHeightTest.kt
index fd3faee..41e8d11 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldSingleLineHeightTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldSingleLineHeightTest.kt
@@ -17,7 +17,7 @@
package androidx.compose.foundation.text.input
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.Handle
import androidx.compose.foundation.text.selection.isSelectionHandle
@@ -56,7 +56,7 @@
val state = TextFieldState("")
var reportedSize: IntSize = IntSize.Zero
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
lineLimits = TextFieldLineLimits.SingleLine,
modifier = Modifier.onSizeChanged {
@@ -81,7 +81,7 @@
val state = TextFieldState(defaultText)
var reportedSize: IntSize = IntSize.Zero
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
lineLimits = TextFieldLineLimits.SingleLine,
modifier = Modifier.onSizeChanged {
@@ -105,7 +105,7 @@
fun singleLineTextField_withTallText_showsCursorHandle_whenClicked() {
val state = TextFieldState(tallText)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
lineLimits = TextFieldLineLimits.SingleLine,
modifier = Modifier.testTag(TextfieldTag)
@@ -121,7 +121,7 @@
fun multiLineTextField_withTallText_showsCursorHandle_whenClicked() {
val state = TextFieldState(tallText)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
lineLimits = TextFieldLineLimits.MultiLine(1, 1),
modifier = Modifier.testTag(TextfieldTag)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/BasicTextFieldHoverTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/BasicTextFieldHoverTest.kt
index 228f98d..24e17f0 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/BasicTextFieldHoverTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/BasicTextFieldHoverTest.kt
@@ -26,7 +26,7 @@
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.requiredSize
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.PointerIconTestScope
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.ui.Modifier
@@ -57,62 +57,81 @@
@Test
fun whenDefaultIcon_inBoxWithDefaultIcon_textIconIsUsed() = runTest(
- boxIcon = null,
+ boxIconModifier = Modifier,
expectedBoxIcon = TYPE_DEFAULT,
- textFieldIcon = null,
+ textFieldIconModifier = Modifier,
expectedTextIcon = TYPE_TEXT
)
@Test
- fun whenSetIcon_inBoxWithDefaultIcon_setIconIsUsed() = runTest(
- boxIcon = null,
+ fun whenSetIcon_inBoxWithDefaultIcon_textIconIsUsed() = runTest(
+ boxIconModifier = Modifier,
expectedBoxIcon = TYPE_DEFAULT,
- textFieldIcon = PointerIcon.Crosshair,
+ textFieldIconModifier = Modifier.pointerHoverIcon(PointerIcon.Crosshair),
+ expectedTextIcon = TYPE_TEXT
+ )
+
+ @Test
+ fun whenSetIcon_withOverride_inBoxWithDefaultIcon_setIconIsUsed() = runTest(
+ boxIconModifier = Modifier,
+ expectedBoxIcon = TYPE_DEFAULT,
+ textFieldIconModifier = Modifier.pointerHoverIcon(
+ icon = PointerIcon.Crosshair,
+ overrideDescendants = true
+ ),
expectedTextIcon = TYPE_CROSSHAIR
)
@Test
fun whenDefaultIcon_inBoxWithSetIcon_textIconIsUsed() = runTest(
- boxIcon = PointerIcon.Hand,
+ boxIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
expectedBoxIcon = TYPE_HAND,
- textFieldIcon = null,
+ textFieldIconModifier = Modifier,
expectedTextIcon = TYPE_TEXT
)
@Test
- fun whenSetIcon_inBoxWithSetIcon_setIconIsUsed() = runTest(
- boxIcon = PointerIcon.Hand,
+ fun whenSetIcon_inBoxWithSetIcon_textIconIsUsed() = runTest(
+ boxIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
expectedBoxIcon = TYPE_HAND,
- textFieldIcon = PointerIcon.Crosshair,
+ textFieldIconModifier = Modifier.pointerHoverIcon(PointerIcon.Crosshair),
+ expectedTextIcon = TYPE_TEXT
+ )
+
+ @Test
+ fun whenSetIcon_withOverride_inBoxWithSetIcon_setIconIsUsed() = runTest(
+ boxIconModifier = Modifier.pointerHoverIcon(PointerIcon.Hand),
+ expectedBoxIcon = TYPE_HAND,
+ textFieldIconModifier = Modifier.pointerHoverIcon(
+ icon = PointerIcon.Crosshair,
+ overrideDescendants = true
+ ),
expectedTextIcon = TYPE_CROSSHAIR
)
private fun runTest(
- boxIcon: PointerIcon?,
+ boxIconModifier: Modifier,
expectedBoxIcon: Int,
- textFieldIcon: PointerIcon?,
+ textFieldIconModifier: Modifier,
expectedTextIcon: Int,
) = with(PointerIconTestScope(rule)) {
val boxTag = "myParentIcon"
val textFieldTag = "myCoreTextField"
- fun Modifier.testPointerHoverIcon(icon: PointerIcon?): Modifier =
- if (icon == null) this else this.pointerHoverIcon(icon)
-
setContent {
val tfs = rememberTextFieldState("initial text")
Box(
modifier = Modifier
.requiredSize(200.dp)
- .testPointerHoverIcon(boxIcon)
+ .then(boxIconModifier)
.border(BorderStroke(2.dp, SolidColor(Color.Red)))
.testTag(boxTag)
) {
- BasicTextField2(
+ BasicTextField(
state = tfs,
modifier = Modifier
.requiredSize(50.dp)
- .testPointerHoverIcon(textFieldIcon)
+ .then(textFieldIconModifier)
.testTag(textFieldTag)
)
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
index ed23d87..ec63fc7 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/EditorInfoTest.kt
@@ -23,6 +23,7 @@
import androidx.compose.ui.text.input.ImeOptions
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.intl.LocaleList
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
@@ -569,6 +570,27 @@
assertThat(info.extras.keySet().any { it.contains("CONTENT_MIME_TYPES") }).isTrue()
}
+ @SdkSuppress(minSdkVersion = 24)
+ @Test
+ fun hintLocales_areApplied() {
+ val hintLocales = LocaleList("tr")
+ val info = EditorInfo()
+ info.update(ImeOptions(hintLocales = hintLocales))
+
+ assertThat(info.hintLocales?.toLanguageTags()).isEqualTo("tr")
+ }
+
+ @SdkSuppress(minSdkVersion = 24)
+ @Test
+ fun hintLocales_areNullified() {
+ val hintLocales = LocaleList("tr")
+ val info = EditorInfo()
+ info.update(ImeOptions(hintLocales = hintLocales))
+ info.update(ImeOptions.Default)
+
+ assertThat(info.hintLocales).isNull()
+ }
+
private fun EditorInfo.update(
imeOptions: ImeOptions,
contentMimeTypes: Array<String>? = null
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnectionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnectionTest.kt
index d027b6d..2de4ade 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnectionTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnectionTest.kt
@@ -62,11 +62,8 @@
this@StatelessInputConnectionTest.onImeAction?.invoke(imeAction)
}
- override fun requestEdit(
- notifyImeOfChanges: Boolean,
- block: EditingBuffer.() -> Unit
- ) {
- onRequestEdit?.invoke(notifyImeOfChanges, block)
+ override fun requestEdit(block: EditingBuffer.() -> Unit) {
+ onRequestEdit?.invoke(block)
}
override fun sendKeyEvent(keyEvent: KeyEvent) {
@@ -88,7 +85,7 @@
field = value
state = TextFieldState(value.toString(), value.selectionInChars)
}
- private var onRequestEdit: ((Boolean, EditingBuffer.() -> Unit) -> Unit)? = null
+ private var onRequestEdit: ((EditingBuffer.() -> Unit) -> Unit)? = null
private var onSendKeyEvent: ((KeyEvent) -> Unit)? = null
private var onImeAction: ((ImeAction) -> Unit)? = null
private var onCommitContent: ((TransferableContent) -> Boolean)? = null
@@ -191,7 +188,7 @@
@Test
fun commitTextTest_batchSession() {
var requestEditsCalled = 0
- onRequestEdit = { _, block ->
+ onRequestEdit = { block ->
requestEditsCalled++
state.mainBuffer.block()
}
@@ -325,7 +322,7 @@
@Test
fun mixedAPICalls_batchSession() {
var requestEditsCalled = 0
- onRequestEdit = { _, block ->
+ onRequestEdit = { block ->
requestEditsCalled++
state.mainBuffer.block()
}
@@ -366,7 +363,7 @@
@Test
fun do_not_callback_if_only_readonly_ops() {
var requestEditsCalled = 0
- onRequestEdit = { _, _ -> requestEditsCalled++ }
+ onRequestEdit = { _ -> requestEditsCalled++ }
ic.beginBatchEdit()
ic.getSelectedText(1)
ic.endBatchEdit()
@@ -419,12 +416,10 @@
}
@Test
- fun selectAll_contextMenuAction_triggersSelectionAndImeNotification() {
+ fun selectAll_contextMenuAction_triggersSelection() {
value = TextFieldCharSequence("Hello")
var callCount = 0
- var isNotifyIme = false
- onRequestEdit = { notify, block ->
- isNotifyIme = notify
+ onRequestEdit = { block ->
callCount++
state.mainBuffer.block()
}
@@ -432,7 +427,6 @@
ic.performContextMenuAction(android.R.id.selectAll)
Truth.assertThat(callCount).isEqualTo(1)
- Truth.assertThat(isNotifyIme).isTrue()
Truth.assertThat(state.mainBuffer.selection).isEqualTo(TextRange(0, 5))
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt
index b2823e1..9691d9c6 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt
@@ -18,15 +18,20 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.TEST_FONT_FAMILY
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
@@ -39,6 +44,7 @@
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.Dialog
import androidx.test.filters.LargeTest
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -53,7 +59,7 @@
private lateinit var state: TextFieldState
- private val TAG = "BasicTextField2"
+ private val TAG = "BasicTextField"
private val fontSize = 10.sp
@@ -64,7 +70,7 @@
// this test is more about detecting a possible crash
state = TextFieldState()
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier
@@ -92,7 +98,7 @@
fun clickOnText_ltr() {
state = TextFieldState("abc")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier
@@ -113,10 +119,13 @@
state = TextFieldState("\u05D0\u05D1\u05D2")
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
- modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
+ modifier = Modifier
+ .testTag(TAG)
+ .width(50.dp)
+ .height(15.dp)
)
}
}
@@ -132,10 +141,13 @@
state = TextFieldState("abc")
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
- modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
+ modifier = Modifier
+ .testTag(TAG)
+ .width(50.dp)
+ .height(15.dp)
)
}
}
@@ -150,10 +162,13 @@
fun clickOnText_rtl_in_ltrLayout() {
state = TextFieldState("\u05D0\u05D1\u05D2")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
- modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
+ modifier = Modifier
+ .testTag(TAG)
+ .width(50.dp)
+ .height(15.dp)
)
}
@@ -167,10 +182,13 @@
fun clickOnEmptyRegion_ltr() {
state = TextFieldState("abc")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
- modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
+ modifier = Modifier
+ .testTag(TAG)
+ .width(50.dp)
+ .height(15.dp)
)
}
@@ -185,10 +203,13 @@
state = TextFieldState("\u05D0\u05D1\u05D2")
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
- modifier = Modifier.testTag(TAG).width(50.dp).height(15.dp)
+ modifier = Modifier
+ .testTag(TAG)
+ .width(50.dp)
+ .height(15.dp)
)
}
}
@@ -204,7 +225,7 @@
state = TextFieldState("abcabcabcabc")
val scrollState = ScrollState(0)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
lineLimits = TextFieldLineLimits.SingleLine,
@@ -231,7 +252,7 @@
state = TextFieldState("abc abc abc abc")
val scrollState = ScrollState(0)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 2),
@@ -250,4 +271,35 @@
assertThat(state.text.selectionInChars).isEqualTo(TextRange(5))
assertThat(scrollState.value).isGreaterThan(0)
}
+
+ @Test
+ fun textFieldInsideDialog_tapsChangeCursorPosition() {
+ state = TextFieldState("abc abc abc abc")
+ val show = mutableStateOf(true)
+ val focusRequester = FocusRequester()
+ rule.setContent {
+ Dialog(
+ onDismissRequest = { show.value = false },
+ content = {
+ BasicTextField(
+ state = state,
+ textStyle = defaultTextStyle,
+ modifier = Modifier
+ .fillMaxWidth()
+ .testTag(TAG)
+ .focusRequester(focusRequester),
+ )
+ LaunchedEffect(Unit) {
+ focusRequester.requestFocus()
+ }
+ }
+ )
+ }
+
+ with(rule.onNodeWithTag(TAG)) {
+ performTouchInput { click(Offset(2 * fontSize.toPx(), centerY)) }
+ }
+ rule.waitForIdle()
+ assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+ }
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt
index fe3827a..d1114e4 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt
@@ -19,7 +19,7 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.DefaultCursorThickness
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.Handle
@@ -75,7 +75,7 @@
private lateinit var state: TextFieldState
- private val TAG = "BasicTextField2"
+ private val TAG = "BasicTextField"
private val fontSize = 10.sp
@@ -89,7 +89,7 @@
fun cursorHandle_showsAtCorrectLocation_ltr() {
state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier.testTag(TAG)
@@ -114,7 +114,7 @@
fun cursorHandle_hasMinimumTouchSizeArea() = with(rule.density) {
state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier.width(100.dp).testTag(TAG)
@@ -139,7 +139,7 @@
fun tapTextField_cursorHandleFiltered() {
state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
inputTransformation = { _, valueWithChanges ->
@@ -162,7 +162,7 @@
fun cursorHandle_showsAtCorrectLocation_outOfTextBoundsTouch_ltr() {
state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -190,7 +190,7 @@
state = TextFieldState("\u05D0\u05D1\u05D2\u05D3\u05D4")
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -218,7 +218,7 @@
fun cursorHandle_showsAtCorrectLocation_outOfTextBoundsTouch_rtl() {
state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -245,7 +245,7 @@
fun cursorHandle_notVisibleOnEmptyField() {
state = TextFieldState()
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier.testTag(TAG)
@@ -262,7 +262,7 @@
fun cursorHandle_doesNotShow_whenTextFieldIsReadOnly() {
state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier.testTag(TAG),
@@ -280,7 +280,7 @@
fun cursorHandle_disappears_whenTextIsEdited() {
state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier.testTag(TAG)
@@ -300,7 +300,7 @@
fun cursorHandle_disappears_whenTextStateChanges() {
state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier.testTag(TAG)
@@ -320,7 +320,7 @@
fun cursorHandle_doesNotDisappear_whenSelectionChanges() {
state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier.testTag(TAG)
@@ -350,7 +350,7 @@
}
rule.setContent {
CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier.testTag(TAG)
@@ -373,7 +373,7 @@
fun cursorHandle_coercesAtBoundaries_ltr() {
state = TextFieldState("hello")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -414,7 +414,7 @@
var width = 0
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -459,7 +459,7 @@
lateinit var scope: CoroutineScope
rule.setTextFieldTestContent {
scope = rememberCoroutineScope()
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
// scrollable but still only show maximum one line in its viewport
@@ -490,7 +490,7 @@
lateinit var scope: CoroutineScope
rule.setTextFieldTestContent {
scope = rememberCoroutineScope()
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
// scrollable but still only show maximum one line in its viewport
@@ -519,7 +519,7 @@
state = TextFieldState("hello hello hello hello", initialSelectionInChars = TextRange.Zero)
val scrollState = ScrollState(0)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
// scrollable but still only show maximum one line in its viewport
@@ -554,7 +554,7 @@
state = TextFieldState("hello hello hello hello", initialSelectionInChars = TextRange.Zero)
val scrollState = ScrollState(0)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
// scrollable but still only show maximum one line in its viewport
@@ -584,7 +584,7 @@
fun cursorHandleDrag_getsFiltered() {
state = TextFieldState("abc abc")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
inputTransformation = { _, valueWithChanges ->
@@ -612,7 +612,7 @@
fun moveCursorHandleToRight_ltr() {
state = TextFieldState("abc", initialSelectionInChars = TextRange.Zero)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -636,7 +636,7 @@
fun moveCursorHandleToLeft_ltr() {
state = TextFieldState("abc", initialSelectionInChars = TextRange.Zero)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -661,7 +661,7 @@
fun moveCursorHandleToRight_ltr_outOfBounds() {
state = TextFieldState("abc", initialSelectionInChars = TextRange.Zero)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -685,7 +685,7 @@
fun moveCursorHandleToLeft_ltr_outOfBounds() {
state = TextFieldState("abc", initialSelectionInChars = TextRange(3))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -712,7 +712,7 @@
initialSelectionInChars = TextRange.Zero
)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
lineLimits = TextFieldLineLimits.SingleLine,
@@ -740,7 +740,7 @@
initialSelectionInChars = TextRange.Zero
)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
lineLimits = TextFieldLineLimits.SingleLine,
@@ -774,7 +774,7 @@
state = TextFieldState("\u05D0\u05D1\u05D2")
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -800,7 +800,7 @@
state = TextFieldState("\u05D0\u05D1\u05D2")
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -826,7 +826,7 @@
state = TextFieldState("\u05D0\u05D1\u05D2")
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -852,7 +852,7 @@
state = TextFieldState("\u05D0\u05D1\u05D2")
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -883,7 +883,7 @@
)
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
lineLimits = TextFieldLineLimits.SingleLine,
@@ -917,7 +917,7 @@
)
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
lineLimits = TextFieldLineLimits.SingleLine,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt
index 3134afe..d60ed98 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt
@@ -21,7 +21,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.Handle
import androidx.compose.foundation.text.TEST_FONT_FAMILY
@@ -61,7 +61,7 @@
import org.junit.Test
/**
- * Tests for long click interactions on BasicTextField2.
+ * Tests for long click interactions on BasicTextField.
*/
@OptIn(ExperimentalFoundationApi::class)
@LargeTest
@@ -70,7 +70,7 @@
@get:Rule
val rule = createComposeRule()
- private val TAG = "BasicTextField2"
+ private val TAG = "BasicTextField"
private val fontSize = 10.sp
@@ -79,7 +79,7 @@
@Test
fun emptyTextField_longPressDoesNotShowCursor() {
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = rememberTextFieldState(),
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -95,7 +95,7 @@
fun longPress_requestsFocus_beforePointerIsReleased() {
val state = TextFieldState("Hello, World!")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -115,7 +115,7 @@
fun longPressOnEmptyRegion_showsCursorAtTheEnd() {
val state = TextFieldState("abc")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier
@@ -147,7 +147,7 @@
LocalTextToolbar provides textToolbar,
LocalClipboardManager provides clipboardManager
) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier
@@ -170,7 +170,7 @@
fun longPressOnWord_selectsWord() {
val state = TextFieldState("abc def ghi")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -190,7 +190,7 @@
fun longPressOnWhitespace_doesNotSelectWhitespace() {
val state = TextFieldState("abc def ghi")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -214,7 +214,7 @@
lateinit var scope: CoroutineScope
rule.setTextFieldTestContent {
scope = rememberCoroutineScope()
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
scrollState = scrollState,
@@ -239,7 +239,7 @@
fun longPressOnDecoratedTextField_selectsWord() {
val state = TextFieldState("abc def ghi")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG),
@@ -269,7 +269,7 @@
fun longPress_dragToRight_selectsCurrentAndNextWord_ltr() {
val state = TextFieldState("abc def ghi")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -289,7 +289,7 @@
fun longPress_dragToLeft_selectsCurrentAndPreviousWord_ltr() {
val state = TextFieldState("abc def ghi")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -309,7 +309,7 @@
fun longPress_dragDown_selectsFromCurrentToTargetWord_ltr() {
val state = TextFieldState("abc def\nabc def\nabc def")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -329,7 +329,7 @@
fun longPress_dragUp_selectsFromCurrentToTargetWord_ltr() {
val state = TextFieldState("abc def\nabc def\nabc def")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -349,7 +349,7 @@
fun longPress_startingFromEndPadding_dragToLeft_selectsLastWord_ltr() {
val state = TextFieldState("abc def")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier
@@ -373,7 +373,7 @@
fun longPress_dragToRight_selectsCurrentAndPreviousWord_rtl() {
val state = TextFieldState(rtlText3)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -393,7 +393,7 @@
fun longPress_dragToLeft_selectsCurrentAndNextWord_rtl() {
val state = TextFieldState(rtlText3)
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -413,7 +413,7 @@
fun longPress_dragDown_selectsFromCurrentToTargetWord_rtl() {
val state = TextFieldState("$rtlText2\n$rtlText2\n$rtlText2")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -433,7 +433,7 @@
fun longPress_dragUp_selectsFromCurrentToTargetWord_rtl() {
val state = TextFieldState("$rtlText2\n$rtlText2\n$rtlText2")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier.testTag(TAG)
@@ -454,7 +454,7 @@
val state = TextFieldState(rtlText2)
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
modifier = Modifier
@@ -477,7 +477,7 @@
fun longPress_startDraggingToScrollRight_startHandleDoesNotShow_ltr() {
val state = TextFieldState("abc def ghi ".repeat(10))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
lineLimits = TextFieldLineLimits.SingleLine,
@@ -513,7 +513,7 @@
fun longPress_startDraggingToScrollDown_startHandleDoesNotShow_ltr() {
val state = TextFieldState("abc def ghi ".repeat(10))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
lineLimits = TextFieldLineLimits.MultiLine(1, 3),
@@ -549,7 +549,7 @@
fun longPress_startDraggingToScrollLeft_endHandleDoesNotShow_ltr() {
val state = TextFieldState("abc def ghi ".repeat(10))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
lineLimits = TextFieldLineLimits.SingleLine,
@@ -587,7 +587,7 @@
fun longPress_startDraggingToScrollUp_endHandleDoesNotShow_ltr() {
val state = TextFieldState("abc def ghi ".repeat(10))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = defaultTextStyle,
lineLimits = TextFieldLineLimits.MultiLine(1, 3),
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierTest.kt
index 1399054..ddbfb3a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierTest.kt
@@ -25,7 +25,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.Handle
import androidx.compose.foundation.text.TEST_FONT_FAMILY
import androidx.compose.foundation.text.input.TextFieldLineLimits
@@ -89,7 +89,7 @@
maxLines: Int
) {
val state = remember { TextFieldState(text) }
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = modifier,
textStyle = style,
@@ -230,7 +230,7 @@
@Test
fun magnifier_insideDecorationBox() {
- val tag = "BasicTextField2"
+ val tag = "BasicTextField"
val state = TextFieldState(
"aaaa",
initialSelectionInChars = TextRange.Zero
@@ -238,7 +238,7 @@
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalDensity provides Density(1f, 1f)) {
- BasicTextField2(
+ BasicTextField(
state = state,
Modifier.testTag(tag),
textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
@@ -268,7 +268,7 @@
@Test
fun magnifier_insideDecorationBox_scrolledVertically() {
- val tag = "BasicTextField2"
+ val tag = "BasicTextField"
val state = TextFieldState(
"aaaa\naaaa\naaaa\n".repeat(5),
initialSelectionInChars = TextRange.Zero
@@ -279,7 +279,7 @@
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalDensity provides Density(1f, 1f)) {
coroutineScope = rememberCoroutineScope()
- BasicTextField2(
+ BasicTextField(
state = state,
Modifier.testTag(tag),
textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
@@ -315,7 +315,7 @@
@Test
fun magnifier_insideDecorationBox_scrolledHorizontally() {
- val tag = "BasicTextField2"
+ val tag = "BasicTextField"
val state = TextFieldState(
"aaaa aaaa aaaa ".repeat(5),
initialSelectionInChars = TextRange.Zero
@@ -326,7 +326,7 @@
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalDensity provides Density(1f, 1f)) {
coroutineScope = rememberCoroutineScope()
- BasicTextField2(
+ BasicTextField(
state = state,
Modifier.testTag(tag).width(100.dp),
textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
@@ -374,7 +374,7 @@
else
"\u05D0\u05D1\u05D2\u05D3"
- val tag = "BasicTextField2"
+ val tag = "BasicTextField"
val state = TextFieldState(
"$fillerWord $fillerWord $fillerWord ".repeat(10),
initialSelectionInChars = TextRange.Zero
@@ -382,7 +382,7 @@
rule.setTextFieldTestContent {
CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
- BasicTextField2(
+ BasicTextField(
state = state,
Modifier
.fillMaxWidth()
@@ -441,7 +441,7 @@
override val isWindowFocused = false
}
) {
- BasicTextField2(
+ BasicTextField(
state = state,
textStyle = TextStyle(fontFamily = TEST_FONT_FAMILY, fontSize = 20.sp),
lineLimits = TextFieldLineLimits.SingleLine
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt
index f454f9a..d6ef91d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt
@@ -29,7 +29,7 @@
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.Handle
import androidx.compose.foundation.text.TEST_FONT_FAMILY
@@ -84,7 +84,7 @@
private lateinit var state: TextFieldState
- private val TAG = "BasicTextField2"
+ private val TAG = "BasicTextField"
private val fontSize = 10.sp
private val fontSizePx = with(rule.density) { fontSize.toPx() }
@@ -93,7 +93,7 @@
fun selectionHandles_doNotShow_whenFieldNotFocused() {
state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -109,7 +109,7 @@
fun selectionHandles_haveMinimumTouchSizeArea() = with(rule.density) {
state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -140,7 +140,7 @@
fun selectionHandles_appears_whenFieldGetsFocused() {
state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -164,7 +164,7 @@
.size(100.dp)
.focusRequester(focusRequester)
.focusable())
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -193,7 +193,7 @@
rule.setContent {
CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier.testTag(TAG).width(100.dp)
@@ -223,7 +223,7 @@
rule.setContent {
CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier.testTag(TAG).width(100.dp)
@@ -253,7 +253,7 @@
fun selectionHandles_locatedAtTheRightPosition_ltr_ltr() {
state = TextFieldState("hello, world", initialSelectionInChars = TextRange(2, 5))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -286,7 +286,7 @@
fun selectionHandles_locatedAtTheRightPosition_ltr_rtl() {
state = TextFieldState("abc \u05D0\u05D1\u05D2", initialSelectionInChars = TextRange(1, 6))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -319,7 +319,7 @@
// make it scrollable
state = TextFieldState("hello ".repeat(10), initialSelectionInChars = TextRange(1, 2))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
lineLimits = TextFieldLineLimits.SingleLine,
@@ -350,7 +350,7 @@
// make it scrollable
state = TextFieldState("hello ".repeat(10), initialSelectionInChars = TextRange(1, 2))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 2),
@@ -391,7 +391,7 @@
.horizontalScroll(rememberScrollState())
.testTag(containerTag)
) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -435,7 +435,7 @@
.verticalScroll(rememberScrollState())
.testTag(containerTag)
) {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -472,7 +472,7 @@
fun dragStartSelectionHandle_toExtendSelection() {
state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -493,7 +493,7 @@
fun dragEndSelectionHandle_toExtendSelection() {
state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -514,7 +514,7 @@
fun doubleClickOnWord_toSelectWord() {
state = TextFieldState("abc def ghj")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -538,7 +538,7 @@
fun doubleClickOnWhitespace_doesNotSelectWhitespace() {
state = TextFieldState("abc def ghj")
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -566,7 +566,7 @@
lateinit var scope: CoroutineScope
rule.setTextFieldTestContent {
scope = rememberCoroutineScope()
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
lineLimits = TextFieldLineLimits.SingleLine,
@@ -596,7 +596,7 @@
lateinit var scope: CoroutineScope
rule.setTextFieldTestContent {
scope = rememberCoroutineScope()
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 3),
@@ -623,7 +623,7 @@
fun dragEndSelectionHandle_outOfBounds_horizontally() {
state = TextFieldState("abc def ".repeat(10), initialSelectionInChars = TextRange(0, 3))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
lineLimits = TextFieldLineLimits.SingleLine,
@@ -649,7 +649,7 @@
state = TextFieldState("abc def ".repeat(10), initialSelectionInChars = TextRange(0, 3))
lateinit var layoutResult: () -> TextLayoutResult?
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 3),
@@ -677,7 +677,7 @@
fun dragStartSelectionHandle_extendsByWord() {
state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -699,7 +699,7 @@
fun dragEndSelectionHandle_extendsByWord() {
state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -721,7 +721,7 @@
fun dragStartSelectionHandle_shrinksByCharacter() {
state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -743,7 +743,7 @@
fun dragEndSelectionHandle_shrinksByCharacter() {
state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -765,7 +765,7 @@
fun dragStartSelectionHandlePastEndHandle_reversesTheSelection() {
state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
@@ -786,7 +786,7 @@
fun dragEndSelectionHandlePastStartHandle_canReverseSelection() {
state = TextFieldState("abc def ghj", initialSelectionInChars = TextRange(4, 7))
rule.setTextFieldTestContent {
- BasicTextField2(
+ BasicTextField(
state,
textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
modifier = Modifier
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt
index 84b03e2..2145d76 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt
@@ -18,7 +18,7 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.Handle
import androidx.compose.foundation.text.input.TextFieldState
@@ -59,13 +59,13 @@
@get:Rule
val rule = createComposeRule()
- private val Tag = "BasicTextField2"
+ private val Tag = "BasicTextField"
@Test
fun whenBackPressed_andReleased_textFieldClearsSelection() {
val state = TextFieldState("hello", TextRange(0, 0))
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state,
Modifier
.testTag(Tag)
@@ -87,7 +87,7 @@
val state = TextFieldState("hello", TextRange(0, 0))
var backPressed = 0
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state,
Modifier
.testTag(Tag)
@@ -115,7 +115,7 @@
fun whenBackPressed_coreTextFieldRetainsSelection() {
val state = TextFieldState("hello", TextRange(0, 0))
rule.setContent {
- BasicTextField2(
+ BasicTextField(
state,
Modifier
.testTag(Tag)
@@ -140,7 +140,7 @@
val state = TextFieldState("Hello")
rule.setTextFieldTestContent {
softwareKeyboardController = LocalSoftwareKeyboardController.current
- BasicTextField2(
+ BasicTextField(
state,
Modifier
.testTag(Tag)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt
index d2b1d17a..e435d3a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt
@@ -25,7 +25,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.Handle
import androidx.compose.foundation.text.TEST_FONT_FAMILY
@@ -102,7 +102,7 @@
val fontSizePx = with(rule.density) { fontSize.toPx() }
- val TAG = "BasicTextField2"
+ val TAG = "BasicTextField"
private var enabled by mutableStateOf(true)
@@ -798,7 +798,7 @@
.focusable()
.size(100.dp)
)
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.width(100.dp)
@@ -835,7 +835,7 @@
CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
Column {
if (toggleState.value) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = Modifier
.width(100.dp)
@@ -898,7 +898,7 @@
LocalTextToolbar provides toolbar,
LocalClipboardManager provides clipboardManager
) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = modifier
.width(100.dp)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt
index aa12202..aa55ad0 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt
@@ -23,7 +23,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.FocusedWindowTest
import androidx.compose.foundation.text.Handle
import androidx.compose.foundation.text.TEST_FONT_FAMILY
@@ -182,7 +182,7 @@
setContent { tag ->
sizeNullable = remember { mutableStateOf(null) }
tfs = rememberTextFieldState(text)
- BasicTextField2(
+ BasicTextField(
state = tfs,
textStyle = textStyle,
lineLimits = TextFieldLineLimits.SingleLine,
@@ -272,7 +272,7 @@
setContent { tag ->
sizeNullable = remember { mutableStateOf(null) }
tfs = rememberTextFieldState(text)
- BasicTextField2(
+ BasicTextField(
state = tfs,
textStyle = TextStyle(fontFamily = fontFamily, fontSize = fontSize),
lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 4),
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextField2UndoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextFieldUndoTest.kt
similarity index 95%
rename from compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextField2UndoTest.kt
rename to compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextFieldUndoTest.kt
index cdb3d02..774953d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextField2UndoTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextFieldUndoTest.kt
@@ -17,7 +17,7 @@
package androidx.compose.foundation.text.input.internal.undo
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.internal.selection.FakeClipboardManager
import androidx.compose.runtime.CompositionLocalProvider
@@ -48,7 +48,7 @@
@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
@LargeTest
@RunWith(AndroidJUnit4::class)
-internal class BasicTextField2UndoTest {
+internal class BasicTextFieldUndoTest {
@get:Rule
val rule = createComposeRule()
@@ -57,7 +57,7 @@
val state = TextFieldState("Hello", TextRange(5))
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
rule.onNode(hasSetTextAction()).performTextInput(", World")
@@ -73,7 +73,7 @@
val state = TextFieldState("Hello", TextRange(5))
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
rule.onNode(hasSetTextAction()).performTextInput(", World")
@@ -90,7 +90,7 @@
val state = TextFieldState("Hello", TextRange(5))
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
rule.onNode(hasSetTextAction()).typeText(", World")
@@ -106,7 +106,7 @@
val state = TextFieldState("Hello", TextRange(5))
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
with(rule.onNode(hasSetTextAction())) {
@@ -130,7 +130,7 @@
val state = TextFieldState("Hello, World", TextRange(12))
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
with(rule.onNode(hasSetTextAction())) {
@@ -153,7 +153,7 @@
val state = TextFieldState("Hello, World", TextRange(6))
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
with(rule.onNode(hasSetTextAction())) {
@@ -181,7 +181,7 @@
val state = TextFieldState("Hello", TextRange(5))
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
with(rule.onNode(hasSetTextAction())) {
@@ -199,7 +199,7 @@
val state = TextFieldState("Hello", TextRange(5))
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
with(rule.onNode(hasSetTextAction())) {
@@ -225,7 +225,7 @@
val state = TextFieldState()
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
with(rule.onNode(hasSetTextAction())) {
@@ -256,7 +256,7 @@
val state = TextFieldState()
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
with(rule.onNode(hasSetTextAction())) {
@@ -295,7 +295,7 @@
rule.setContent {
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
- BasicTextField2(state)
+ BasicTextField(state)
}
}
@@ -322,7 +322,7 @@
val state = TextFieldState("abc def ghi", TextRange(11))
rule.setContent {
- BasicTextField2(state)
+ BasicTextField(state)
}
with(rule.onNode(hasSetTextAction())) {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/HardwareKeyboardTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/HardwareKeyboardTest.kt
index bba31f0..0713741 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/HardwareKeyboardTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/HardwareKeyboardTest.kt
@@ -24,6 +24,7 @@
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.input.InputMethodInterceptor
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
@@ -56,6 +57,7 @@
class HardwareKeyboardTest {
@get:Rule
val rule = createComposeRule()
+ private val inputMethodInterceptor = InputMethodInterceptor(rule)
@Test
fun textField_typedEvents() {
@@ -580,7 +582,7 @@
sequence: SequenceScope.() -> Unit,
) {
lateinit var clipboardManager: ClipboardManager
- rule.setContent {
+ inputMethodInterceptor.setContent {
clipboardManager = LocalClipboardManager.current
BasicTextField(
value = value.value,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
index bca9582..935519c 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
@@ -1582,6 +1582,40 @@
assertThat(value.selection).isEqualTo(tfvAfterBackspace.selection)
}
+ // Regression test for b/322835187
+ @Test
+ fun whenToggleReadOnly_onTypedTextField_noChangeNorCrash() {
+ val tag = "tag"
+
+ var value by mutableStateOf(TextFieldValue())
+ var readOnly by mutableStateOf(false)
+ rule.setTextFieldTestContent {
+ BasicTextField(
+ value = value,
+ onValueChange = { value = it },
+ readOnly = readOnly,
+ modifier = Modifier.testTag(tag),
+ )
+ }
+ rule.onNodeWithTag(tag)
+ .requestFocus()
+ .performTextInput("Hello")
+
+ rule.runOnIdle {
+ assertThat(value.text).isEqualTo("Hello")
+ assertThat(value.selection).isEqualTo(TextRange(5))
+ }
+
+ rule.runOnUiThread { readOnly = true }
+ rule.waitForIdle()
+ rule.runOnUiThread { readOnly = false }
+
+ rule.runOnIdle {
+ assertThat(value.text).isEqualTo("Hello")
+ assertThat(value.selection).isEqualTo(TextRange(5))
+ }
+ }
+
// Regression test for b/322851615
@Test
fun whenRemeasureInnerTextField_andNotDecorationBox_firstTapPlacesCursorAtCorrectOffset() {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.android.kt
index dbe565c..8347e5c 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.android.kt
@@ -17,9 +17,11 @@
package androidx.compose.foundation
import android.content.Context
+import android.graphics.RenderNode
import android.os.Build
import android.widget.EdgeEffect
import androidx.annotation.ColorInt
+import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.EdgeEffectCompat.distanceCompat
import androidx.compose.foundation.EdgeEffectCompat.onAbsorbCompat
@@ -36,24 +38,23 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.center
+import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.NativeCanvas
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.drawscope.draw
+import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.pointer.PointerId
import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.layout
-import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.InspectorValueInfo
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Velocity
-import androidx.compose.ui.unit.toSize
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastFirstOrNull
import kotlin.math.roundToInt
@@ -70,38 +71,352 @@
}
}
-private class DrawOverscrollModifier(
+@RequiresApi(Build.VERSION_CODES.S)
+private class DrawStretchOverscrollModifier(
private val overscrollEffect: AndroidEdgeEffectOverscrollEffect,
+ private val edgeEffectWrapper: EdgeEffectWrapper,
inspectorInfo: InspectorInfo.() -> Unit
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
+ /**
+ * There is an unwanted behavior in the stretch overscroll effect we have to workaround:
+ * when the effect is started it is getting the current RenderNode bounds and clips the content
+ * by those bounds. Even if this RenderNode is not configured to do clipping. Or if it clips,
+ * but not within its bounds, but by the outline provided which could have a completely
+ * different bounds. That is what happens with our scrolling containers - they all clip by the
+ * rect which is larger than the RenderNode bounds in order to not clip the shadows drawn in
+ * the cross axis of the scrolling direction. This issue is not that visible in the Views world
+ * because Views do clip by default. So adding one more clip doesn't change much. Thus why the
+ * whole shadows mechanism in the Views world works differently, the shadows are drawn not
+ * in-place, but with the background of the first parent which has a background.
+ *
+ * To solve this we need to render into a larger area, either by creating a larger layer for the
+ * child to draw in, or by manually rendering the stretch into a larger RenderNode, and then
+ * drawing that RenderNode into the existing layer. The difficulty here is that we only want to
+ * extend the cross axis / clip the main axis (scrolling containers do this already), otherwise
+ * the extra layer space will be transformed by the stretch, which results in an incorrect
+ * effect that can also end up revealing content underneath the scrolling container, as we
+ * stretch the transparent pixels in the extra space. For this to work we would need to know
+ * the stretch direction at layer creation time (i.e, placeWithLayer inside placement), but
+ * [OverscrollEffect] has no knowledge of directionality until an event is received. Creating a
+ * larger layer in this way is also more expensive and requires more parts, as we have to use
+ * two layout modifiers to achieve the desired effect.
+ *
+ * As a result we instead create a RenderNode that we extend in the cross-axis direction by
+ * [MaxSupportedElevation] on each side, to allow for non-clipped space without affecting layer
+ * size. We then draw the content (translated to be centered) and apply the stretch into this
+ * larger RenderNode, and then draw the RenderNode back into the original canvas (translated
+ * back to balance the previous translation), allowing for any shadows / other content drawn
+ * outside the cross-axis bounds to be unclipped by the RenderNode stretch.
+ */
+ private var _renderNode: RenderNode? = null
+ private val renderNode
+ get() = _renderNode ?: RenderNode("AndroidEdgeEffectOverscrollEffect")
+ .also { _renderNode = it }
+
+ @Suppress("KotlinConstantConditions")
override fun ContentDrawScope.draw() {
- drawContent()
- with(overscrollEffect) {
- drawOverscroll()
+ overscrollEffect.updateSize(size)
+ if (size.isEmpty()) {
+ // Draw any out of bounds content
+ drawContent()
+ return
+ }
+ overscrollEffect.redrawSignal.value // <-- value read to redraw if needed
+ val maxElevation = MaxSupportedElevation.toPx()
+ val canvas = drawContext.canvas.nativeCanvas
+ var needsInvalidate = false
+ with(edgeEffectWrapper) {
+ val shouldDrawVerticalStretch = shouldDrawVerticalStretch()
+ val shouldDrawHorizontalStretch = shouldDrawHorizontalStretch()
+ when {
+ // Drawing in both directions, so we need to match canvas size and essentially clip
+ // both directions. We don't need the renderNode in this case, but it would
+ // complicate the rest of the drawing logic.
+ shouldDrawVerticalStretch && shouldDrawHorizontalStretch ->
+ renderNode.setPosition(0, 0, canvas.width, canvas.height)
+ // Drawing vertical stretch, so expand the width to prevent clipping
+ shouldDrawVerticalStretch ->
+ renderNode.setPosition(
+ 0,
+ 0,
+ canvas.width + (maxElevation.roundToInt() * 2),
+ canvas.height
+ )
+ // Drawing horizontal stretch, so expand the height to prevent clipping
+ shouldDrawHorizontalStretch ->
+ renderNode.setPosition(
+ 0,
+ 0,
+ canvas.width,
+ canvas.height + (maxElevation.roundToInt() * 2)
+ )
+ // Not drawing any stretch, so early return - we can draw into the existing canvas
+ else -> {
+ drawContent()
+ return
+ }
+ }
+ val recordingCanvas = renderNode.beginRecording()
+ // Views call RenderNode.clearStretch() (@hide API) to reset the stretch as part of
+ // the draw pass. We can't call this API, so by default the stretch would just keep on
+ // increasing for each new delta. Instead, to work around this, we can effectively
+ // 'negate' the previously rendered stretch by applying it, rotated 180 degrees, which
+ // cancels out the stretch applied to the RenderNode by the real stretch. To do this,
+ // we pull the negated stretch by the distance of the real stretch amount in each draw
+ // frame. Then in the next draw frame, we draw the negated stretch first, and then
+ // finish it so we can pull it by the real effect's distance again.
+ // Note that `draw` here isn't really drawing anything, it's applying a stretch to the
+ // whole RenderNode, so we can't clip / translate the drawing region here.
+ if (isLeftNegationStretched()) {
+ val leftEffectNegation = getOrCreateLeftEffectNegation()
+ // Invert the stretch
+ drawRightStretch(leftEffectNegation, recordingCanvas)
+ leftEffectNegation.finish()
+ }
+ if (isLeftAnimating()) {
+ val leftEffect = getOrCreateLeftEffect()
+ needsInvalidate = drawLeftStretch(leftEffect, recordingCanvas) || needsInvalidate
+ if (isLeftStretched()) {
+ // Displacement isn't currently used in AOSP for stretch, but provide the same
+ // displacement in case any OEMs have custom behavior.
+ val displacementY = overscrollEffect.displacement().y
+ getOrCreateLeftEffectNegation()
+ .onPullDistanceCompat(leftEffect.distanceCompat, 1 - displacementY)
+ }
+ }
+ if (isTopNegationStretched()) {
+ val topEffectNegation = getOrCreateTopEffectNegation()
+ // Invert the stretch
+ drawBottomStretch(topEffectNegation, recordingCanvas)
+ topEffectNegation.finish()
+ }
+ if (isTopAnimating()) {
+ val topEffect = getOrCreateTopEffect()
+ needsInvalidate = drawTopStretch(topEffect, recordingCanvas) || needsInvalidate
+ if (isTopStretched()) {
+ // Displacement isn't currently used in AOSP for stretch, but provide the same
+ // displacement in case any OEMs have custom behavior.
+ val displacementX = overscrollEffect.displacement().x
+ getOrCreateTopEffectNegation()
+ .onPullDistanceCompat(topEffect.distanceCompat, displacementX)
+ }
+ }
+ if (isRightNegationStretched()) {
+ val rightEffectNegation = getOrCreateRightEffectNegation()
+ // Invert the stretch
+ drawLeftStretch(rightEffectNegation, recordingCanvas)
+ rightEffectNegation.finish()
+ }
+ if (isRightAnimating()) {
+ val rightEffect = getOrCreateRightEffect()
+ needsInvalidate = drawRightStretch(rightEffect, recordingCanvas) || needsInvalidate
+ if (isRightStretched()) {
+ // Displacement isn't currently used in AOSP for stretch, but provide the same
+ // displacement in case any OEMs have custom behavior.
+ val displacementY = overscrollEffect.displacement().y
+ getOrCreateRightEffectNegation()
+ .onPullDistanceCompat(rightEffect.distanceCompat, displacementY)
+ }
+ }
+ if (isBottomNegationStretched()) {
+ val bottomEffectNegation = getOrCreateBottomEffectNegation()
+ // Invert the stretch
+ drawTopStretch(bottomEffectNegation, recordingCanvas)
+ bottomEffectNegation.finish()
+ }
+ if (isBottomAnimating()) {
+ val bottomEffect = getOrCreateBottomEffect()
+ needsInvalidate =
+ drawBottomStretch(bottomEffect, recordingCanvas) || needsInvalidate
+ if (isBottomStretched()) {
+ // Displacement isn't currently used in AOSP for stretch, but provide the same
+ // displacement in case any OEMs have custom behavior.
+ val displacementX = overscrollEffect.displacement().x
+ getOrCreateBottomEffectNegation()
+ .onPullDistanceCompat(bottomEffect.distanceCompat, 1 - displacementX)
+ }
+ }
+
+ if (needsInvalidate) overscrollEffect.invalidateOverscroll()
+ // Render the content for ContentDrawScope into the RenderNode, using the same size
+ // provided by ContentDrawScope - we only want to prevent clipping, not actually
+ // change the size of the content.
+ // Since we expand the size of the RenderNode so we don't clip the cross-axis content,
+ // we need to re-center the content in the RenderNode.
+ // We 'clip' in the direction of the stretch, so in that case there is no extra space
+ // and hence no need to translate. Otherwise, add the extra space.
+ val left = if (shouldDrawHorizontalStretch) 0f else maxElevation
+ val top = if (shouldDrawVerticalStretch) 0f else maxElevation
+ val outerDraw = this@draw
+ with(outerDraw) {
+ draw(this, this.layoutDirection, Canvas(recordingCanvas), size) {
+ translate(left, top) {
+ // Since the stretch effect isn't really 'drawn', but is just set on
+ // the RenderNode, it doesn't really matter when we call this in terms of
+ // draw ordering.
+ outerDraw.drawContent()
+ }
+ }
+ }
+ renderNode.endRecording()
+ // Now we can draw the larger RenderNode inside the actual canvas - but we need to
+ // translate it back by the amount we previously offset by inside the larger RenderNode.
+ val restore = canvas.save()
+ canvas.translate(-left, -top)
+ canvas.drawRenderNode(renderNode)
+ canvas.restoreToCount(restore)
}
}
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is DrawOverscrollModifier) return false
-
- return overscrollEffect == other.overscrollEffect
+ private fun shouldDrawVerticalStretch() = with(edgeEffectWrapper) {
+ isTopAnimating() || isTopNegationStretched() ||
+ isBottomAnimating() || isBottomNegationStretched()
+ }
+ private fun shouldDrawHorizontalStretch() = with(edgeEffectWrapper) {
+ isLeftAnimating() || isLeftNegationStretched() ||
+ isRightAnimating() || isRightNegationStretched()
}
- override fun hashCode(): Int {
- return overscrollEffect.hashCode()
+ private fun drawLeftStretch(left: EdgeEffect, canvas: NativeCanvas): Boolean {
+ return drawWithRotation(rotationDegrees = 270f, edgeEffect = left, canvas = canvas)
}
- override fun toString(): String {
- return "DrawOverscrollModifier(overscrollEffect=$overscrollEffect)"
+ private fun drawTopStretch(top: EdgeEffect, canvas: NativeCanvas): Boolean {
+ return drawWithRotation(rotationDegrees = 0f, edgeEffect = top, canvas = canvas)
+ }
+
+ private fun drawRightStretch(right: EdgeEffect, canvas: NativeCanvas): Boolean {
+ return drawWithRotation(rotationDegrees = 90f, edgeEffect = right, canvas = canvas)
+ }
+
+ private fun drawBottomStretch(bottom: EdgeEffect, canvas: NativeCanvas): Boolean {
+ return drawWithRotation(rotationDegrees = 180f, edgeEffect = bottom, canvas = canvas)
+ }
+
+ private fun drawWithRotation(
+ rotationDegrees: Float,
+ edgeEffect: EdgeEffect,
+ canvas: NativeCanvas
+ ): Boolean {
+ if (rotationDegrees == 0f) {
+ val needsInvalidate = edgeEffect.draw(canvas)
+ return needsInvalidate
+ }
+ val restore = canvas.save()
+ canvas.rotate(rotationDegrees)
+ val needsInvalidate = edgeEffect.draw(canvas)
+ canvas.restoreToCount(restore)
+ return needsInvalidate
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private class DrawGlowOverscrollModifier(
+ private val overscrollEffect: AndroidEdgeEffectOverscrollEffect,
+ private val edgeEffectWrapper: EdgeEffectWrapper,
+ private val overscrollConfig: OverscrollConfiguration,
+ inspectorInfo: InspectorInfo.() -> Unit
+) : DrawModifier, InspectorValueInfo(inspectorInfo) {
+
+ @Suppress("KotlinConstantConditions")
+ override fun ContentDrawScope.draw() {
+ overscrollEffect.updateSize(size)
+ if (size.isEmpty()) {
+ // Draw any out of bounds content
+ drawContent()
+ return
+ }
+ drawContent()
+ overscrollEffect.redrawSignal.value // <-- value read to redraw if needed
+ val canvas = drawContext.canvas.nativeCanvas
+ var needsInvalidate = false
+ with(edgeEffectWrapper) {
+ if (isLeftAnimating()) {
+ val leftEffect = getOrCreateLeftEffect()
+ needsInvalidate = drawLeftGlow(leftEffect, canvas) || needsInvalidate
+ }
+ if (isTopAnimating()) {
+ val topEffect = getOrCreateTopEffect()
+ needsInvalidate = drawTopGlow(topEffect, canvas) || needsInvalidate
+ }
+ if (isRightAnimating()) {
+ val rightEffect = getOrCreateRightEffect()
+ needsInvalidate = drawRightGlow(rightEffect, canvas) || needsInvalidate
+ }
+ if (isBottomAnimating()) {
+ val bottomEffect = getOrCreateBottomEffect()
+ needsInvalidate = drawBottomGlow(bottomEffect, canvas) || needsInvalidate
+ }
+ if (needsInvalidate) overscrollEffect.invalidateOverscroll()
+ }
+ }
+
+ private fun DrawScope.drawLeftGlow(left: EdgeEffect, canvas: NativeCanvas): Boolean {
+ val offset = Offset(
+ -size.height,
+ overscrollConfig.drawPadding.calculateLeftPadding(layoutDirection).toPx()
+ )
+ return drawWithRotationAndOffset(
+ rotationDegrees = 270f,
+ offset = offset,
+ edgeEffect = left,
+ canvas = canvas
+ )
+ }
+
+ private fun DrawScope.drawTopGlow(top: EdgeEffect, canvas: NativeCanvas): Boolean {
+ val offset = Offset(0f, overscrollConfig.drawPadding.calculateTopPadding().toPx())
+ return drawWithRotationAndOffset(
+ rotationDegrees = 0f,
+ offset = offset,
+ edgeEffect = top,
+ canvas = canvas
+ )
+ }
+
+ private fun DrawScope.drawRightGlow(right: EdgeEffect, canvas: NativeCanvas): Boolean {
+ val width = size.width.roundToInt()
+ val rightPadding = overscrollConfig.drawPadding.calculateRightPadding(layoutDirection)
+ val offset = Offset(0f, -width.toFloat() + rightPadding.toPx())
+ return drawWithRotationAndOffset(
+ rotationDegrees = 90f,
+ offset = offset,
+ edgeEffect = right,
+ canvas = canvas
+ )
+ }
+
+ private fun DrawScope.drawBottomGlow(bottom: EdgeEffect, canvas: NativeCanvas): Boolean {
+ val bottomPadding = overscrollConfig.drawPadding.calculateBottomPadding().toPx()
+ val offset = Offset(-size.width, -size.height + bottomPadding)
+ return drawWithRotationAndOffset(
+ rotationDegrees = 180f,
+ offset = offset,
+ edgeEffect = bottom,
+ canvas = canvas
+ )
+ }
+
+ private fun drawWithRotationAndOffset(
+ rotationDegrees: Float,
+ offset: Offset,
+ edgeEffect: EdgeEffect,
+ canvas: NativeCanvas
+ ): Boolean {
+ val restore = canvas.save()
+ canvas.rotate(rotationDegrees)
+ canvas.translate(offset.x, offset.y)
+ val needsInvalidate = edgeEffect.draw(canvas)
+ canvas.restoreToCount(restore)
+ return needsInvalidate
}
}
@OptIn(ExperimentalFoundationApi::class)
internal class AndroidEdgeEffectOverscrollEffect(
context: Context,
- private val overscrollConfig: OverscrollConfiguration
+ overscrollConfig: OverscrollConfiguration
) : OverscrollEffect {
private var pointerPosition: Offset? = null
@@ -110,7 +425,7 @@
glowColor = overscrollConfig.glowColor.toArgb()
)
- private val redrawSignal = mutableStateOf(Unit, neverEqualPolicy())
+ internal val redrawSignal = mutableStateOf(Unit, neverEqualPolicy())
@VisibleForTesting
internal var invalidationEnabled = true
@@ -131,12 +446,11 @@
stopOverscrollAnimation()
scrollCycleInProgress = true
}
- val pointer = pointerPosition ?: containerSize.center
// Relax existing stretches if needed before performing scroll
val consumedPixelsY = when {
delta.y == 0f -> 0f
edgeEffectWrapper.isTopStretched() -> {
- pullTop(delta, pointer).also {
+ pullTop(delta).also {
// Release / reset state if we have fully relaxed the stretch
if (!edgeEffectWrapper.isTopStretched()) {
edgeEffectWrapper.getOrCreateTopEffect().onRelease()
@@ -144,7 +458,7 @@
}
}
edgeEffectWrapper.isBottomStretched() -> {
- pullBottom(delta, pointer).also {
+ pullBottom(delta).also {
// Release / reset state if we have fully relaxed the stretch
if (!edgeEffectWrapper.isBottomStretched()) {
edgeEffectWrapper.getOrCreateBottomEffect().onRelease()
@@ -156,7 +470,7 @@
val consumedPixelsX = when {
delta.x == 0f -> 0f
edgeEffectWrapper.isLeftStretched() -> {
- pullLeft(delta, pointer).also {
+ pullLeft(delta).also {
// Release / reset state if we have fully relaxed the stretch
if (!edgeEffectWrapper.isLeftStretched()) {
edgeEffectWrapper.getOrCreateLeftEffect().onRelease()
@@ -164,7 +478,7 @@
}
}
edgeEffectWrapper.isRightStretched() -> {
- pullRight(delta, pointer).also {
+ pullRight(delta).also {
// Release / reset state if we have fully relaxed the stretch
if (!edgeEffectWrapper.isRightStretched()) {
edgeEffectWrapper.getOrCreateRightEffect().onRelease()
@@ -185,19 +499,19 @@
// Ignore small deltas (< 0.5) as this usually comes from floating point rounding issues
// and can cause scrolling to lock up (b/265363356)
val appliedHorizontalOverscroll = if (leftForOverscroll.x > 0.5f) {
- pullLeft(leftForOverscroll, pointer)
+ pullLeft(leftForOverscroll)
true
} else if (leftForOverscroll.x < -0.5f) {
- pullRight(leftForOverscroll, pointer)
+ pullRight(leftForOverscroll)
true
} else {
false
}
val appliedVerticalOverscroll = if (leftForOverscroll.y > 0.5f) {
- pullTop(leftForOverscroll, pointer)
+ pullTop(leftForOverscroll)
true
} else if (leftForOverscroll.y < -0.5f) {
- pullBottom(leftForOverscroll, pointer)
+ pullBottom(leftForOverscroll)
true
} else {
false
@@ -274,33 +588,34 @@
private fun stopOverscrollAnimation(): Boolean {
var stopped = false
- val fakeDisplacement = containerSize.center // displacement doesn't matter here
+ // Displacement doesn't matter here
if (edgeEffectWrapper.isLeftStretched()) {
- pullLeft(Offset.Zero, fakeDisplacement)
+ pullLeft(Offset.Zero)
stopped = true
}
if (edgeEffectWrapper.isRightStretched()) {
- pullRight(Offset.Zero, fakeDisplacement)
+ pullRight(Offset.Zero)
stopped = true
}
if (edgeEffectWrapper.isTopStretched()) {
- pullTop(Offset.Zero, fakeDisplacement)
+ pullTop(Offset.Zero)
stopped = true
}
if (edgeEffectWrapper.isBottomStretched()) {
- pullBottom(Offset.Zero, fakeDisplacement)
+ pullBottom(Offset.Zero)
stopped = true
}
return stopped
}
- private val onNewSize: (IntSize) -> Unit = { size ->
- val differentSize = size.toSize() != containerSize
- containerSize = size.toSize()
+ internal fun updateSize(size: Size) {
+ val initialSetSize = containerSize == Size.Zero
+ val differentSize = size != containerSize
+ containerSize = size
if (differentSize) {
- edgeEffectWrapper.setSize(size)
+ edgeEffectWrapper.setSize(IntSize(size.width.roundToInt(), size.height.roundToInt()))
}
- if (differentSize) {
+ if (!initialSetSize && differentSize) {
invalidateOverscroll()
animateToRelease()
}
@@ -308,8 +623,17 @@
private var pointerId: PointerId? = null
+ /**
+ * @return displacement based on the last [pointerPosition] and [containerSize]
+ */
+ internal fun displacement(): Offset {
+ val pointer = pointerPosition ?: containerSize.center
+ val x = pointer.x / containerSize.width
+ val y = pointer.y / containerSize.height
+ return Offset(x, y)
+ }
+
override val effectModifier: Modifier = Modifier
- .then(StretchOverscrollNonClippingLayer)
.pointerInput(Unit) {
awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false)
@@ -332,133 +656,30 @@
// don't change any existing effects
}
}
- .onSizeChanged(onNewSize)
.then(
- DrawOverscrollModifier(
- this@AndroidEdgeEffectOverscrollEffect,
- debugInspectorInfo {
- name = "overscroll"
- value = this@AndroidEdgeEffectOverscrollEffect
- })
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ DrawStretchOverscrollModifier(
+ this@AndroidEdgeEffectOverscrollEffect,
+ edgeEffectWrapper,
+ debugInspectorInfo {
+ name = "overscroll"
+ value = this@AndroidEdgeEffectOverscrollEffect
+ }
+ )
+ } else {
+ DrawGlowOverscrollModifier(
+ this@AndroidEdgeEffectOverscrollEffect,
+ edgeEffectWrapper,
+ overscrollConfig,
+ debugInspectorInfo {
+ name = "overscroll"
+ value = this@AndroidEdgeEffectOverscrollEffect
+ }
+ )
+ }
)
- fun DrawScope.drawOverscroll() {
- if (containerSize.isEmpty()) {
- return
- }
- this.drawIntoCanvas {
- redrawSignal.value // <-- value read to redraw if needed
- val canvas = it.nativeCanvas
- var needsInvalidate = false
- // each side workflow:
- // 1. reset what was draw in the past cycle, effectively clearing the effect
- // 2. Draw the effect on the edge
- // 3. Remember how much was drawn to clear in 1. in the next cycle
- if (edgeEffectWrapper.isLeftNegationStretched()) {
- val leftEffectNegation = edgeEffectWrapper.getOrCreateLeftEffectNegation()
- drawRight(leftEffectNegation, canvas)
- leftEffectNegation.finish()
- }
- if (edgeEffectWrapper.isLeftAnimating()) {
- val leftEffect = edgeEffectWrapper.getOrCreateLeftEffect()
- needsInvalidate = drawLeft(leftEffect, canvas) || needsInvalidate
- if (edgeEffectWrapper.isLeftStretched()) {
- edgeEffectWrapper
- .getOrCreateLeftEffectNegation()
- .onPullDistanceCompat(leftEffect.distanceCompat, 0f)
- }
- }
- if (edgeEffectWrapper.isTopNegationStretched()) {
- val topEffectNegation = edgeEffectWrapper.getOrCreateTopEffectNegation()
- drawBottom(topEffectNegation, canvas)
- topEffectNegation.finish()
- }
- if (edgeEffectWrapper.isTopAnimating()) {
- val topEffect = edgeEffectWrapper.getOrCreateTopEffect()
- needsInvalidate = drawTop(topEffect, canvas) ||
- needsInvalidate
- if (edgeEffectWrapper.isTopStretched()) {
- edgeEffectWrapper
- .getOrCreateTopEffectNegation()
- .onPullDistanceCompat(topEffect.distanceCompat, 0f)
- }
- }
- if (edgeEffectWrapper.isRightNegationStretched()) {
- val rightEffectNegation = edgeEffectWrapper.getOrCreateRightEffectNegation()
- drawLeft(rightEffectNegation, canvas)
- rightEffectNegation.finish()
- }
- if (edgeEffectWrapper.isRightAnimating()) {
- val rightEffect = edgeEffectWrapper.getOrCreateRightEffect()
- needsInvalidate = drawRight(rightEffect, canvas) ||
- needsInvalidate
- if (edgeEffectWrapper.isRightStretched()) {
- edgeEffectWrapper
- .getOrCreateRightEffectNegation()
- .onPullDistanceCompat(rightEffect.distanceCompat, 0f)
- }
- }
- if (edgeEffectWrapper.isBottomNegationStretched()) {
- val bottomEffectNegation = edgeEffectWrapper.getOrCreateBottomEffectNegation()
- drawTop(bottomEffectNegation, canvas)
- bottomEffectNegation.finish()
- }
- if (edgeEffectWrapper.isBottomAnimating()) {
- val bottomEffect = edgeEffectWrapper.getOrCreateBottomEffect()
- needsInvalidate = drawBottom(bottomEffect, canvas) ||
- needsInvalidate
- if (edgeEffectWrapper.isBottomStretched()) {
- edgeEffectWrapper
- .getOrCreateBottomEffectNegation()
- .onPullDistanceCompat(bottomEffect.distanceCompat, 0f)
- }
- }
- if (needsInvalidate) invalidateOverscroll()
- }
- }
-
- private fun DrawScope.drawLeft(left: EdgeEffect, canvas: NativeCanvas): Boolean {
- val restore = canvas.save()
- canvas.rotate(270f)
- canvas.translate(
- -containerSize.height,
- overscrollConfig.drawPadding.calculateLeftPadding(layoutDirection).toPx()
- )
- val needsInvalidate = left.draw(canvas)
- canvas.restoreToCount(restore)
- return needsInvalidate
- }
-
- private fun DrawScope.drawTop(top: EdgeEffect, canvas: NativeCanvas): Boolean {
- val restore = canvas.save()
- canvas.translate(0f, overscrollConfig.drawPadding.calculateTopPadding().toPx())
- val needsInvalidate = top.draw(canvas)
- canvas.restoreToCount(restore)
- return needsInvalidate
- }
-
- private fun DrawScope.drawRight(right: EdgeEffect, canvas: NativeCanvas): Boolean {
- val restore = canvas.save()
- val width = containerSize.width.roundToInt()
- val rightPadding = overscrollConfig.drawPadding.calculateRightPadding(layoutDirection)
- canvas.rotate(90f)
- canvas.translate(0f, -width.toFloat() + rightPadding.toPx())
- val needsInvalidate = right.draw(canvas)
- canvas.restoreToCount(restore)
- return needsInvalidate
- }
-
- private fun DrawScope.drawBottom(bottom: EdgeEffect, canvas: NativeCanvas): Boolean {
- val restore = canvas.save()
- canvas.rotate(180f)
- val bottomPadding = overscrollConfig.drawPadding.calculateBottomPadding().toPx()
- canvas.translate(-containerSize.width, -containerSize.height + bottomPadding)
- val needsInvalidate = bottom.draw(canvas)
- canvas.restoreToCount(restore)
- return needsInvalidate
- }
-
- private fun invalidateOverscroll() {
+ internal fun invalidateOverscroll() {
if (invalidationEnabled) {
redrawSignal.value = Unit
}
@@ -495,8 +716,8 @@
return needsInvalidation
}
- private fun pullTop(scroll: Offset, displacement: Offset): Float {
- val displacementX: Float = displacement.x / containerSize.width
+ private fun pullTop(scroll: Offset): Float {
+ val displacementX = displacement().x
val pullY = scroll.y / containerSize.height
val topEffect = edgeEffectWrapper.getOrCreateTopEffect()
val consumed = topEffect.onPullDistanceCompat(pullY, displacementX) * containerSize.height
@@ -509,8 +730,8 @@
}
}
- private fun pullBottom(scroll: Offset, displacement: Offset): Float {
- val displacementX: Float = displacement.x / containerSize.width
+ private fun pullBottom(scroll: Offset): Float {
+ val displacementX = displacement().x
val pullY = scroll.y / containerSize.height
val bottomEffect = edgeEffectWrapper.getOrCreateBottomEffect()
val consumed = -bottomEffect.onPullDistanceCompat(
@@ -526,8 +747,8 @@
}
}
- private fun pullLeft(scroll: Offset, displacement: Offset): Float {
- val displacementY: Float = displacement.y / containerSize.height
+ private fun pullLeft(scroll: Offset): Float {
+ val displacementY = displacement().y
val pullX = scroll.x / containerSize.width
val leftEffect = edgeEffectWrapper.getOrCreateLeftEffect()
val consumed = leftEffect.onPullDistanceCompat(
@@ -543,8 +764,8 @@
}
}
- private fun pullRight(scroll: Offset, displacement: Offset): Float {
- val displacementY: Float = displacement.y / containerSize.height
+ private fun pullRight(scroll: Offset): Float {
+ val displacementY = displacement().y
val pullX = scroll.x / containerSize.width
val rightEffect = edgeEffectWrapper.getOrCreateRightEffect()
val consumed = -rightEffect.onPullDistanceCompat(
@@ -574,9 +795,8 @@
private var leftEffect: EdgeEffect? = null
private var rightEffect: EdgeEffect? = null
- // hack explanation: those edge effects are used to negate the previous effect
- // of the corresponding edge
- // used to mimic the render node reset that is not available in the platform
+ // These are used to negate the previous stretch, since RenderNode#clearStretch() is not public
+ // API. See DrawStretchOverscrollModifier for more information.
private var topEffectNegation: EdgeEffect? = null
private var bottomEffectNegation: EdgeEffect? = null
private var leftEffectNegation: EdgeEffect? = null
@@ -652,58 +872,3 @@
rightEffectNegation?.setSize(size.height, size.width)
}
}
-
-/**
- * There is an unwanted behavior in the stretch overscroll effect we have to workaround:
- * When the effect is started it is getting the current RenderNode bounds and clips the content
- * by those bounds. Even if this RenderNode is not configured to do clipping. Or if it clips,
- * but not within its bounds, but by the outline provided which could have a completely different
- * bounds. That is what happens with our scrolling containers - they all clip by the rect which is
- * larger than the RenderNode bounds in order to not clip the shadows drawn in the cross axis of
- * the scrolling direction. This issue is not that visible in the Views world because Views do
- * clip by default. So adding one more clip doesn't change much. Thus why the whole shadows
- * mechanism in the Views world works differently, the shadows are drawn not in-place, but with
- * the background of the first parent which has a background.
- * In order to neutralize this unnecessary clipping we can use similar technique to what we
- * use in those scrolling container clipping by extending the layer size on some predefined
- * [MaxSupportedElevation] constant. In this case we have to solve that with two layout modifiers:
- * 1) the inner one will measure its measurable as previously, but report to the parent modifier
- * with added extra size.
- * 2) the outer modifier will position its measurable with the layer, so the layer size is
- * increased, and then report the measured size of its measurable without the added extra size.
- * With such approach everything is measured and positioned as before, but we introduced an
- * extra layer with the incremented size, which will be used by the overscroll effect and allows
- * to draw the content without clipping the shadows.
- */
-private val StretchOverscrollNonClippingLayer: Modifier =
- // we only need to fix the layer size when the stretch overscroll is active (Android 12+)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- Modifier
- .layout { measurable, constraints ->
- val placeable = measurable.measure(constraints)
- val extraSizePx = (MaxSupportedElevation * 2).roundToPx()
- layout(
- (placeable.measuredWidth - extraSizePx).coerceAtLeast(0),
- (placeable.measuredHeight - extraSizePx).coerceAtLeast(0)
- ) {
- // because this modifier report the size which is larger than the passed max
- // constraints this larger box will be automatically centered within the
- // constraints. we need to first add out offset and then neutralize the centering.
- placeable.placeWithLayer(
- -extraSizePx / 2 - (placeable.width - placeable.measuredWidth) / 2,
- -extraSizePx / 2 - (placeable.height - placeable.measuredHeight) / 2
- )
- }
- }
- .layout { measurable, constraints ->
- val placeable = measurable.measure(constraints)
- val extraSizePx = (MaxSupportedElevation * 2).roundToPx()
- val width = placeable.width + extraSizePx
- val height = placeable.height + extraSizePx
- layout(width, height) {
- placeable.place(extraSizePx / 2, extraSizePx / 2)
- }
- }
- } else {
- Modifier
- }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt
index 0d57e2b..568a377 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt
@@ -28,13 +28,11 @@
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.nativeKeyCode
import androidx.compose.ui.input.key.type
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.requireView
-internal actual fun CompositionLocalConsumerModifierNode
- .isComposeRootInScrollableContainer(): Boolean {
- return currentValueOf(LocalView).isInScrollableViewGroup()
+internal actual fun DelegatableNode.isComposeRootInScrollableContainer(): Boolean {
+ return requireView().isInScrollableViewGroup()
}
private fun View.isInScrollableViewGroup(): Boolean {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
index d004fc1..59e1459 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.android.kt
@@ -30,17 +30,16 @@
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionInRoot
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.SemanticsModifierNode
-import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.node.observeReads
+import androidx.compose.ui.node.requireDensity
+import androidx.compose.ui.node.requireView
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.unit.Density
@@ -253,7 +252,6 @@
var platformMagnifierFactory: PlatformMagnifierFactory =
PlatformMagnifierFactory.getForCurrentPlatform()
) : Modifier.Node(),
- CompositionLocalConsumerModifierNode,
GlobalPositionAwareModifierNode,
DrawModifierNode,
SemanticsModifierNode,
@@ -352,9 +350,9 @@
override fun onObservedReadsChanged() {
observeReads {
val previousView = view
- val view = currentValueOf(LocalView).also { this.view = it }
+ val view = requireView().also { this.view = it }
val previousDensity = density
- val density = currentValueOf(LocalDensity).also { this.density = it }
+ val density = requireDensity().also { this.density = it }
if (magnifier == null || view != previousView || density != previousDensity) {
recreateMagnifier()
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/OverscrollConfiguration.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/OverscrollConfiguration.android.kt
index 59ae137..ea3357d 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/OverscrollConfiguration.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/OverscrollConfiguration.android.kt
@@ -24,15 +24,13 @@
/**
* Metadata for overscroll effects for android platform.
*
- * Note: this API is experimental and liable to be changed / removed. [glowColor] only applies
- * before Android S and so might be misleading to have in a generic configuration object, and
- * [drawPadding] may be moved to a different part of the API surface / removed depending on changes
- * to [OverscrollEffect].
+ * Note: this API is experimental and liable to be changed / removed. [glowColor] and [drawPadding]
+ * only apply before Android S and so might be misleading to have in a generic configuration object.
*
* @param glowColor color for the glow effect, if the platform effect is a glow effect, otherwise
* ignored.
* @param drawPadding the amount of padding to apply from scrollable container bounds to
- * effect before drawing it
+ * the effect before drawing it, if the platform effect is a glow effect, otherwise ignored.
*/
@ExperimentalFoundationApi
@Stable
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/RectListNode.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/RectListNode.android.kt
index 64ae204..8c8e16f 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/RectListNode.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/RectListNode.android.kt
@@ -23,19 +23,17 @@
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.findRootCoordinates
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
-import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.node.requireView
import kotlin.math.roundToInt
internal abstract class RectListNode(
open var rect: ((LayoutCoordinates) -> Rect)?
-) : Modifier.Node(), GlobalPositionAwareModifierNode, CompositionLocalConsumerModifierNode {
+) : Modifier.Node(), GlobalPositionAwareModifierNode {
private var androidRect: android.graphics.Rect? = null
protected val view: View
- get() = currentValueOf(LocalView)
+ get() = requireView()
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
val newRect = if (rect == null) {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.android.kt
index cf801bf..caa9eb2 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.android.kt
@@ -24,19 +24,18 @@
import android.view.View
import androidx.compose.ui.draganddrop.DragAndDropEvent
import androidx.compose.ui.draganddrop.toAndroidDragEvent
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.requireView
import androidx.core.view.DragAndDropPermissionsCompat
-internal actual fun CompositionLocalConsumerModifierNode.dragAndDropRequestPermission(
+internal actual fun DelegatableNode.dragAndDropRequestPermission(
event: DragAndDropEvent
) {
if (Build.VERSION.SDK_INT < 24) return
// If there is no contentUri, there's no need to request permissions
if (!event.toAndroidDragEvent().clipData.containsContentUri()) return
if (node.isAttached) {
- val view = currentValueOf(LocalView)
+ val view = requireView()
val activity = tryGetActivity(view) ?: return
DragAndDropPermissionsCompat.request(activity, event.toAndroidDragEvent())
}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.android.kt
index 63e7631..838c8cec 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.android.kt
@@ -25,8 +25,8 @@
import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.util.trace
import java.util.concurrent.TimeUnit
+import kotlin.math.max
@ExperimentalFoundationApi
@Composable
@@ -101,18 +101,8 @@
* The list of currently not processed prefetch requests. The requests will be processed one by
* during subsequent [run]s.
*/
- private val prefetchRequests = mutableVectorOf<PrefetchExecutor.Request>()
-
- /**
- * Average time the prefetching operations takes. Keeping it allows us to not start the work
- * if in this frame we are most likely not going to finish the work in time to not delay the
- * next frame.
- */
- private var averagePrecomposeTimeNs: Long = 0
- private var averagePremeasureTimeNs: Long = 0
-
+ private val prefetchRequests = mutableVectorOf<PrefetchRequest>()
private var prefetchScheduled = false
-
private val choreographer = Choreographer.getInstance()
/** Is true when LazyList was composed and not yet disposed. */
@@ -136,52 +126,16 @@
}
val latestFrameVsyncNs = TimeUnit.MILLISECONDS.toNanos(view.drawingTime)
val nextFrameNs = latestFrameVsyncNs + frameIntervalNs
- var oneOverTimeTaskAllowed = System.nanoTime() > nextFrameNs
+ val oneOverTimeTaskAllowed = System.nanoTime() > nextFrameNs
+ val scope = PrefetchRequestScopeImpl(nextFrameNs, oneOverTimeTaskAllowed)
var scheduleForNextFrame = false
while (prefetchRequests.isNotEmpty() && !scheduleForNextFrame) {
val request = prefetchRequests[0]
- if (!request.isValid) {
- prefetchRequests.removeAt(0)
- } else if (!request.isComposed) {
- val beforeTimeNs = System.nanoTime()
- // check if there is enough time left in this frame. otherwise, we schedule
- // a next frame callback in which we will post the message in the handler again.
- if (enoughTimeLeft(
- beforeTimeNs,
- nextFrameNs,
- averagePrecomposeTimeNs
- ) || oneOverTimeTaskAllowed
- ) {
- trace("compose:lazylist:prefetch:compose") {
- oneOverTimeTaskAllowed = false
- request.performComposition()
- averagePrecomposeTimeNs = calculateAverageTime(
- System.nanoTime() - beforeTimeNs, averagePrecomposeTimeNs
- )
- }
- } else {
- scheduleForNextFrame = true
- }
+ val hasMoreWorkToDo = with(request) { scope.execute() }
+ if (hasMoreWorkToDo) {
+ scheduleForNextFrame = true
} else {
- val beforeTimeNs = System.nanoTime()
- if (enoughTimeLeft(
- beforeTimeNs,
- nextFrameNs,
- averagePremeasureTimeNs
- ) || oneOverTimeTaskAllowed
- ) {
- trace("compose:lazylist:prefetch:measure") {
- oneOverTimeTaskAllowed = false
- request.performMeasure()
- averagePremeasureTimeNs = calculateAverageTime(
- System.nanoTime() - beforeTimeNs, averagePremeasureTimeNs
- )
- // we finished this request
- prefetchRequests.removeAt(0)
- }
- } else {
- scheduleForNextFrame = true
- }
+ prefetchRequests.removeAt(0)
}
}
@@ -194,9 +148,6 @@
}
}
- private fun enoughTimeLeft(now: Long, nextFrame: Long, average: Long) =
- now + average < nextFrame
-
/**
* Choreographer frame callback. It will be called when during the previous frame we didn't
* have enough time left. We will post a new message in the handler in order to try to
@@ -208,20 +159,8 @@
}
}
- private fun calculateAverageTime(new: Long, current: Long): Long {
- // Calculate a weighted moving average of time taken to compose an item. We use weighted
- // moving average to bias toward more recent measurements, and to minimize storage /
- // computation cost. (the idea is taken from RecycledViewPool)
- return if (current == 0L) {
- new
- } else {
- // dividing first to avoid a potential overflow
- current / 4 * 3 + new / 4
- }
- }
-
- override fun requestPrefetch(request: PrefetchExecutor.Request) {
- prefetchRequests.add(request)
+ override fun requestPrefetch(prefetchRequest: PrefetchRequest) {
+ prefetchRequests.add(prefetchRequest)
if (!prefetchScheduled) {
prefetchScheduled = true
// schedule the prefetching
@@ -241,6 +180,28 @@
override fun onAbandoned() {}
+ class PrefetchRequestScopeImpl(
+ private val nextFrameTimeNs: Long,
+ isOneOverTimeTaskAllowed: Boolean
+ ) : PrefetchRequestScope {
+
+ private var canDoOverTimeTask = isOneOverTimeTaskAllowed
+
+ override val availableTimeNanos: Long
+ get() {
+ // This logic is meant to be temporary until we replace the isOneOverTimeTaskAllowed
+ // logic with something more general. For now, we assume that a PrefetchRequest
+ // impl will check availableTimeNanos once per task and we give it a large amount
+ // of time the first time it checks if we allow an overtime task.
+ return if (canDoOverTimeTask) {
+ canDoOverTimeTask = false
+ Long.MAX_VALUE
+ } else {
+ max(0, nextFrameTimeNs - System.nanoTime())
+ }
+ }
+ }
+
companion object {
/**
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.android.kt
index aa155b3..56eca4f 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.android.kt
@@ -19,14 +19,12 @@
import android.graphics.Rect as AndroidRect
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.layout.positionInRoot
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.requireView
-internal actual fun CompositionLocalConsumerModifierNode.defaultBringIntoViewParent():
- BringIntoViewParent =
+internal actual fun DelegatableNode.defaultBringIntoViewParent(): BringIntoViewParent =
BringIntoViewParent { childCoordinates, boundsProvider ->
- val view = currentValueOf(LocalView)
+ val view = requireView()
val childOffset = childCoordinates.positionInRoot()
val rootRect = boundsProvider()?.translate(childOffset)
if (rootRect != null) {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
index 89261da..554b743 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
@@ -66,22 +66,27 @@
): Nothing {
coroutineScope {
launch(start = CoroutineStart.UNDISPATCHED) {
- state.collectImeNotifications { old, new ->
- val needUpdateSelection =
- (old.selectionInChars != new.selectionInChars) ||
- old.compositionInChars != new.compositionInChars
- if (needUpdateSelection) {
+ state.collectImeNotifications { oldValue, newValue, restartImeIfContentChanges ->
+ val oldSelection = oldValue.selectionInChars
+ val newSelection = newValue.selectionInChars
+ val oldComposition = oldValue.compositionInChars
+ val newComposition = newValue.compositionInChars
+
+ if ((oldSelection != newSelection) || oldComposition != newComposition) {
composeImm.updateSelection(
- selectionStart = new.selectionInChars.min,
- selectionEnd = new.selectionInChars.max,
- compositionStart = new.compositionInChars?.min ?: -1,
- compositionEnd = new.compositionInChars?.max ?: -1
+ selectionStart = newSelection.min,
+ selectionEnd = newSelection.max,
+ compositionStart = oldComposition?.min ?: -1,
+ compositionEnd = oldComposition?.max ?: -1
)
}
// No need to restart the IME if keyboard type is configured as Password. IME
// should not keep an internal input state if the content needs to be secured.
- if (!old.contentEquals(new) && imeOptions.keyboardType != KeyboardType.Password) {
+ if (restartImeIfContentChanges &&
+ !oldValue.contentEquals(newValue) &&
+ imeOptions.keyboardType != KeyboardType.Password
+ ) {
composeImm.restartInput()
}
}
@@ -101,12 +106,9 @@
override val text: TextFieldCharSequence
get() = state.visualText
- override fun requestEdit(
- notifyImeOfChanges: Boolean,
- block: EditingBuffer.() -> Unit
- ) {
+ override fun requestEdit(block: EditingBuffer.() -> Unit) {
state.editUntransformedTextAsUser(
- notifyImeOfChanges = notifyImeOfChanges,
+ restartImeIfContentChanges = false,
block = block
)
}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt
index c92e5e8..a37be5d2 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/EditorInfo.android.kt
@@ -16,13 +16,17 @@
package androidx.compose.foundation.text.input.internal
+import android.os.Build
import android.text.InputType
import android.view.inputmethod.EditorInfo
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.intl.LocaleList
import androidx.core.view.inputmethod.EditorInfoCompat
/**
@@ -60,6 +64,10 @@
privateImeOptions = it
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ LocaleListHelper.setHintLocales(this, imeOptions.hintLocales)
+ }
+
this.inputType = when (imeOptions.keyboardType) {
KeyboardType.Text -> InputType.TYPE_CLASS_TEXT
KeyboardType.Ascii -> {
@@ -147,3 +155,23 @@
}
private fun hasFlag(bits: Int, flag: Int): Boolean = (bits and flag) == flag
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be
+ * AOT compiled. It is expected that this class will soft-fail verification, but the classes
+ * which use this method will pass.
+ */
+@RequiresApi(24)
+internal object LocaleListHelper {
+ @RequiresApi(24)
+ @DoNotInline
+ fun setHintLocales(editorInfo: EditorInfo, localeList: LocaleList?) {
+ if (localeList == null) {
+ editorInfo.hintLocales = null
+ return
+ }
+ editorInfo.hintLocales = android.os.LocaleList(
+ *localeList.map { it.platformLocale }.toTypedArray()
+ )
+ }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnection.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnection.android.kt
index 4228aa1..e1d91c8 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnection.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/StatelessInputConnection.android.kt
@@ -358,8 +358,7 @@
logDebug("performContextMenuAction($id)")
when (id) {
android.R.id.selectAll -> {
- // no need to batch context menu actions.
- session.requestEdit(notifyImeOfChanges = true) {
+ addEditCommandWithBatch {
setSelection(0, text.length)
}
}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.android.kt
index 3616297..ce9868f 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.android.kt
@@ -41,13 +41,9 @@
/**
* Callback to execute for InputConnection to communicate the changes requested by the IME.
*
- * @param notifyImeOfChanges Normally any request coming from IME should not be
- * back-communicated but [InputConnection.performContextMenuAction] does not behave like a
- * regular IME command. Its changes must be resent to IME to keep it in sync with
- * [TextFieldState].
* @param block Lambda scoped to an EditingBuffer to apply changes direct onto a buffer.
*/
- fun requestEdit(notifyImeOfChanges: Boolean = false, block: EditingBuffer.() -> Unit)
+ fun requestEdit(block: EditingBuffer.() -> Unit)
/**
* Delegates IME requested KeyEvents.
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/AllCapsTransformationTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/AllCapsTransformationTest.kt
index 84ec250..f94ada6 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/AllCapsTransformationTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/AllCapsTransformationTest.kt
@@ -37,7 +37,7 @@
@Test
fun allNewTypedCharacters_convertedToUppercase() {
- val transformation = InputTransformation.allCaps(Locale("en_US"))
+ val transformation = InputTransformation.allCaps(Locale("en-US"))
val originalValue = TextFieldCharSequence("")
val buffer = TextFieldBuffer(originalValue).apply {
@@ -51,7 +51,7 @@
@Test
fun oldCharacters_areNotConverted() {
- val transformation = InputTransformation.allCaps(Locale("en_US"))
+ val transformation = InputTransformation.allCaps(Locale("en-US"))
val originalValue = TextFieldCharSequence("hello")
val buffer = TextFieldBuffer(originalValue).apply {
@@ -79,7 +79,7 @@
@Test
fun multipleEdits() {
- val transformation = InputTransformation.allCaps(Locale("en_US"))
+ val transformation = InputTransformation.allCaps(Locale("en-US"))
var originalValue = TextFieldCharSequence("hello")
var buffer = TextFieldBuffer(originalValue)
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
index 5ccc7ab..78463c7 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
@@ -45,7 +45,10 @@
val state = TextFieldState(firstValue)
var resetCalled = 0
- state.addNotifyImeListener { _, _ -> resetCalled++ }
+ var selectionCalled = 0
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
state.editAsUser { commitText("X", 1) }
val newState = state.text
@@ -55,6 +58,7 @@
assertThat(newState.selectionInChars.max).isEqualTo(1)
// edit command updates should not trigger reset listeners.
assertThat(resetCalled).isEqualTo(0)
+ assertThat(selectionCalled).isEqualTo(1)
}
@Test
@@ -63,7 +67,10 @@
val state = TextFieldState(firstValue)
var resetCalled = 0
- state.addNotifyImeListener { _, _ -> resetCalled++ }
+ var selectionCalled = 0
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
state.editAsUser { setSelection(0, 2) }
val newState = state.text
@@ -73,13 +80,18 @@
assertThat(newState.selectionInChars.max).isEqualTo(2)
// edit command updates should not trigger reset listeners.
assertThat(resetCalled).isEqualTo(0)
+ assertThat(selectionCalled).isEqualTo(1)
}
@Test
fun testNewState_bufferNotUpdated_ifSameModelStructurally() {
val state = TextFieldState()
+
var resetCalled = 0
- state.addNotifyImeListener { _, _ -> resetCalled++ }
+ var selectionCalled = 0
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
val initialBuffer = state.mainBuffer
state.resetStateAndNotifyIme(
@@ -94,13 +106,18 @@
assertThat(state.mainBuffer).isSameInstanceAs(updatedBuffer)
assertThat(resetCalled).isEqualTo(2)
+ assertThat(selectionCalled).isEqualTo(0)
}
@Test
fun testNewState_new_buffer_created_if_text_is_different() {
val state = TextFieldState()
+
var resetCalled = 0
- state.addNotifyImeListener { _, _ -> resetCalled++ }
+ var selectionCalled = 0
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
state.resetStateAndNotifyIme(textFieldValue)
@@ -111,13 +128,18 @@
assertThat(state.mainBuffer).isNotSameInstanceAs(initialBuffer)
assertThat(resetCalled).isEqualTo(2)
+ assertThat(selectionCalled).isEqualTo(0)
}
@Test
fun testNewState_buffer_not_recreated_if_selection_is_different() {
val state = TextFieldState()
+
var resetCalled = 0
- state.addNotifyImeListener { _, _ -> resetCalled++ }
+ var selectionCalled = 0
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
state.resetStateAndNotifyIme(textFieldValue)
@@ -133,13 +155,18 @@
state.mainBuffer.selectionEnd
)
assertThat(resetCalled).isEqualTo(2)
+ assertThat(selectionCalled).isEqualTo(0)
}
@Test
fun testNewState_buffer_not_recreated_if_composition_is_different() {
val state = TextFieldState()
+
var resetCalled = 0
- state.addNotifyImeListener { _, _ -> resetCalled++ }
+ var selectionCalled = 0
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange(1))
state.resetStateAndNotifyIme(textFieldValue)
@@ -160,6 +187,7 @@
assertThat(EditingBuffer.NOWHERE).isEqualTo(state.mainBuffer.compositionStart)
assertThat(EditingBuffer.NOWHERE).isEqualTo(state.mainBuffer.compositionEnd)
assertThat(resetCalled).isEqualTo(2)
+ assertThat(selectionCalled).isEqualTo(0)
}
@Test
@@ -167,8 +195,12 @@
val initialSelection = TextRange(2, 1)
val textFieldValue = TextFieldCharSequence("qwerty", initialSelection, TextRange(1))
val state = TextFieldState(textFieldValue)
+
var resetCalled = 0
- state.addNotifyImeListener { _, _ -> resetCalled++ }
+ var selectionCalled = 0
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
val initialBuffer = state.mainBuffer
@@ -184,19 +216,26 @@
assertThat(updatedSelection.start).isEqualTo(initialBuffer.selectionStart)
assertThat(updatedSelection.end).isEqualTo(initialBuffer.selectionEnd)
assertThat(resetCalled).isEqualTo(1)
+ assertThat(selectionCalled).isEqualTo(0)
}
@Test
fun compositionIsCleared_when_textChanged() {
val state = TextFieldState()
+
var resetCalled = 0
- state.addNotifyImeListener { _, _ -> resetCalled++ }
+ var selectionCalled = 0
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
// set the initial value
state.editAsUser {
commitText("ab", 0)
setComposingRegion(0, 2)
}
+ assertThat(resetCalled).isEqualTo(0)
+ assertThat(selectionCalled).isEqualTo(1)
// change the text
val newValue =
@@ -209,6 +248,8 @@
assertThat(state.text.toString()).isEqualTo(newValue.toString())
assertThat(state.text.compositionInChars).isNull()
+ assertThat(resetCalled).isEqualTo(1)
+ assertThat(selectionCalled).isEqualTo(1)
}
@Test
@@ -360,7 +401,7 @@
var resetCalledOld: TextFieldCharSequence? = null
var resetCalledNew: TextFieldCharSequence? = null
- state.addNotifyImeListener { old, new ->
+ state.addImeContentListener { old, new, _ ->
resetCalledOld = old
resetCalledNew = new
}
@@ -369,7 +410,7 @@
state.editAsUser(
inputTransformation = { _, new -> new.revertAllChanges() },
- notifyImeOfChanges = false
+ restartImeIfContentChanges = false
) {
commitText("d", 4)
}
@@ -391,7 +432,7 @@
fail("filter ran, old=\"$old\", new=\"$new\"")
}
- state.editAsUser(inputTransformation, notifyImeOfChanges = false) {}
+ state.editAsUser(inputTransformation, restartImeIfContentChanges = false) {}
}
@Test
@@ -405,7 +446,7 @@
state.editAsUser(
inputTransformation = inputTransformation,
- notifyImeOfChanges = false
+ restartImeIfContentChanges = false
) { finishComposingText() }
}
@@ -420,7 +461,7 @@
state.editAsUser(
inputTransformation = inputTransformation,
- notifyImeOfChanges = false
+ restartImeIfContentChanges = false
) { finishComposingText() }
}
@@ -436,7 +477,10 @@
)
}
- state.editAsUser(inputTransformation = inputTransformation, notifyImeOfChanges = false) {
+ state.editAsUser(
+ inputTransformation = inputTransformation,
+ restartImeIfContentChanges = false
+ ) {
setComposingRegion(0, 5)
commitText("hello", 1)
setSelection(2, 2)
@@ -460,7 +504,7 @@
state.editAsUser(
inputTransformation = inputTransformation,
- notifyImeOfChanges = false
+ restartImeIfContentChanges = false
) { setSelection(0, 5) }
}
@@ -479,7 +523,10 @@
assertThat(new.toString()).isEqualTo("world")
}
- state.editAsUser(inputTransformation = inputTransformation, notifyImeOfChanges = false) {
+ state.editAsUser(
+ inputTransformation = inputTransformation,
+ restartImeIfContentChanges = false
+ ) {
deleteAll()
commitText("world", 1)
setSelection(2, 2)
@@ -513,6 +560,18 @@
) = TextFieldState(value.toString(), value.selectionInChars)
private fun TextFieldState.editAsUser(block: EditingBuffer.() -> Unit) {
- editAsUser(inputTransformation = null, notifyImeOfChanges = false, block = block)
+ editAsUser(inputTransformation = null, restartImeIfContentChanges = false, block = block)
+ }
+
+ private fun TextFieldState.addImeContentListener(
+ listener: (TextFieldCharSequence, TextFieldCharSequence, Boolean) -> Unit
+ ) {
+ addNotifyImeListener { oldValue, newValue, restartImeIfContentChanges ->
+ listener(
+ oldValue,
+ newValue,
+ restartImeIfContentChanges
+ )
+ }
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 1fabc9c..3adfa54 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -42,7 +42,6 @@
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
import androidx.compose.ui.modifier.ModifierLocalModifierNode
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
@@ -402,8 +401,7 @@
* within a scrollable Compose layout, to calculate whether this modifier is within some form of
* scrollable container, and hence should delay presses.
*/
-internal expect fun CompositionLocalConsumerModifierNode
- .isComposeRootInScrollableContainer(): Boolean
+internal expect fun DelegatableNode.isComposeRootInScrollableContainer(): Boolean
/**
* Whether the specified [KeyEvent] should trigger a press for a clickable component.
@@ -878,7 +876,7 @@
private var role: Role?,
onClick: () -> Unit
) : DelegatingNode(), PointerInputModifierNode, KeyInputModifierNode, FocusEventModifierNode,
- SemanticsModifierNode, CompositionLocalConsumerModifierNode, ModifierLocalModifierNode {
+ SemanticsModifierNode, ModifierLocalModifierNode {
protected var enabled = enabled
private set
protected var onClick = onClick
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
index cd801d5..5e32fa2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
@@ -19,8 +19,7 @@
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.relocation.BringIntoViewRequester
-import androidx.compose.foundation.relocation.BringIntoViewRequesterNode
+import androidx.compose.foundation.relocation.scrollIntoView
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusEventModifierNode
@@ -183,7 +182,6 @@
}
}
-@OptIn(ExperimentalFoundationApi::class)
internal class FocusableNode(
interactionSource: MutableInteractionSource?
) : DelegatingNode(), FocusEventModifierNode, SemanticsModifierNode,
@@ -207,13 +205,6 @@
// 3. Entire window is panned due to `softInputMode=ADJUST_PAN` – report the correct focused
// rect to the view system, and the view system itself will keep the focused area in view.
// See aosp/1964580.
- private val bringIntoViewRequester = BringIntoViewRequester()
-
- /** This is just needed for the delegate, it's not referenced anywhere directly. */
- @Suppress("unused")
- private val bringIntoViewRequesterNode = delegate(
- BringIntoViewRequesterNode(bringIntoViewRequester)
- )
fun update(interactionSource: MutableInteractionSource?) =
focusableInteractionNode.update(interactionSource)
@@ -224,7 +215,7 @@
val isFocused = focusState.isFocused
if (isFocused) {
coroutineScope.launch {
- bringIntoViewRequester.bringIntoView()
+ scrollIntoView()
}
}
if (isAttached) invalidateSemantics()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
index f40719c..7aeaf11 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
@@ -50,6 +50,7 @@
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.ScrollAxisRange
+import androidx.compose.ui.semantics.getScrollViewportLength
import androidx.compose.ui.semantics.horizontalScrollAxisRange
import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.scrollBy
@@ -295,6 +296,11 @@
return@scrollBy true
}
)
+
+ getScrollViewportLength {
+ it.add(state.viewportSize.toFloat())
+ true
+ }
}
}
.scrollingContainer(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.kt
index 4eaf3ce..14c8b35 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.kt
@@ -17,11 +17,11 @@
package androidx.compose.foundation.content.internal
import androidx.compose.ui.draganddrop.DragAndDropEvent
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatableNode
/**
* Requests necessary platform permissions to read the content that's delivered by [event].
*/
-internal expect fun CompositionLocalConsumerModifierNode.dragAndDropRequestPermission(
+internal expect fun DelegatableNode.dragAndDropRequestPermission(
event: DragAndDropEvent
)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyLayoutSemanticState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyLayoutSemanticState.kt
index f668923..4b4394b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyLayoutSemanticState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyLayoutSemanticState.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.lazy
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.lazy.layout.LazyLayoutSemanticState
import androidx.compose.ui.semantics.CollectionInfo
@@ -46,4 +47,13 @@
} else {
CollectionInfo(rowCount = 1, columnCount = -1)
}
+
+ override val viewport: Int
+ get() = if (state.layoutInfo.orientation == Orientation.Vertical) {
+ state.layoutInfo.viewportSize.height
+ } else {
+ state.layoutInfo.viewportSize.width
+ }
+ override val contentPadding: Int
+ get() = state.layoutInfo.beforeContentPadding + state.layoutInfo.afterContentPadding
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt
index e5746d4..834e3da 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.lazy.grid
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.lazy.layout.LazyLayoutSemanticState
import androidx.compose.runtime.Composable
@@ -49,5 +50,14 @@
// TODO(popam): check if this is correct - it would be nice to provide correct columns
override fun collectionInfo(): CollectionInfo =
CollectionInfo(rowCount = -1, columnCount = -1)
+
+ override val viewport: Int
+ get() = if (state.layoutInfo.orientation == Orientation.Vertical) {
+ state.layoutInfo.viewportSize.height
+ } else {
+ state.layoutInfo.viewportSize.width
+ }
+ override val contentPadding: Int
+ get() = state.layoutInfo.beforeContentPadding + state.layoutInfo.afterContentPadding
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
index eac40e5..619c8e4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
@@ -21,6 +21,8 @@
import androidx.compose.runtime.Stable
import androidx.compose.ui.layout.SubcomposeLayoutState
import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.util.trace
+import kotlin.system.measureNanoTime
/**
* State for lazy items prefetching, used by lazy layouts to instruct the prefetcher.
@@ -35,6 +37,8 @@
@ExperimentalFoundationApi
@Stable
class LazyLayoutPrefetchState(internal val prefetchExecutor: PrefetchExecutor? = null) {
+
+ private val prefetchMetrics: PrefetchMetrics = PrefetchMetrics()
internal var prefetchHandleProvider: PrefetchHandleProvider? = null
/**
@@ -44,7 +48,8 @@
* @param constraints [Constraints] to use for premeasuring.
*/
fun schedulePrefetch(index: Int, constraints: Constraints): PrefetchHandle {
- return prefetchHandleProvider?.schedulePrefetch(index, constraints) ?: DummyHandle
+ return prefetchHandleProvider?.schedulePrefetch(index, constraints, prefetchMetrics)
+ ?: DummyHandle
}
sealed interface PrefetchHandle {
@@ -56,6 +61,57 @@
}
}
+/**
+ * [PrefetchMetrics] tracks composition and measure timings for subcompositions so that they can be
+ * used to estimate whether we can fit prefetch work into idle time without delaying the start of
+ * the next frame.
+ */
+@ExperimentalFoundationApi
+internal class PrefetchMetrics {
+
+ /**
+ * The current average time composition has taken during prefetches of this LazyLayout.
+ */
+ var averageCompositionTimeNanos: Long = 0L
+ private set
+
+ /**
+ * The current average time measure has taken during prefetches of this LazyLayout.
+ */
+ var averageMeasureTimeNanos: Long = 0L
+ private set
+
+ /**
+ * Executes the [doComposition] block and updates [averageCompositionTimeNanos] with the new
+ * average.
+ */
+ internal inline fun recordCompositionTiming(doComposition: () -> Unit) {
+ val executionTime = measureNanoTime(doComposition)
+ averageCompositionTimeNanos =
+ calculateAverageTime(executionTime, averageCompositionTimeNanos)
+ }
+
+ /**
+ * Executes the [doMeasure] block and updates [averageMeasureTimeNanos] with the new average.
+ */
+ internal inline fun recordMeasureTiming(doMeasure: () -> Unit) {
+ val executionTime = measureNanoTime(doMeasure)
+ averageMeasureTimeNanos = calculateAverageTime(executionTime, averageMeasureTimeNanos)
+ }
+
+ private fun calculateAverageTime(new: Long, current: Long): Long {
+ // Calculate a weighted moving average of time taken to compose an item. We use weighted
+ // moving average to bias toward more recent measurements, and to minimize storage /
+ // computation cost. (the idea is taken from RecycledViewPool)
+ return if (current == 0L) {
+ new
+ } else {
+ // dividing first to avoid a potential overflow
+ current / 4 * 3 + new / 4
+ }
+ }
+}
+
@ExperimentalFoundationApi
private object DummyHandle : PrefetchHandle {
override fun cancel() {}
@@ -73,27 +129,30 @@
private val subcomposeLayoutState: SubcomposeLayoutState,
private val executor: PrefetchExecutor
) {
- fun schedulePrefetch(index: Int, constraints: Constraints): PrefetchHandle =
- HandleAndRequestImpl(index, constraints).also {
+ fun schedulePrefetch(
+ index: Int,
+ constraints: Constraints,
+ prefetchMetrics: PrefetchMetrics
+ ): PrefetchHandle =
+ HandleAndRequestImpl(index, constraints, prefetchMetrics).also {
executor.requestPrefetch(it)
}
@ExperimentalFoundationApi
private inner class HandleAndRequestImpl(
private val index: Int,
- private val constraints: Constraints
- ) : PrefetchHandle, PrefetchExecutor.Request {
+ private val constraints: Constraints,
+ private val prefetchMetrics: PrefetchMetrics,
+ ) : PrefetchHandle, PrefetchRequest {
private var precomposeHandle: SubcomposeLayoutState.PrecomposedSlotHandle? = null
private var isMeasured = false
private var isCanceled = false
-
- override val isValid: Boolean
+ private val isComposed get() = precomposeHandle != null
+ private val isValid
get() = !isCanceled &&
index in 0 until itemContentFactory.itemProvider().itemCount
- override val isComposed get() = precomposeHandle != null
-
override fun cancel() {
if (!isCanceled) {
isCanceled = true
@@ -102,7 +161,40 @@
}
}
- override fun performComposition() {
+ override fun PrefetchRequestScope.execute(): Boolean {
+ if (!isValid) {
+ return false
+ }
+
+ if (!isComposed) {
+ if (prefetchMetrics.averageCompositionTimeNanos < availableTimeNanos) {
+ prefetchMetrics.recordCompositionTiming {
+ trace("compose:lazy:prefetch:compose") {
+ performComposition()
+ }
+ }
+ } else {
+ return true
+ }
+ }
+
+ if (!isMeasured) {
+ if (prefetchMetrics.averageMeasureTimeNanos < availableTimeNanos) {
+ prefetchMetrics.recordMeasureTiming {
+ trace("compose:lazy:prefetch:measure") {
+ performMeasure()
+ }
+ }
+ } else {
+ return true
+ }
+ }
+
+ // All our work is done
+ return false
+ }
+
+ private fun performComposition() {
require(isValid) {
"Callers should check whether the request is still valid before calling " +
"performComposition()"
@@ -115,7 +207,7 @@
precomposeHandle = subcomposeLayoutState.precompose(key, content)
}
- override fun performMeasure() {
+ private fun performMeasure() {
require(!isCanceled) {
"Callers should check whether the request is still valid before calling " +
"performMeasure()"
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
index cb8df26..13a097c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
@@ -24,6 +24,7 @@
import androidx.compose.ui.semantics.CollectionInfo
import androidx.compose.ui.semantics.ScrollAxisRange
import androidx.compose.ui.semantics.collectionInfo
+import androidx.compose.ui.semantics.getScrollViewportLength
import androidx.compose.ui.semantics.horizontalScrollAxisRange
import androidx.compose.ui.semantics.indexForKey
import androidx.compose.ui.semantics.isTraversalGroup
@@ -123,6 +124,11 @@
scrollToIndex(action = scrollToIndexAction)
}
+ getScrollViewportLength {
+ it.add((state.viewport - state.contentPadding).toFloat())
+ true
+ }
+
this.collectionInfo = collectionInfo
}
}
@@ -133,6 +139,9 @@
val firstVisibleItemScrollOffset: Int
val firstVisibleItemIndex: Int
val canScrollForward: Boolean
+ val viewport: Int
+ val contentPadding: Int
+
fun collectionInfo(): CollectionInfo
suspend fun animateScrollBy(delta: Float)
suspend fun scrollToItem(index: Int)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.kt
index 7e1b31e..b49b069 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.kt
@@ -30,8 +30,12 @@
/**
* Implementations of this interface accept prefetch requests via [requestPrefetch] and decide when
* to execute them in a way that will have minimal impact on user experience, e.g. during frame idle
- * time. Executing a request involves invoking [Request.performComposition] and
- * [Request.performMeasure].
+ * time.
+ *
+ * Requests should be executed by invoking [PrefetchRequest.execute]. The implementation of
+ * [PrefetchRequest.execute] will return `false` when all work for that request is done, or `true`
+ * when it still has more to do but doesn't think it can complete it within
+ * [PrefetchRequestScope.availableTimeNanos].
*/
@ExperimentalFoundationApi
interface PrefetchExecutor {
@@ -40,30 +44,36 @@
* Accepts a prefetch request. Implementations should find a time to execute them which will
* have minimal impact on user experience.
*/
- fun requestPrefetch(request: Request)
+ fun requestPrefetch(prefetchRequest: PrefetchRequest)
+}
- sealed interface Request {
+/**
+ * A request for prefetch which can be submitted to a [PrefetchExecutor] to execute during idle
+ * time.
+ */
+@ExperimentalFoundationApi
+sealed interface PrefetchRequest {
- /**
- * Whether this is still a valid request (wasn't canceled, within list bounds). If it's
- * not valid, it should be dropped and not executed.
- */
- val isValid: Boolean
+ /**
+ * Gives this request a chance to execute work. It should only do work if it thinks it can
+ * finish it within [PrefetchRequestScope.availableTimeNanos].
+ *
+ * @return whether this request has more work it wants to do, but ran out of time. `true`
+ * indicates this request wants to have [execute] called again to do more work, while `false`
+ * indicates its work is complete.
+ */
+ fun PrefetchRequestScope.execute(): Boolean
+}
- /**
- * Whether this request has been composed via [performComposition].
- */
- val isComposed: Boolean
+/**
+ * Scope for [PrefetchRequest.execute], supplying info about how much time it has to execute requests.
+ */
+@ExperimentalFoundationApi
+interface PrefetchRequestScope {
- /**
- * Composes the content belonging to this request.
- */
- fun performComposition()
-
- /**
- * Measures the Composition belonging to this request. Must be called after
- * [performComposition].
- */
- fun performMeasure()
- }
+ /**
+ * How much time is available to do prefetch work. Implementations of [PrefetchRequest] should
+ * do their best to fit their work into this time without going over.
+ */
+ val availableTimeNanos: Long
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemantics.kt
index 3554fe1..009f1e1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemantics.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.lazy.staggeredgrid
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.lazy.layout.LazyLayoutSemanticState
import androidx.compose.runtime.Composable
@@ -48,5 +49,14 @@
override fun collectionInfo(): CollectionInfo =
CollectionInfo(-1, -1)
+
+ override val viewport: Int
+ get() = if (state.layoutInfo.orientation == Orientation.Vertical) {
+ state.layoutInfo.viewportSize.height
+ } else {
+ state.layoutInfo.viewportSize.width
+ }
+ override val contentPadding: Int
+ get() = state.layoutInfo.beforeContentPadding + state.layoutInfo.afterContentPadding
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
index 97add2d..c81ea77 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
@@ -19,12 +19,12 @@
package androidx.compose.foundation.pager
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.spring
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.BringIntoViewSpec
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.gestures.TargetedFlingBehavior
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
@@ -61,7 +61,6 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastAll
-import kotlin.math.absoluteValue
import kotlin.math.roundToInt
import kotlinx.coroutines.coroutineScope
@@ -137,7 +136,13 @@
PagerWrapperFlingBehavior(flingBehavior, state)
}
- val pagerBringIntoViewSpec = remember(state) { PagerBringIntoViewSpec(state) }
+ val defaultBringIntoViewSpec = ScrollableDefaults.bringIntoViewSpec()
+ val pagerBringIntoViewSpec = remember(state, defaultBringIntoViewSpec) {
+ PagerBringIntoViewSpec(
+ state,
+ defaultBringIntoViewSpec
+ )
+ }
val coroutineScope = rememberCoroutineScope()
@@ -291,38 +296,72 @@
}
@OptIn(ExperimentalFoundationApi::class)
-private class PagerBringIntoViewSpec(val pagerState: PagerState) : BringIntoViewSpec {
+private class PagerBringIntoViewSpec(
+ val pagerState: PagerState,
+ val defaultBringIntoViewSpec: BringIntoViewSpec
+) : BringIntoViewSpec {
- override val scrollAnimationSpec: AnimationSpec<Float> = spring()
+ override val scrollAnimationSpec: AnimationSpec<Float> =
+ defaultBringIntoViewSpec.scrollAnimationSpec
/**
* [calculateScrollDistance] for Pager behaves differently than in a normal list. We must
* always respect the snapped pages over bringing a child into view. The logic here will
* behave like so:
*
- * 1) If a child is outside of the view, start bringing it into view.
- * 2) If a child's trailing edge is outside of the page bounds and the child is smaller than
- * the page, scroll until the trailing edge is in view.
- * 3) Once a child is fully in view, if it is smaller than the page, scroll until the page is
- * settled.
- * 4) If the child is larger than the page, scroll until it is partially in view and continue
- * scrolling until the page is settled.
+ * 1) If there's an ongoing request from the default bring into view spec, override the value
+ * to make it land on the closest page to the requested offset.
+ * 2) If there's no ongoing request it means that either we moved enough to fulfill the
+ * previously on going request or we didn't need move at all.
+ * 2a) If we didn't move at all we do nothing (pagerState.firstVisiblePageOffset == 0)
+ * 2b) If we fulfilled the default request, settle to the next page in the direction where
+ * we were scrolling before. We use firstVisiblePage as anchor, but the goal is to keep
+ * the pager snapped.
*/
override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float {
- return if (offset >= containerSize || offset < 0) {
- offset
+ val proposedOffsetMove =
+ defaultBringIntoViewSpec.calculateScrollDistance(offset, size, containerSize)
+
+ val finalOffset = if (proposedOffsetMove != 0.0f) {
+ overrideProposedOffsetMove(proposedOffsetMove)
} else {
- if (size <= containerSize && (offset + size) > containerSize) {
- offset // bring into view
+ // if there's no info from the default behavior, or if we already satisfied their
+ // request.
+ if (pagerState.firstVisiblePageOffset == 0) {
+ // do nothing, we're settled
+ 0f
} else {
- // are we in a settled position?
- if (pagerState.currentPageOffsetFraction.absoluteValue == 0.0f) {
- 0f
+ // move one page forward or backward, whilst making sure we don't move out of bounds
+ // again.
+ val reversedFirstPageScroll = pagerState.firstVisiblePageOffset * -1f
+ if (pagerState.isScrollingForward) {
+ reversedFirstPageScroll + pagerState.pageSizeWithSpacing
} else {
- offset
- }
+ reversedFirstPageScroll
+ }.coerceIn(-containerSize, containerSize)
+ // moving the pager outside of container size bounds will make the focused item
+ // disappear so we're limiting how much we can scroll so the page won't move too much.
}
}
+
+ return finalOffset
+ }
+
+ private fun overrideProposedOffsetMove(
+ proposedOffsetMove: Float
+ ): Float {
+ var correctedOffset = pagerState.firstVisiblePageOffset.toFloat() * -1
+
+ // if moving forward, start from the first visible page, move as many pages as proposed.
+ while (proposedOffsetMove > 0.0f && correctedOffset < proposedOffsetMove) {
+ correctedOffset += pagerState.pageSizeWithSpacing
+ }
+
+ // if moving backwards, start from the first visible page, move as many pages as proposed.
+ while (proposedOffsetMove < 0.0f && correctedOffset > proposedOffsetMove) {
+ correctedOffset -= pagerState.pageSizeWithSpacing
+ }
+ return correctedOffset
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutSemanticState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutSemanticState.kt
index 6f7cad0..2d93543 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutSemanticState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutSemanticState.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation.pager
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.lazy.layout.LazyLayoutSemanticState
import androidx.compose.ui.semantics.CollectionInfo
@@ -47,4 +48,13 @@
} else {
CollectionInfo(rowCount = 1, columnCount = state.pageCount)
}
+
+ override val viewport: Int
+ get() = if (state.layoutInfo.orientation == Orientation.Vertical) {
+ state.layoutInfo.viewportSize.height
+ } else {
+ state.layoutInfo.viewportSize.width
+ }
+ override val contentPadding: Int
+ get() = state.layoutInfo.beforeContentPadding + state.layoutInfo.afterContentPadding
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index 6912ae3..e3d50cc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -171,7 +171,7 @@
internal var upDownDifference: Offset by mutableStateOf(Offset.Zero)
private val animatedScrollScope = PagerLazyAnimateScrollScope(this)
- private var isScrollingForward: Boolean by mutableStateOf(false)
+ internal var isScrollingForward: Boolean by mutableStateOf(false)
internal val scrollPosition = PagerScrollPosition(currentPage, currentPageOffsetFraction, this)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt
index 1f3cc8a..d4d5cc4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt
@@ -19,25 +19,15 @@
package androidx.compose.foundation.relocation
-import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.modifier.ModifierLocalModifierNode
-import androidx.compose.ui.modifier.modifierLocalOf
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.LayoutAwareModifierNode
-
-/**
- * The Key for the ModifierLocal that can be used to access the [BringIntoViewParent].
- */
-internal val ModifierLocalBringIntoViewParent = modifierLocalOf<BringIntoViewParent?> { null }
+import androidx.compose.ui.node.DelegatableNode
/**
* Platform-specific "root" of the [BringIntoViewParent] chain to call into when there are no
- * [ModifierLocalBringIntoViewParent]s above a [BringIntoViewChildNode].
+ * [BringIntoViewParent]s above a node.
*/
-internal expect fun CompositionLocalConsumerModifierNode.defaultBringIntoViewParent():
- BringIntoViewParent
+internal expect fun DelegatableNode.defaultBringIntoViewParent(): BringIntoViewParent
/**
* A node that can respond to [bringChildIntoView] requests from its children by scrolling its
@@ -45,8 +35,8 @@
*/
internal fun interface BringIntoViewParent {
/**
- * Scrolls this node's content so that [boundsProvider] will be in visible bounds. Must ensure that the
- * request is propagated up to the parent node.
+ * Scrolls this node's content so that [boundsProvider] will be in visible bounds. Must ensure
+ * that the request is propagated up to the parent node.
*
* This method will not return until this request has been satisfied or interrupted by a
* newer request.
@@ -61,30 +51,3 @@
*/
suspend fun bringChildIntoView(childCoordinates: LayoutCoordinates, boundsProvider: () -> Rect?)
}
-
-/**
- * Common modifier logic shared between both requester and responder modifiers, namely recording
- * the [LayoutCoordinates] of the modifier and providing access to the appropriate
- * [BringIntoViewParent]: either one read from the [ModifierLocalBringIntoViewParent], or if no
- * modifier local is specified then the [defaultParent].
- *
- * @property defaultParent The [BringIntoViewParent] to use if there is no
- * [ModifierLocalBringIntoViewParent] available to read. This parent should always be obtained by
- * calling [defaultBringIntoViewParent] to support platform-specific integration.
- */
-internal abstract class BringIntoViewChildNode : Modifier.Node(),
- ModifierLocalModifierNode, LayoutAwareModifierNode, CompositionLocalConsumerModifierNode {
- private val defaultParent = defaultBringIntoViewParent()
-
- private val localParent: BringIntoViewParent? get() = ModifierLocalBringIntoViewParent.current
-
- protected var hasBeenPlaced = false
- private set
-
- protected val parent: BringIntoViewParent
- get() = localParent ?: defaultParent
-
- override fun onPlaced(coordinates: LayoutCoordinates) {
- hasBeenPlaced = true
- }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
index 8edd0ac..3bb672c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
@@ -23,11 +23,8 @@
import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.requireLayoutCoordinates
import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.unit.toSize
/**
* Can be used to send [bringIntoView] requests. Pass it as a parameter to
@@ -35,16 +32,13 @@
*
* For instance, you can call [bringIntoView()][bringIntoView] to make all the
* scrollable parents scroll so that the specified item is brought into the
- * parent bounds.
- *
- * Here is a sample where a composable is brought into view:
- * @sample androidx.compose.foundation.samples.BringIntoViewSample
- *
- * Here is a sample where part of a composable is brought into view:
- * @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
+ * scroll viewport.
*
* Note: this API is experimental while we optimise the performance and find the right API shape
- * for it
+ * for it.
+ *
+ * @sample androidx.compose.foundation.samples.BringIntoViewSample
+ * @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
*/
@ExperimentalFoundationApi
sealed interface BringIntoViewRequester {
@@ -60,10 +54,7 @@
* [Modifier.bringIntoViewRequester()][bringIntoViewRequester] associated with this
* [BringIntoViewRequester] will be used.
*
- * Here is a sample where a composable is brought into view:
* @sample androidx.compose.foundation.samples.BringIntoViewSample
- *
- * Here is a sample where a part of a composable is brought into view:
* @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
*/
suspend fun bringIntoView(rect: Rect? = null)
@@ -91,7 +82,7 @@
/**
* Modifier that can be used to send
- * [bringIntoView][BringIntoViewRequester.bringIntoView] requests.
+ * [scrollIntoView][BringIntoViewRequester.bringIntoView] requests.
*
* The following example uses a `bringIntoViewRequester` to bring an item into
* the parent bounds. The example demonstrates how a composable can ask its
@@ -102,7 +93,7 @@
*
* @param bringIntoViewRequester An instance of [BringIntoViewRequester]. This
* hoisted object can be used to send
- * [bringIntoView][BringIntoViewRequester.bringIntoView] requests to parents
+ * [scrollIntoView][BringIntoViewRequester.scrollIntoView] requests to parents
* of the current composable.
*
* Note: this API is experimental while we optimise the performance and find the right API shape
@@ -120,7 +111,7 @@
override suspend fun bringIntoView(rect: Rect?) {
modifiers.forEach {
- it.bringIntoView(rect)
+ it.scrollIntoView(rect)
}
}
}
@@ -154,13 +145,13 @@
/**
* A modifier that holds state and modifier implementations for [bringIntoViewRequester]. It has
- * access to the next [BringIntoViewParent] via [BringIntoViewChildNode], and uses that parent
- * to respond to requests to [bringIntoView].
+ * access to the next [BringIntoViewParent] via [findBringIntoViewParent], and uses that parent
+ * to respond to requests to [scrollIntoView].
*/
@ExperimentalFoundationApi
internal class BringIntoViewRequesterNode(
private var requester: BringIntoViewRequester
-) : BringIntoViewChildNode() {
+) : Modifier.Node() {
override val shouldAutoInvalidate: Boolean = false
override fun onAttach() {
@@ -184,19 +175,4 @@
override fun onDetach() {
disposeRequester()
}
-
- /**
- * Requests that [rect] (if non-null) or the entire bounds of this modifier's node (if [rect]
- * is null) be brought into view by the [parent] [BringIntoViewParent].
- */
- suspend fun bringIntoView(rect: Rect?) {
- if (!isAttached) return
- parent.bringChildIntoView(requireLayoutCoordinates()) {
- // If the rect is not specified, use a rectangle representing the entire composable.
- // If the coordinates are detached when this call is made, we don't bother even
- // submitting the request, but if the coordinates become detached while the request
- // is being handled we just return a null Rect.
- rect ?: if (isAttached) requireLayoutCoordinates().size.toSize().toRect() else null
- }
- }
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
index 7e3b9f7..2e8e4da 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
@@ -23,9 +23,11 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.modifier.ModifierLocalMap
-import androidx.compose.ui.modifier.modifierLocalMapOf
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.TraversableNode
+import androidx.compose.ui.node.findNearestAncestor
import androidx.compose.ui.node.requireLayoutCoordinates
import androidx.compose.ui.platform.InspectorInfo
import kotlinx.coroutines.coroutineScope
@@ -36,9 +38,8 @@
* the item is visible on screen. To apply a responder to an element, pass it to the
* [bringIntoViewResponder] modifier.
*
- * When a component calls [BringIntoViewRequester.bringIntoView], the
- * [BringIntoView ModifierLocal][ModifierLocalBringIntoViewParent] is read to gain access to the
- * [BringIntoViewResponder], which is responsible for, in order:
+ * When a component calls [BringIntoViewRequester.bringIntoView], the nearest
+ * [BringIntoViewResponder] is found, which is responsible for, in order:
*
* 1. Calculating a rectangle that its parent responder should bring into view by returning it from
* [calculateRectForParent].
@@ -137,22 +138,30 @@
/**
* A modifier that holds state and modifier implementations for [bringIntoViewResponder]. It has
- * access to the next [BringIntoViewParent] via [BringIntoViewChildNode] and additionally
+ * access to the next [BringIntoViewParent] via [findBringIntoViewParent] and additionally
* provides itself as the [BringIntoViewParent] for subsequent modifiers. This class is responsible
* for recursively propagating requests up the responder chain.
*/
@OptIn(ExperimentalFoundationApi::class)
internal class BringIntoViewResponderNode(
var responder: BringIntoViewResponder
-) : BringIntoViewChildNode(), BringIntoViewParent {
+) : Modifier.Node(), BringIntoViewParent, LayoutAwareModifierNode, TraversableNode {
+
+ override val traverseKey: Any
+ get() = TraverseKey
+
override val shouldAutoInvalidate: Boolean = false
- override val providedValues: ModifierLocalMap =
- modifierLocalMapOf(ModifierLocalBringIntoViewParent to this)
+ // TODO(b/324613946) Get rid of this check.
+ private var hasBeenPlaced = false
+ override fun onPlaced(coordinates: LayoutCoordinates) {
+ hasBeenPlaced = true
+ }
/**
- * Responds to a child's request by first converting [boundsProvider] into this node's [LayoutCoordinates]
- * and then, concurrently, calling the [responder] and the [parent] to handle the request.
+ * Responds to a child's request by first converting [boundsProvider] into this node's
+ * [LayoutCoordinates] and then, concurrently, calling the [responder] and the [parent] to
+ * handle the request.
*/
override suspend fun bringChildIntoView(
childCoordinates: LayoutCoordinates,
@@ -192,7 +201,8 @@
// responder's coroutine.
launch {
if (isAttached) {
- parent.bringChildIntoView(
+ val parent = findBringIntoViewParent()
+ parent?.bringChildIntoView(
childCoordinates = requireLayoutCoordinates(),
boundsProvider = parentRect
)
@@ -200,6 +210,18 @@
}
}
}
+
+ companion object TraverseKey
+}
+
+/**
+ * Finds the nearest ancestor [BringIntoViewResponderNode], or returns [defaultBringIntoViewParent]
+ * if none can be found. Returns null if the node is not attached.
+ */
+internal fun DelegatableNode.findBringIntoViewParent(): BringIntoViewParent? {
+ if (!node.isAttached) return null
+ return (findNearestAncestor(BringIntoViewResponderNode) as BringIntoViewParent?)
+ ?: defaultBringIntoViewParent()
}
/**
@@ -215,13 +237,3 @@
// Translate the rect to this parent's local coordinates.
return rect.translate(localRect.topLeft)
}
-
-/**
- * Returns true if [other] is fully contained inside this [Rect], using inclusive bound checks.
- */
-private fun Rect.completelyOverlaps(other: Rect): Boolean {
- return left <= other.left &&
- top <= other.top &&
- right >= other.right &&
- bottom >= other.bottom
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/ScrollIntoViewRequester.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/ScrollIntoViewRequester.kt
new file mode 100644
index 0000000..da62957
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/ScrollIntoViewRequester.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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:JvmMultifileClass
+@file:JvmName("ScrollIntoView")
+
+package androidx.compose.foundation.relocation
+
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.toRect
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.requireLayoutCoordinates
+import androidx.compose.ui.unit.toSize
+
+/**
+ * Bring this node into bounds by making all the scrollable parents scroll appropriately.
+ *
+ * This method will not return until this request is satisfied or a newer request interrupts it.
+ * If this call is interrupted by a newer call, this method will throw a
+ * [CancellationException][kotlinx.coroutines.CancellationException].
+ *
+ * @param rect The rectangle (In local coordinates) that should be brought into view. If you
+ * don't specify the coordinates, the coordinates of the
+ * [Modifier.bringIntoViewRequester()][bringIntoViewRequester] associated with this
+ * [BringIntoViewRequester] will be used.
+ *
+ * @sample androidx.compose.foundation.samples.BringIntoViewSample
+ * @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
+ */
+suspend fun DelegatableNode.scrollIntoView(rect: Rect? = null) {
+ if (!node.isAttached) return
+ val layoutCoordinates = requireLayoutCoordinates()
+ val parent = findBringIntoViewParent() ?: return
+ parent.bringChildIntoView(layoutCoordinates) {
+ // If the rect is not specified, use a rectangle representing the entire composable.
+ // If the coordinates are detached when this call is made, we don't bother even
+ // submitting the request, but if the coordinates become detached while the request
+ // is being handled we just return a null Rect.
+ rect ?: layoutCoordinates.takeIf { it.isAttached }?.size?.toSize()?.toRect()
+ }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt
index ac97ee0..1d4daa6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt
@@ -32,13 +32,11 @@
import androidx.compose.foundation.text.input.TextObfuscationMode
import androidx.compose.foundation.text.input.internal.CodepointTransformation
import androidx.compose.foundation.text.input.internal.mask
-import androidx.compose.foundation.text.input.internal.syncTextFieldState
import androidx.compose.foundation.text.input.then
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
@@ -56,11 +54,9 @@
import androidx.compose.ui.semantics.password
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.Density
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
@@ -71,147 +67,14 @@
/**
* BasicSecureTextField is specifically designed for password entry fields and is a preconfigured
- * alternative to [BasicTextField2]. It only supports a single line of content and comes with
+ * alternative to [BasicTextField]. It only supports a single line of content and comes with
* default settings for [KeyboardOptions], [InputTransformation], and [CodepointTransformation] that
* are appropriate for entering secure content. Additionally, some context menu actions like cut,
* copy, and drag are disabled for added security.
*
- * Whenever the user edits the text, [onValueChange] is called with the most up to date state
- * represented by [String] with which developer is expected to update their state.
- *
- * While focused and being edited, the caller temporarily loses _direct_ control of the contents of
- * the field through the [value] parameter. If an unexpected [value] is passed in during this time,
- * the contents of the field will _not_ be updated to reflect the value until editing is done. When
- * editing is done (i.e. focus is lost), the field will be updated to the last [value] received. Use
- * an [inputTransformation] to accept or reject changes during editing. For more direct control of
- * the field contents use the [BasicSecureTextField] overload that accepts a [TextFieldState].
- *
- * @param value The input [String] text to be shown in the text field.
- * @param onValueChange The callback that is triggered when the user or the system updates the
- * text. The updated text is passed as a parameter of the callback. The value passed to the callback
- * will already have had the [inputTransformation] applied.
+ * @param state [TextFieldState] object that holds the internal state of a [BasicTextField].
* @param modifier optional [Modifier] for this text field.
- * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
- * field will be neither editable nor focusable, the input of the text field will not be selectable.
- * @param onSubmit Called when the user submits a form either by pressing the action button in the
- * input method editor (IME), or by pressing the enter key on a hardware keyboard. If the user
- * submits the form by pressing the action button in the IME, the provided IME action is passed to
- * the function. If the user submits the form by pressing the enter key on a hardware keyboard,
- * the defined [imeAction] parameter is passed to the function. Return true to indicate that the
- * action has been handled completely, which will skip the default behavior, such as hiding the
- * keyboard for the [ImeAction.Done] action.
- * @param imeAction The IME action. This IME action is honored by keyboard and may show specific
- * icons on the keyboard.
- * @param textObfuscationMode Determines the method used to obscure the input text.
- * @param keyboardType The keyboard type to be used in this text field. It is set to
- * [KeyboardType.Password] by default. Use [KeyboardType.NumberPassword] for numerical password
- * fields.
- * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
- * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
- * hardware and software keyboard events, pasting or dropping text, accessibility services, and
- * tests. The transformation will _not_ be applied when changing the [value] programmatically, or
- * when the transformation is changed. If the transformation is changed on an existing text field,
- * it will be applied to the next user edit. The transformation will not immediately affect the
- * current [value].
- * @param textStyle Style configuration for text content that's displayed in the editor.
- * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
- * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
- * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
- * for different [Interaction]s.
- * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
- * provided, there will be no cursor drawn.
- * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
- * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
- * or null if it cannot. The function reads the layout result from a snapshot state object, and will
- * invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
- * paragraph information, size of the text, baselines and other details. The callback can be used to
- * add additional decoration or functionality to the text. For example, to draw a cursor or
- * selection around the text. [Density] scope is the one that was used while creating the given text
- * layout.
- * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
- * messages or similar, and automatically increase the hit target area of the text field.
- * @param scrollState Used to manage the horizontal scroll when the input content exceeds the
- * bounds of the text field. It controls the state of the scroll for the text field.
- */
-@ExperimentalFoundationApi
-@Composable
-fun BasicSecureTextField(
- value: String,
- onValueChange: (String) -> Unit,
- modifier: Modifier = Modifier,
- // TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
- onSubmit: ImeActionHandler? = null,
- imeAction: ImeAction = ImeAction.Default,
- textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
- keyboardType: KeyboardType = KeyboardType.Password,
- enabled: Boolean = true,
- inputTransformation: InputTransformation? = null,
- textStyle: TextStyle = TextStyle.Default,
- interactionSource: MutableInteractionSource? = null,
- cursorBrush: Brush = SolidColor(Color.Black),
- onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
- decorator: TextFieldDecorator? = null,
- scrollState: ScrollState = rememberScrollState(),
-) {
- val state = remember {
- TextFieldState(
- initialText = value,
- // Initialize the cursor to be at the end of the field.
- initialSelectionInChars = TextRange(value.length)
- )
- }
-
- // This is effectively a rememberUpdatedState, but it combines the updated state (text) with
- // some state that is preserved across updates (selection).
- var valueWithSelection by remember {
- mutableStateOf(
- TextFieldValue(
- text = value,
- selection = TextRange(value.length)
- )
- )
- }
- valueWithSelection = valueWithSelection.copy(text = value)
-
- BasicSecureTextField(
- state = state,
- modifier = modifier.syncTextFieldState(
- state = state,
- value = valueWithSelection,
- onValueChanged = {
- // Don't fire the callback if only the selection/cursor changed.
- if (it.text != valueWithSelection.text) {
- onValueChange(it.text)
- }
- valueWithSelection = it
- },
- writeSelectionFromTextFieldValue = false
- ),
- onSubmit = onSubmit,
- imeAction = imeAction,
- textObfuscationMode = textObfuscationMode,
- keyboardType = keyboardType,
- enabled = enabled,
- inputTransformation = inputTransformation,
- textStyle = textStyle,
- interactionSource = interactionSource,
- cursorBrush = cursorBrush,
- scrollState = scrollState,
- onTextLayout = onTextLayout,
- decorator = decorator,
- )
-}
-
-/**
- * BasicSecureTextField is specifically designed for password entry fields and is a preconfigured
- * alternative to [BasicTextField2]. It only supports a single line of content and comes with
- * default settings for [KeyboardOptions], [InputTransformation], and [CodepointTransformation] that
- * are appropriate for entering secure content. Additionally, some context menu actions like cut,
- * copy, and drag are disabled for added security.
- *
- * @param state [TextFieldState] object that holds the internal state of a [BasicTextField2].
- * @param modifier optional [Modifier] for this text field.
- * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
+ * @param enabled controls the enabled state of the [BasicTextField]. When `false`, the text
* field will be neither editable nor focusable, the input of the text field will not be selectable.
* @param onSubmit Called when the user submits a form either by pressing the action button in the
* input method editor (IME), or by pressing the enter key on a hardware keyboard. If the user
@@ -318,7 +181,7 @@
)
DisableCutCopy {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = secureTextFieldModifier,
enabled = enabled,
@@ -357,7 +220,7 @@
val passwordRevealFilter = PasswordRevealFilter(::scheduleHide)
/**
- * Pass to [BasicTextField2] for obscuring text input.
+ * Pass to [BasicTextField] for obscuring text input.
*/
val codepointTransformation = CodepointTransformation { codepointIndex, codepoint ->
if (codepointIndex == passwordRevealFilter.revealCodepointIndex) {
@@ -508,7 +371,7 @@
}
CompositionLocalProvider(LocalTextToolbar provides copyDisabledToolbar) {
Box(modifier = Modifier.onPreviewKeyEvent { keyEvent ->
- // BasicTextField2 uses this static mapping
+ // BasicTextField uses this static mapping
val command = platformDefaultKeyMapping.map(keyEvent)
// do not propagate copy and cut operations
command == KeyCommand.COPY || command == KeyCommand.CUT
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
index 02bcfa4..7207c73 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
@@ -41,7 +41,6 @@
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.ColorProducer
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasurePolicy
@@ -120,12 +119,7 @@
null
}
val finalModifier = if (selectionController != null || onTextLayout != null) {
- // put pointerHoverIcon before the user modifier so that they can override it
- val startModifier = if (selectionController == null) modifier else Modifier
- .pointerHoverIcon(textPointerIcon)
- .then(modifier)
-
- startModifier
+ modifier
// TODO(b/274781644): Remove this graphicsLayer
.graphicsLayer()
.textModifier(
@@ -290,14 +284,9 @@
val hasInlineContent = text.hasInlineContent()
val hasLinks = text.hasLinks()
if (!hasInlineContent && !hasLinks) {
- // put pointerHoverIcon before the user modifier so that they can override it
- val startModifier = if (selectionController == null) modifier else Modifier
- .pointerHoverIcon(textPointerIcon)
- .then(modifier)
-
// this is the same as text: String, use all the early exits
Layout(
- modifier = startModifier
+ modifier = modifier
// TODO(b/274781644): Remove this graphicsLayer
.graphicsLayer()
.textModifier(
@@ -647,11 +636,6 @@
{ measuredPlaceholderPositions?.value = it }
} else null
- // put pointerHoverIcon before the user modifier so that they can override it
- val startModifier = if (selectionController == null) modifier else Modifier
- .pointerHoverIcon(textPointerIcon)
- .then(modifier)
-
Layout(
content = {
textScope?.LinksComposables()
@@ -659,7 +643,7 @@
InlineChildren(text = text, inlineContents = it)
}
},
- modifier = startModifier
+ modifier = modifier
// TODO(b/274781644): Remove this graphicsLayer
.graphicsLayer()
.textModifier(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField2.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField2.kt
index 20ee63c..8284212 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField2.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField2.kt
@@ -45,7 +45,6 @@
import androidx.compose.foundation.text.input.internal.TextLayoutState
import androidx.compose.foundation.text.input.internal.TransformedTextFieldState
import androidx.compose.foundation.text.input.internal.selection.TextFieldSelectionState
-import androidx.compose.foundation.text.input.internal.syncTextFieldState
import androidx.compose.foundation.text.selection.SelectionHandle
import androidx.compose.foundation.text.selection.SelectionHandleAnchor
import androidx.compose.foundation.text.selection.SelectionHandleInfo
@@ -55,9 +54,7 @@
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Brush
@@ -73,11 +70,9 @@
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
@@ -86,190 +81,36 @@
* Basic text composable that provides an interactive box that accepts text input through software
* or hardware keyboard, but provides no decorations like hint or placeholder.
*
- * Whenever the user edits the text, [onValueChange] is called with the most up to date state
- * represented by [String] with which developer is expected to update their state.
- *
- * While focused and being edited, the caller temporarily loses _direct_ control of the contents of
- * the field through the [value] parameter. If an unexpected [value] is passed in during this time,
- * the contents of the field will _not_ be updated to reflect the value until editing is done. When
- * editing is done (i.e. focus is lost), the field will be updated to the last [value] received. Use
- * a [inputTransformation] to accept or reject changes during editing. For more direct control of
- * the field contents use the [BasicTextField2] overload that accepts a [TextFieldState].
- *
- * Unlike [TextFieldState] overload, this composable does not let the developer control selection,
- * cursor, and observe text composition information. Please check [TextFieldState] and corresponding
- * [BasicTextField2] overload for more information.
- *
- * If you want to add decorations to your text field, such as icon or similar, and increase the
- * hit target area, use the decorator:
- * @sample androidx.compose.foundation.samples.BasicTextField2DecoratorSample
- *
- * In order to filter (e.g. only allow digits, limit the number of characters), or change (e.g.
- * convert every character to uppercase) the input received from the user, use an
- * [InputTransformation].
- * @sample androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
- *
- * Limiting the height of the [BasicTextField2] in terms of line count and choosing a scroll
- * direction can be achieved by using [TextFieldLineLimits].
- *
- * Scroll state of the composable is also hoisted to enable observation and manipulation of the
- * scroll behavior by the developer, e.g. bringing a searched keyword into view by scrolling to its
- * position without focusing, or changing selection.
- *
- * @param value The input [String] text to be shown in the text field.
- * @param onValueChange The callback that is triggered when the user or the system updates the
- * text. The updated text is passed as a parameter of the callback. The value passed to the callback
- * will already have had the [inputTransformation] applied.
- * @param modifier optional [Modifier] for this text field.
- * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
- * field will be neither editable nor focusable, the input of the text field will not be selectable.
- * @param readOnly controls the editable state of the [BasicTextField2]. When `true`, the text
- * field can not be modified, however, a user can focus it and copy text from it. Read-only text
- * fields are usually used to display pre-filled forms that user can not edit.
- * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
- * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
- * hardware and software keyboard events, pasting or dropping text, accessibility services, and
- * tests. The transformation will _not_ be applied when a new [value] is passed in, or when the
- * transformation is changed. If the transformation is changed on an existing text field, it will be
- * applied to the next user edit, it will not immediately affect the current [value].
- * @param textStyle Typographic and graphic style configuration for text content that's displayed
- * in the editor.
- * @param keyboardOptions Software keyboard options that contain configurations such as
- * [KeyboardType] and [ImeAction].
- * @param keyboardActions When the input service emits an IME action, the corresponding callback
- * is called. Note that this IME action may be different from what you specified in
- * [KeyboardOptions.imeAction].
- * @param lineLimits Whether the text field should be [SingleLine], scroll horizontally, and
- * ignore newlines; or [MultiLine] and grow and scroll vertically. If [SingleLine] is passed, all
- * newline characters ('\n') within the text will be replaced with regular whitespace (' '),
- * ensuring that the contents of the text field are presented in a single line.
- * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
- * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
- * or null if it cannot. The function reads the layout result from a snapshot state object, and will
- * invalidate its caller when the layout result changes. A [TextLayoutResult] object contains
- * paragraph information, size of the text, baselines and other details. The callback can be used to
- * add additional decoration or functionality to the text. For example, to draw a cursor or
- * selection around the text. [Density] scope is the one that was used while creating the given text
- * layout.
- * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
- * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
- * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
- * for different [Interaction]s.
- * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
- * provided, then no cursor will be drawn.
- * @param outputTransformation An [OutputTransformation] that transforms how the contents of the
- * text field are presented.
- * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
- * messages or similar, and automatically increase the hit target area of the text field.
- * @param scrollState Scroll state that manages either horizontal or vertical scroll of TextField.
- * If [lineLimits] is [SingleLine], this text field is treated as single line with horizontal
- * scroll behavior. In other cases the text field becomes vertically scrollable.
- * @param outputTransformation An [OutputTransformation] that transforms how the contents of the
- * text field are presented.
- */
-@ExperimentalFoundationApi
-// This takes a composable lambda, but it is not primarily a container.
-@Suppress("ComposableLambdaParameterPosition")
-@Composable
-fun BasicTextField2(
- value: String,
- onValueChange: (String) -> Unit,
- modifier: Modifier = Modifier,
- enabled: Boolean = true,
- readOnly: Boolean = false,
- inputTransformation: InputTransformation? = null,
- textStyle: TextStyle = TextStyle.Default,
- keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
- keyboardActions: KeyboardActions = KeyboardActions.Default,
- lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
- onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
- interactionSource: MutableInteractionSource? = null,
- cursorBrush: Brush = SolidColor(Color.Black),
- outputTransformation: OutputTransformation? = null,
- decorator: TextFieldDecorator? = null,
- scrollState: ScrollState = rememberScrollState(),
- // Last parameter must not be a function unless it's intended to be commonly used as a trailing
- // lambda.
-) {
- val state = remember {
- TextFieldState(
- initialText = value,
- // Initialize the cursor to be at the end of the field.
- initialSelectionInChars = TextRange(value.length)
- )
- }
-
- // This is effectively a rememberUpdatedState, but it combines the updated state (text) with
- // some state that is preserved across updates (selection).
- var valueWithSelection by remember {
- mutableStateOf(
- TextFieldValue(
- text = value,
- selection = TextRange(value.length)
- )
- )
- }
- valueWithSelection = valueWithSelection.copy(text = value)
-
- BasicTextField2(
- state = state,
- modifier = modifier.syncTextFieldState(
- state = state,
- value = valueWithSelection,
- onValueChanged = {
- // Don't fire the callback if only the selection/cursor changed.
- if (it.text != valueWithSelection.text) {
- onValueChange(it.text)
- }
- valueWithSelection = it
- },
- writeSelectionFromTextFieldValue = false
- ),
- enabled = enabled,
- readOnly = readOnly,
- inputTransformation = inputTransformation,
- textStyle = textStyle,
- keyboardOptions = keyboardOptions,
- keyboardActions = keyboardActions,
- lineLimits = lineLimits,
- onTextLayout = onTextLayout,
- interactionSource = interactionSource,
- cursorBrush = cursorBrush,
- scrollState = scrollState,
- outputTransformation = outputTransformation,
- decorator = decorator,
- )
-}
-
-/**
- * Basic text composable that provides an interactive box that accepts text input through software
- * or hardware keyboard, but provides no decorations like hint or placeholder.
- *
* All the editing state of this composable is hoisted through [state]. Whenever the contents of
* this composable change via user input or semantics, [TextFieldState.text] gets updated.
* Similarly, all the programmatic updates made to [state] also reflect on this composable.
*
* If you want to add decorations to your text field, such as icon or similar, and increase the
* hit target area, use the decorator:
- * @sample androidx.compose.foundation.samples.BasicTextField2DecoratorSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldDecoratorSample
*
* In order to filter (e.g. only allow digits, limit the number of characters), or change (e.g.
* convert every character to uppercase) the input received from the user, use an
* [InputTransformation].
- * @sample androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldCustomInputTransformationSample
*
- * Limiting the height of the [BasicTextField2] in terms of line count and choosing a scroll
+ * Limiting the height of the [BasicTextField] in terms of line count and choosing a scroll
* direction can be achieved by using [TextFieldLineLimits].
*
* Scroll state of the composable is also hoisted to enable observation and manipulation of the
* scroll behavior by the developer, e.g. bringing a searched keyword into view by scrolling to its
* position without focusing, or changing selection.
*
- * @param state [TextFieldState] object that holds the internal editing state of [BasicTextField2].
+ * It's also possible to internally wrap around an existing TextFieldState and expose a more
+ * lightweight state hoisting mechanism through a value that dictates the content of the TextField
+ * and an onValueChange callback that communicates the changes to this value.
+ * @sample androidx.compose.foundation.samples.BasicTextFieldWithValueOnValueChangeSample
+ *
+ * @param state [TextFieldState] object that holds the internal editing state of [BasicTextField].
* @param modifier optional [Modifier] for this text field.
- * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
+ * @param enabled controls the enabled state of the [BasicTextField]. When `false`, the text
* field will be neither editable nor focusable, the input of the text field will not be selectable.
- * @param readOnly controls the editable state of the [BasicTextField2]. When `true`, the text
+ * @param readOnly controls the editable state of the [BasicTextField]. When `true`, the text
* field can not be modified, however, a user can focus it and copy text from it. Read-only text
* fields are usually used to display pre-filled forms that user can not edit.
* @param inputTransformation Optional [InputTransformation] that will be used to transform changes
@@ -316,7 +157,7 @@
// This takes a composable lambda, but it is not primarily a container.
@Suppress("ComposableLambdaParameterPosition")
@Composable
-fun BasicTextField2(
+fun BasicTextField(
state: TextFieldState,
modifier: Modifier = Modifier,
enabled: Boolean = true,
@@ -335,7 +176,7 @@
// Last parameter must not be a function unless it's intended to be commonly used as a trailing
// lambda.
) {
- BasicTextField2(
+ BasicTextField(
state = state,
modifier = modifier,
enabled = enabled,
@@ -365,7 +206,7 @@
// This takes a composable lambda, but it is not primarily a container.
@Suppress("ComposableLambdaParameterPosition")
@Composable
-internal fun BasicTextField2(
+internal fun BasicTextField(
state: TextFieldState,
modifier: Modifier = Modifier,
enabled: Boolean = true,
@@ -452,10 +293,7 @@
}
}
- val decorationModifiers = Modifier
- // put pointerHoverIcon before the user modifier so that they can override it
- .pointerHoverIcon(textPointerIcon)
- .then(modifier)
+ val decorationModifiers = modifier
.then(
// semantics + some focus + input session + touch to focus
TextFieldDecoratorModifier(
@@ -487,6 +325,7 @@
),
interactionSource = interactionSource,
)
+ .pointerHoverIcon(textPointerIcon)
Box(decorationModifiers, propagateMinConstraints = true) {
val nonNullDecorator = decorator ?: DefaultTextFieldDecorator
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index 94a3abe..b7938b5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -359,7 +359,7 @@
state,
manager.value,
imeOptions,
- offsetMapping
+ manager.offsetMapping
)
} else {
endInputSession(state)
@@ -400,6 +400,7 @@
mouseSelectionObserver = manager.mouseSelectionObserver,
textDragObserver = manager.touchSelectionObserver,
)
+ .pointerHoverIcon(textPointerIcon)
val drawModifier = Modifier.drawBehind {
state.layoutResult?.let { layoutResult ->
@@ -623,10 +624,7 @@
// Modifiers that should be applied to the outer text field container. Usually those include
// gesture and semantics modifiers.
- val decorationBoxModifier = Modifier
- // put pointerHoverIcon before the user modifier so that they can override it
- .pointerHoverIcon(textPointerIcon)
- .then(modifier)
+ val decorationBoxModifier = modifier
.legacyTextInputAdapter(legacyTextInputServiceAdapter)
.then(focusModifier)
.interceptDPadAndMoveFocus(state, focusManager)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt
index 20415c6..6e68dc8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt
@@ -23,6 +23,7 @@
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PlatformImeOptions
+import androidx.compose.ui.text.intl.LocaleList
/**
* The keyboard configuration options for TextFields. It is not guaranteed if software keyboard
@@ -46,6 +47,10 @@
* @param platformImeOptions defines the platform specific IME options.
* @param shouldShowKeyboardOnFocus when true, software keyboard will show on focus gain. When
* false, the user must interact (e.g. tap) before the keyboard is shown.
+ * @param hintLocales List of the languages that the user is supposed to switch to no matter what
+ * input method subtype is currently used. This special "hint" can be used mainly for, but not
+ * limited to, multilingual users who want IMEs to switch language based on editor's context.
+ * Pass null to express the intention that a specific hint should not be set.
*/
@Immutable
class KeyboardOptions(
@@ -54,7 +59,9 @@
val keyboardType: KeyboardType = KeyboardType.Text,
val imeAction: ImeAction = ImeAction.Default,
val platformImeOptions: PlatformImeOptions? = null,
- val shouldShowKeyboardOnFocus: Boolean = true
+ val shouldShowKeyboardOnFocus: Boolean = true,
+ @get:Suppress("NullableCollection")
+ val hintLocales: LocaleList? = null
) {
companion object {
@@ -98,6 +105,24 @@
shouldShowKeyboardOnFocus = true
)
+ @Deprecated("Maintained for binary compat", level = DeprecationLevel.HIDDEN)
+ constructor(
+ capitalization: KeyboardCapitalization = KeyboardCapitalization.None,
+ autoCorrect: Boolean = true,
+ keyboardType: KeyboardType = KeyboardType.Text,
+ imeAction: ImeAction = ImeAction.Default,
+ platformImeOptions: PlatformImeOptions? = null,
+ shouldShowKeyboardOnFocus: Boolean = true
+ ) : this(
+ capitalization,
+ autoCorrect,
+ keyboardType,
+ imeAction,
+ platformImeOptions,
+ shouldShowKeyboardOnFocus,
+ hintLocales = null
+ )
+
/**
* Returns a new [ImeOptions] with the values that are in this [KeyboardOptions] and provided
* params.
@@ -110,7 +135,8 @@
autoCorrect = autoCorrect,
keyboardType = keyboardType,
imeAction = imeAction,
- platformImeOptions = platformImeOptions
+ platformImeOptions = platformImeOptions,
+ hintLocales = hintLocales
)
fun copy(
@@ -119,7 +145,8 @@
keyboardType: KeyboardType = this.keyboardType,
imeAction: ImeAction = this.imeAction,
platformImeOptions: PlatformImeOptions? = this.platformImeOptions,
- showKeyboardOnFocus: Boolean = this.shouldShowKeyboardOnFocus
+ showKeyboardOnFocus: Boolean = this.shouldShowKeyboardOnFocus,
+ hintLocales: LocaleList? = this.hintLocales
): KeyboardOptions {
return KeyboardOptions(
capitalization = capitalization,
@@ -127,7 +154,33 @@
keyboardType = keyboardType,
imeAction = imeAction,
platformImeOptions = platformImeOptions,
- shouldShowKeyboardOnFocus = showKeyboardOnFocus
+ shouldShowKeyboardOnFocus = showKeyboardOnFocus,
+ hintLocales = hintLocales
+ )
+ }
+
+ @Deprecated(
+ "Maintained for binary compatibility",
+ level = DeprecationLevel.HIDDEN
+ )
+ fun copy(
+ capitalization: KeyboardCapitalization = this.capitalization,
+ autoCorrect: Boolean = this.autoCorrect,
+ keyboardType: KeyboardType = this.keyboardType,
+ imeAction: ImeAction = this.imeAction,
+ platformImeOptions: PlatformImeOptions? = this.platformImeOptions,
+ shouldShowKeyboardOnFocus: Boolean = this.shouldShowKeyboardOnFocus
+ ): KeyboardOptions {
+ return KeyboardOptions(
+ capitalization = capitalization,
+ autoCorrect = autoCorrect,
+ keyboardType = keyboardType,
+ imeAction = imeAction,
+ platformImeOptions = platformImeOptions,
+ shouldShowKeyboardOnFocus = shouldShowKeyboardOnFocus,
+ hintLocales = this.hintLocales
+ // New properties must be added here even though this is deprecated. The deprecated copy
+ // constructors should still work on instances created with newer library versions.
)
}
@@ -149,6 +202,7 @@
imeAction = imeAction,
platformImeOptions = platformImeOptions,
shouldShowKeyboardOnFocus = this.shouldShowKeyboardOnFocus,
+ hintLocales = this.hintLocales
// New properties must be added here even though this is deprecated. The deprecated copy
// constructors should still work on instances created with newer library versions.
)
@@ -171,6 +225,7 @@
imeAction = imeAction,
platformImeOptions = this.platformImeOptions,
shouldShowKeyboardOnFocus = this.shouldShowKeyboardOnFocus,
+ hintLocales = this.hintLocales
// New properties must be added here even though this is deprecated. The deprecated copy
// constructors should still work on instances created with newer library versions.
)
@@ -186,6 +241,7 @@
if (imeAction != other.imeAction) return false
if (platformImeOptions != other.platformImeOptions) return false
if (shouldShowKeyboardOnFocus != other.shouldShowKeyboardOnFocus) return false
+ if (hintLocales != other.hintLocales) return false
return true
}
@@ -197,6 +253,7 @@
result = 31 * result + imeAction.hashCode()
result = 31 * result + platformImeOptions.hashCode()
result = 31 * result + shouldShowKeyboardOnFocus.hashCode()
+ result = 31 * result + hintLocales.hashCode()
return result
}
@@ -204,6 +261,7 @@
return "KeyboardOptions(capitalization=$capitalization, autoCorrect=$autoCorrect, " +
"keyboardType=$keyboardType, imeAction=$imeAction, " +
"platformImeOptions=$platformImeOptions, " +
- "shouldShowKeyboardOnFocus=$shouldShowKeyboardOnFocus)"
+ "shouldShowKeyboardOnFocus=$shouldShowKeyboardOnFocus, " +
+ "hintLocales=$hintLocales)"
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
index 563832c..1d2ad88 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
@@ -38,7 +38,7 @@
* - [InputTransformation].[maxLengthInCodepoints]`()`
* - [InputTransformation].[allCaps]`()`
*
- * @sample androidx.compose.foundation.samples.BasicTextField2CustomInputTransformationSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldCustomInputTransformationSample
*/
@ExperimentalFoundationApi
@Stable
@@ -81,7 +81,7 @@
* The returned filter will use the [KeyboardOptions] from [next] if non-null, otherwise it will
* use the options from this transformation.
*
- * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationChainingSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldInputTransformationChainingSample
*
* @param next The [InputTransformation] that will be ran after this one.
*/
@@ -101,7 +101,7 @@
* The returned filter will use the [KeyboardOptions] from [next] if non-null, otherwise it will
* use the options from this transformation.
*
- * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationChainingSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldInputTransformationChainingSample
*
* @param next The [InputTransformation] that will be ran after this one.
*/
@@ -119,8 +119,8 @@
* The selection or cursor will be updated automatically. For more control of selection
* implement [InputTransformation] directly.
*
- * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationByValueChooseSample
- * @sample androidx.compose.foundation.samples.BasicTextField2InputTransformationByValueReplaceSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldInputTransformationByValueChooseSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldInputTransformationByValueReplaceSample
*/
@ExperimentalFoundationApi
@Stable
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/OutputTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/OutputTransformation.kt
index 36089f1..2144234 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/OutputTransformation.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/OutputTransformation.kt
@@ -17,12 +17,12 @@
package androidx.compose.foundation.text.input
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.runtime.Stable
/**
* A function ([transformOutput]) that transforms the text presented to a user by a
- * [BasicTextField2].
+ * [BasicTextField].
*/
@ExperimentalFoundationApi
@Stable
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
index 6e20f5c..1c0c6f7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
@@ -82,8 +82,8 @@
* means that the returned [ChangeList] always reflects the complete list of changes made to
* this value at any given time, even those made after reading this property.
*
- * @sample androidx.compose.foundation.samples.BasicTextField2ChangeIterationSample
- * @sample androidx.compose.foundation.samples.BasicTextField2ChangeReverseIterationSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldChangeIterationSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldChangeReverseIterationSample
*/
val changes: ChangeList get() = changeTracker ?: EmptyChangeList
@@ -547,7 +547,7 @@
* will be visited more than once. If you need to make changes, consider using
* [forEachChangeReversed].
*
- * @sample androidx.compose.foundation.samples.BasicTextField2ChangeIterationSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldChangeIterationSample
*
* @see forEachChangeReversed
*/
@@ -570,7 +570,7 @@
* one or changes may be skipped. [block] may make non-overlapping changes after the current one
* safely, such changes will not be visited.
*
- * @sample androidx.compose.foundation.samples.BasicTextField2ChangeReverseIterationSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldChangeReverseIterationSample
*
* @see forEachChange
*/
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldDecorator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldDecorator.kt
index 481226b..fb79323 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldDecorator.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldDecorator.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@
* placeholder, helper messages or similar, and automatically increase the hit target area
* of the text field.
*
- * @sample androidx.compose.foundation.samples.BasicTextField2DecoratorSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldDecoratorSample
*/
@ExperimentalFoundationApi
fun interface TextFieldDecorator {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
index 8272b3b2..42d9a49 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
@@ -56,7 +56,7 @@
* save and restore the field state. For more advanced use cases, pass [TextFieldState.Saver] to
* [rememberSaveable].
*
- * @sample androidx.compose.foundation.samples.BasicTextField2StateCompleteSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldStateCompleteSample
*/
@ExperimentalFoundationApi
@Stable
@@ -98,7 +98,7 @@
* To observe changes to this property outside a restartable function, see [forEachTextValue]
* and [textAsFlow].
*
- * @sample androidx.compose.foundation.samples.BasicTextField2TextDerivedStateSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldTextDerivedStateSample
*
* @see edit
* @see forEachTextValue
@@ -114,7 +114,7 @@
* text and cursor/selection. See the documentation on [TextFieldBuffer] for a more detailed
* description of the available operations.
*
- * @sample androidx.compose.foundation.samples.BasicTextField2StateEditSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldStateEditSample
*
* @see setTextAndPlaceCursorAtEnd
* @see setTextAndSelectAll
@@ -131,7 +131,7 @@
/**
* Undo history controller for this TextFieldState.
*
- * @sample androidx.compose.foundation.samples.BasicTextField2UndoSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldUndoSample
*/
// TextField does not implement UndoState because Undo related APIs should be able to remain
// separately experimental than TextFieldState
@@ -179,13 +179,14 @@
* in the Undo stack. This can be controlled by [undoBehavior].
*
* @param inputTransformation [InputTransformation] to run after [block] is applied
- * @param notifyImeOfChanges Whether IME should be notified of these changes. Only pass false to
- * this argument if the source of the changes is IME itself.
+ * @param restartImeIfContentChanges Whether IME should be restarted if the proposed changes
+ * end up editing the text content. Only pass false to this argument if the source of the
+ * changes is IME itself.
* @param block The function that updates the current editing buffer.
*/
internal inline fun editAsUser(
inputTransformation: InputTransformation?,
- notifyImeOfChanges: Boolean = true,
+ restartImeIfContentChanges: Boolean = true,
undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible,
block: EditingBuffer.() -> Unit
) {
@@ -201,7 +202,12 @@
return
}
- commitEditAsUser(previousValue, inputTransformation, notifyImeOfChanges, undoBehavior)
+ commitEditAsUser(
+ previousValue = previousValue,
+ inputTransformation = inputTransformation,
+ restartImeIfContentChanges = restartImeIfContentChanges,
+ undoBehavior = undoBehavior
+ )
}
/**
@@ -227,13 +233,17 @@
)
text = afterEditValue
- notifyIme(previousValue, afterEditValue)
+ sendChangesToIme(
+ oldValue = previousValue,
+ newValue = afterEditValue,
+ restartImeIfContentChanges = true
+ )
}
private fun commitEditAsUser(
previousValue: TextFieldCharSequence,
inputTransformation: InputTransformation?,
- notifyImeOfChanges: Boolean,
+ restartImeIfContentChanges: Boolean,
undoBehavior: TextFieldEditUndoBehavior
) {
val afterEditValue = TextFieldCharSequence(
@@ -245,9 +255,11 @@
if (inputTransformation == null) {
val oldValue = text
text = afterEditValue
- if (notifyImeOfChanges) {
- notifyIme(oldValue, afterEditValue)
- }
+ sendChangesToIme(
+ oldValue = oldValue,
+ newValue = afterEditValue,
+ restartImeIfContentChanges = restartImeIfContentChanges
+ )
recordEditForUndo(previousValue, text, mainBuffer.changeTracker, undoBehavior)
return
}
@@ -259,9 +271,11 @@
afterEditValue.selectionInChars == oldValue.selectionInChars
) {
text = afterEditValue
- if (notifyImeOfChanges) {
- notifyIme(oldValue, afterEditValue)
- }
+ sendChangesToIme(
+ oldValue = oldValue,
+ newValue = afterEditValue,
+ restartImeIfContentChanges = restartImeIfContentChanges
+ )
return
}
@@ -281,9 +295,11 @@
)
if (afterFilterValue == afterEditValue) {
text = afterFilterValue
- if (notifyImeOfChanges) {
- notifyIme(oldValue, afterEditValue)
- }
+ sendChangesToIme(
+ oldValue = oldValue,
+ newValue = afterEditValue,
+ restartImeIfContentChanges = restartImeIfContentChanges
+ )
} else {
resetStateAndNotifyIme(afterFilterValue)
}
@@ -338,13 +354,24 @@
*
* State in [TextFieldState] can change through various means but categorically there are two
* sources; Developer([TextFieldState.edit]) and User([TextFieldState.editAsUser]). Only
- * non-filtered IME sourced changes can skip updating the IME. Otherwise, all changes must be
- * contacted to IME to let it synchronize its state with the [TextFieldState]. Such
- * communication is built by IME registering a [NotifyImeListener] on a [TextFieldState].
+ * non-InputTransformed IME sourced changes can skip updating the IME. Otherwise, all changes
+ * must be sent to the IME to let it synchronize its state with the [TextFieldState]. Such
+ * a communication channel is established by the IME registering a [NotifyImeListener] on a
+ * [TextFieldState].
*/
internal fun interface NotifyImeListener {
- fun onChange(oldValue: TextFieldCharSequence, newValue: TextFieldCharSequence)
+ /**
+ * Called when the value in [TextFieldState] changes via any source. The
+ * [restartImeIfContentChanges] flag determines whether a text change between [oldValue]
+ * and [newValue] should restart the ongoing input connection. Selection changes never
+ * require a restart.
+ */
+ fun onChange(
+ oldValue: TextFieldCharSequence,
+ newValue: TextFieldCharSequence,
+ restartImeIfContentChanges: Boolean
+ )
}
/**
@@ -400,16 +427,26 @@
// restartInput call is handled before notifyImeListeners return.
text = finalValue
- notifyIme(bufferState, finalValue)
+ sendChangesToIme(
+ oldValue = bufferState,
+ newValue = finalValue,
+ restartImeIfContentChanges = true
+ )
}
private val notifyImeListeners = mutableVectorOf<NotifyImeListener>()
- private fun notifyIme(
+ /**
+ * Sends an update to the IME depending on .
+ */
+ private fun sendChangesToIme(
oldValue: TextFieldCharSequence,
- newValue: TextFieldCharSequence
+ newValue: TextFieldCharSequence,
+ restartImeIfContentChanges: Boolean
) {
- notifyImeListeners.forEach { it.onChange(oldValue, newValue) }
+ notifyImeListeners.forEach {
+ it.onChange(oldValue, newValue, restartImeIfContentChanges)
+ }
}
/**
@@ -452,7 +489,7 @@
* Returns a [Flow] of the values of [TextFieldState.text] as seen from the global snapshot.
* The initial value is emitted immediately when the flow is collected.
*
- * @sample androidx.compose.foundation.samples.BasicTextField2TextValuesSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldTextValuesSample
*/
@ExperimentalFoundationApi
fun TextFieldState.textAsFlow(): Flow<TextFieldCharSequence> = snapshotFlow { text }
@@ -559,7 +596,7 @@
* either a side effect when text is changed, or filter it in some way, use an
* [InputTransformation].
*
- * @sample androidx.compose.foundation.samples.BasicTextField2ForEachTextValueSample
+ * @sample androidx.compose.foundation.samples.BasicTextFieldForEachTextValueSample
*
* @see textAsFlow
*/
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldCoreModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldCoreModifier.kt
index 48d9bbd..34f091b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldCoreModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldCoreModifier.kt
@@ -20,7 +20,7 @@
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollBy
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.internal.selection.TextFieldSelectionState
import androidx.compose.foundation.text.input.internal.selection.textFieldMagnifierNode
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
@@ -56,6 +56,7 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import kotlin.math.absoluteValue
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.min
@@ -66,7 +67,7 @@
import kotlinx.coroutines.launch
/**
- * Modifier element for the core functionality of [BasicTextField2] that is passed as inner
+ * Modifier element for the core functionality of [BasicTextField] that is passed as inner
* TextField to the decoration box. This is only half the actual modifiers for the field, the other
* half are only attached to the decorated text field.
*
@@ -223,15 +224,27 @@
// this node is writeable, focused and gained that focus just now.
// start the state value observation
changeObserverJob = coroutineScope.launch {
+ // A flag to oscillate the reported isWindowFocused value in snapshotFlow.
+ // Repeatedly returning true/false everytime snapshotFlow is re-evaluated breaks
+ // the assumption that each re-evaluation would also trigger the collector. However,
+ // snapshotFlow carries an implicit `distinctUntilChanged` logic that prevents
+ // the propagation of update events. Instead we introduce a sign that changes each
+ // time snapshotFlow is re-entered. true/false becomes 1/2 or -1/-2.
+ // true = 1 = -1
+ // false = 2 = -2
+ // sign is either 1 or -1
+ var sign = 1
snapshotFlow {
// Read the text state, so the animation restarts when the text or cursor
// position change.
textFieldState.visualText
// Only animate the cursor when its window is actually focused. This also
// disables the cursor animation when the screen is off.
- currentValueOf(LocalWindowInfo).isWindowFocused
+ val isWindowFocused = currentValueOf(LocalWindowInfo).isWindowFocused
+
+ ((if (isWindowFocused) 1 else 2) * sign).also { sign *= -1 }
}.collectLatest { isWindowFocused ->
- if (isWindowFocused) {
+ if (isWindowFocused.absoluteValue == 1) {
cursorAnimation.snapToVisibleAndAnimate()
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
index e720d7b..8fa754b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
@@ -25,7 +25,7 @@
import androidx.compose.foundation.content.readPlainText
import androidx.compose.foundation.interaction.HoverInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.text.BasicTextField2
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.Handle
import androidx.compose.foundation.text.KeyboardActionScope
import androidx.compose.foundation.text.KeyboardActions
@@ -99,7 +99,7 @@
private val MediaTypesAll = setOf(MediaType.All)
/**
- * Modifier element for most of the functionality of [BasicTextField2] that is attached to the
+ * Modifier element for most of the functionality of [BasicTextField] that is attached to the
* decoration box. This is only half the actual modifiers for the field, the other half are only
* attached to the internal text field.
*
@@ -212,7 +212,7 @@
private var dragEnterEvent: HoverInteraction.Enter? = null
/**
- * Special Drag and Drop node for BasicTextField2 that is also aware of `receiveContent` API.
+ * Special Drag and Drop node for BasicTextField that is also aware of `receiveContent` API.
*/
private val dragAndDropNode = delegate(
textFieldDragAndDropNode(
@@ -235,7 +235,7 @@
dragEnterEvent = HoverInteraction.Enter().also {
interactionSource.tryEmit(it)
}
- // Although BasicTextField2 itself is not a `receiveContent` node, it should
+ // Although BasicTextField itself is not a `receiveContent` node, it should
// behave like one. Delegate the enter event to the ancestor nodes just like
// `receiveContent` itself would.
getReceiveContentConfiguration()?.receiveContentListener?.onDragEnter()
@@ -272,7 +272,7 @@
onExited = {
emitDragExitEvent()
textFieldSelectionState.clearHandleDragging()
- // Although BasicTextField2 itself is not a `receiveContent` node, it should
+ // Although BasicTextField itself is not a `receiveContent` node, it should
// behave like one. Delegate the exit event to the ancestor nodes just like
// `receiveContent` itself would.
getReceiveContentConfiguration()?.receiveContentListener?.onDragExit()
@@ -612,7 +612,8 @@
override fun onObservedReadsChanged() {
observeReads {
windowInfo = currentValueOf(LocalWindowInfo)
- startOrDisposeInputSessionOnWindowFocusChange()
+ textFieldSelectionState.isFocused = this.isFocused
+ startInputSessionOnWindowFocusChange()
}
}
@@ -646,12 +647,12 @@
inputSessionJob = null
}
- private fun startOrDisposeInputSessionOnWindowFocusChange() {
+ private fun startInputSessionOnWindowFocusChange() {
if (windowInfo == null) return
+ // b/326323000: We do not dispose input session on just window focus change until another
+ // item requests focus and we lose element focus status which is handled by onFocusEvent.
if (windowInfo?.isWindowFocused == true && isElementFocused) {
startInputSession(fromTap = false)
- } else {
- disposeInputSession()
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.kt
index 5ec5a1c..ffae610 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldKeyEventHandler.kt
@@ -41,7 +41,7 @@
internal expect fun createTextFieldKeyEventHandler(): TextFieldKeyEventHandler
/**
- * Handles KeyEvents coming to a BasicTextField2. This is mostly to support hardware keyboard but
+ * Handles KeyEvents coming to a BasicTextField. This is mostly to support hardware keyboard but
* any KeyEvent can also be sent by the IME or other platform systems.
*
* This class is left abstract to make sure that each platform extends from it. Platforms can
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
index 41b0058..2687589 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
@@ -287,12 +287,12 @@
* @see mapFromTransformed
*/
inline fun editUntransformedTextAsUser(
- notifyImeOfChanges: Boolean = true,
+ restartImeIfContentChanges: Boolean = true,
block: EditingBuffer.() -> Unit
) {
textFieldState.editAsUser(
inputTransformation = inputTransformation,
- notifyImeOfChanges = notifyImeOfChanges,
+ restartImeIfContentChanges = restartImeIfContentChanges,
block = block
)
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
index e796de7..7a4d837 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
@@ -24,6 +24,7 @@
import androidx.compose.foundation.text.selection.SelectionRegistrar
import androidx.compose.foundation.text.selection.hasSelection
import androidx.compose.foundation.text.selection.selectionGestureInput
+import androidx.compose.foundation.text.textPointerIcon
import androidx.compose.runtime.RememberObserver
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
@@ -31,6 +32,7 @@
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.clipRect
+import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.TextOverflow
@@ -82,6 +84,7 @@
selectableId = selectableId,
layoutCoordinates = { params.layoutCoordinates },
)
+ .pointerHoverIcon(textPointerIcon)
override fun onRemembered() {
selectable = selectionRegistrar.subscribe(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
index 0beecfb..6fb59f3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
@@ -21,9 +21,6 @@
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.graphics.Color
import androidx.compose.ui.graphics.ColorProducer
import androidx.compose.ui.graphics.Shadow
@@ -393,9 +390,8 @@
if (willClip) {
val width = layoutCache.layoutSize.width.toFloat()
val height = layoutCache.layoutSize.height.toFloat()
- val bounds = Rect(Offset.Zero, Size(width, height))
canvas.save()
- canvas.clipRect(bounds)
+ canvas.clipRect(left = 0f, top = 0f, right = width, bottom = height)
}
try {
val textDecoration = style.textDecoration ?: TextDecoration.None
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
index 1fb5bf4..39ee20f 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
@@ -20,7 +20,6 @@
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -44,7 +43,7 @@
import androidx.compose.ui.input.pointer.changedToUp
import androidx.compose.ui.input.pointer.isOutOfBounds
import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.center
@@ -52,8 +51,7 @@
import androidx.compose.ui.util.fastAll
import java.awt.event.KeyEvent.VK_ENTER
-internal actual fun CompositionLocalConsumerModifierNode
- .isComposeRootInScrollableContainer(): Boolean {
+internal actual fun DelegatableNode.isComposeRootInScrollableContainer(): Boolean {
return false
}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.desktop.kt
index 68ace05..6190bf233 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/content/internal/DragAndDropRequestPermission.desktop.kt
@@ -17,9 +17,9 @@
package androidx.compose.foundation.content.internal
import androidx.compose.ui.draganddrop.DragAndDropEvent
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatableNode
-internal actual fun CompositionLocalConsumerModifierNode.dragAndDropRequestPermission(
+internal actual fun DelegatableNode.dragAndDropRequestPermission(
event: DragAndDropEvent
) {
/* no-op */
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.desktop.kt
index 6f9cc99..0412a27 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/layout/PrefetchExecutor.desktop.kt
@@ -27,6 +27,6 @@
@ExperimentalFoundationApi
private object NoOpPrefetchExecutor : PrefetchExecutor {
- override fun requestPrefetch(request: PrefetchExecutor.Request) {
+ override fun requestPrefetch(prefetchRequest: PrefetchRequest) {
}
}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.desktop.kt
index 7c4517f..f306d6ac 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.desktop.kt
@@ -16,14 +16,22 @@
package androidx.compose.foundation.relocation
-import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.node.DelegatableNode
/**
* Platform specific internal API to bring a rectangle into view.
*/
-internal actual fun CompositionLocalConsumerModifierNode.defaultBringIntoViewParent():
- BringIntoViewParent =
- BringIntoViewParent { _, _ ->
+internal actual fun DelegatableNode.defaultBringIntoViewParent(): BringIntoViewParent =
+ NoopBringIntoViewParent
+
+private object NoopBringIntoViewParent : BringIntoViewParent {
+ override suspend fun bringChildIntoView(
+ childCoordinates: LayoutCoordinates,
+ boundsProvider: () -> Rect?
+ ) {
// TODO(b/203204124): Implement this if desktop has a
// concept similar to Android's View.requestRectangleOnScreen.
}
+}
diff --git a/compose/integration-tests/docs-snippets/build.gradle b/compose/integration-tests/docs-snippets/build.gradle
index f57bfff..7e35a50 100644
--- a/compose/integration-tests/docs-snippets/build.gradle
+++ b/compose/integration-tests/docs-snippets/build.gradle
@@ -40,7 +40,7 @@
implementation(project(":compose:foundation:foundation-layout"))
implementation(project(":compose:material:material"))
implementation(project(":compose:material3:material3"))
- implementation(project(":compose:material:material-icons-extended"))
+ implementation("androidx.compose.material:material-icons-extended:1.6.0")
implementation(project(":compose:runtime:runtime"))
implementation(project(":compose:runtime:runtime-livedata"))
implementation(project(":compose:ui:ui-graphics"))
diff --git a/compose/integration-tests/hero/hero-implementation/build.gradle b/compose/integration-tests/hero/hero-implementation/build.gradle
index 0b2f096..fabe1431 100644
--- a/compose/integration-tests/hero/hero-implementation/build.gradle
+++ b/compose/integration-tests/hero/hero-implementation/build.gradle
@@ -50,7 +50,6 @@
implementation(project(":compose:material:material"))
implementation(project(":compose:material3:material3"))
implementation(project(":compose:material:material-icons-core"))
- implementation(project(":compose:material:material-icons-extended"))
implementation(project(":compose:runtime:runtime"))
implementation(project(":compose:runtime:runtime-tracing"))
implementation(project(":compose:ui:ui"))
diff --git a/compose/integration-tests/hero/hero-implementation/src/main/java/androidx/compose/integration/hero/implementation/jetsnack/DestinationBar.kt b/compose/integration-tests/hero/hero-implementation/src/main/java/androidx/compose/integration/hero/implementation/jetsnack/DestinationBar.kt
index 30c65f1..4a7a7aab 100644
--- a/compose/integration-tests/hero/hero-implementation/src/main/java/androidx/compose/integration/hero/implementation/jetsnack/DestinationBar.kt
+++ b/compose/integration-tests/hero/hero-implementation/src/main/java/androidx/compose/integration/hero/implementation/jetsnack/DestinationBar.kt
@@ -27,7 +27,7 @@
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.ExpandMore
+import androidx.compose.material.icons.outlined.Create
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -60,7 +60,7 @@
modifier = Modifier.align(Alignment.CenterVertically)
) {
Icon(
- imageVector = Icons.Outlined.ExpandMore,
+ imageVector = Icons.Outlined.Create,
tint = JetsnackTheme.colors.brand,
contentDescription = stringResource(R.string.label_select_delivery)
)
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index 1873374..a905398 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -241,5 +241,19 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+
+ <activity
+ android:name=".CrossfadeActivity"
+ android:label="Compose Crossfade Benchmark"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="androidx.compose.integration.macrobenchmark.target.CROSSFADE_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/CrossfadeActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/CrossfadeActivity.kt
new file mode 100644
index 0000000..eed8a8d
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/CrossfadeActivity.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.integration.macrobenchmark.target
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+
+class CrossfadeActivity : ComponentActivity() {
+
+ @Suppress("UnusedCrossfadeTargetStateParameter")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ Column {
+ var toggled by remember { mutableStateOf(false) }
+ var targetState by remember { mutableStateOf(false) }
+ key(toggled) {
+ Crossfade(
+ modifier = Modifier.size(150.dp),
+ label = "Crossfade",
+ targetState = targetState
+ ) {
+ }
+ }
+ Button(
+ modifier = Modifier.semantics { contentDescription = "toggle-crossfade" },
+ onClick = { toggled = !toggled }) {
+ Text(toggled.toString())
+ }
+ Button(
+ modifier = Modifier.semantics { contentDescription = "toggle-target" },
+ onClick = { targetState = !targetState }) {
+ Text(targetState.toString())
+ }
+ }
+ launchIdlenessTracking()
+ }
+ }
+}
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/CrossfadeBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/CrossfadeBenchmark.kt
new file mode 100644
index 0000000..2152b67a
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/CrossfadeBenchmark.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.integration.macrobenchmark
+
+import android.content.Intent
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import androidx.testutils.createCompilationParams
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class CrossfadeBenchmark(private val compilationMode: CompilationMode) {
+
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Test
+ fun crossfadeBenchmarkInitialComposition() {
+ benchmarkRule.measureRepeated(
+ packageName = PackageName,
+ metrics = listOf(FrameTimingMetric()),
+ compilationMode = compilationMode,
+ iterations = 10,
+ setupBlock = {
+ val intent = Intent().apply { action = Action }
+ startActivityAndWait(intent)
+ }
+ ) {
+ repeat(2) {
+ device.findObject(By.desc(ToggleCrossfadeDescription)).click()
+ device.wait(Until.findObject(By.desc(ComposeIdle)), 3000)
+ }
+ }
+ }
+
+ @Test
+ fun crossfadeBenchmarkTargetStateChange() {
+ benchmarkRule.measureRepeated(
+ packageName = PackageName,
+ metrics = listOf(FrameTimingMetric()),
+ compilationMode = compilationMode,
+ iterations = 10,
+ setupBlock = {
+ val intent = Intent().apply { action = Action }
+ startActivityAndWait(intent)
+ }
+ ) {
+ repeat(2) {
+ device.findObject(By.desc(ToggleTargetStateDescription)).click()
+ device.wait(Until.findObject(By.desc(ComposeIdle)), 3000)
+ }
+ }
+ }
+
+ companion object {
+ private const val PackageName = "androidx.compose.integration.macrobenchmark.target"
+ private const val Action =
+ "androidx.compose.integration.macrobenchmark.target.CROSSFADE_ACTIVITY"
+ const val ToggleCrossfadeDescription = "toggle-crossfade"
+ const val ToggleTargetStateDescription = "toggle-target"
+ const val ComposeIdle = "COMPOSE-IDLE"
+
+ @Parameterized.Parameters(name = "compilationMode={0}")
+ @JvmStatic
+ fun parameters() = createCompilationParams()
+ }
+}
diff --git a/compose/material/material-icons-core/build.gradle b/compose/material/material-icons-core/build.gradle
index 8e3c4f1..d51d9d4 100644
--- a/compose/material/material-icons-core/build.gradle
+++ b/compose/material/material-icons-core/build.gradle
@@ -110,7 +110,7 @@
androidx {
name = "Compose Material Icons Core"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose Material Design core icons. This module contains the most commonly used set of Material icons."
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/material/material-icons-extended/build.gradle b/compose/material/material-icons-extended/build.gradle
index fd46557..e13629d 100644
--- a/compose/material/material-icons-extended/build.gradle
+++ b/compose/material/material-icons-extended/build.gradle
@@ -48,7 +48,6 @@
dependencies {
api(project(":compose:material:material-icons-core"))
implementation(libs.kotlinStdlibCommon)
- implementation("androidx.compose.runtime:runtime:1.6.0")
}
}
@@ -125,7 +124,7 @@
androidx {
name = "Compose Material Icons Extended"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
// This module has a large number (5000+) of generated source files and so doc generation /
// API tracking will simply take too long
runApiTasks = new RunApiTasks.No("Five thousand generated source files")
diff --git a/compose/material/material-navigation/api/current.txt b/compose/material/material-navigation/api/current.txt
new file mode 100644
index 0000000..8255b23
--- /dev/null
+++ b/compose/material/material-navigation/api/current.txt
@@ -0,0 +1,38 @@
+// Signature format: 4.0
+package androidx.compose.material.navigation {
+
+ public final class BottomSheetKt {
+ method @androidx.compose.runtime.Composable public static void ModalBottomSheetLayout(androidx.compose.material.navigation.BottomSheetNavigator bottomSheetNavigator, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape sheetShape, optional float sheetElevation, optional long sheetBackgroundColor, optional long sheetContentColor, optional long scrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ @androidx.navigation.Navigator.Name("bottomSheet") public final class BottomSheetNavigator extends androidx.navigation.Navigator<androidx.compose.material.navigation.BottomSheetNavigator.Destination> {
+ ctor public BottomSheetNavigator(androidx.compose.material.ModalBottomSheetState sheetState);
+ method public androidx.compose.material.navigation.BottomSheetNavigator.Destination createDestination();
+ method public androidx.compose.material.navigation.BottomSheetNavigatorSheetState getNavigatorSheetState();
+ property public final androidx.compose.material.navigation.BottomSheetNavigatorSheetState navigatorSheetState;
+ }
+
+ @androidx.navigation.NavDestination.ClassType(Composable::class) public static final class BottomSheetNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
+ ctor public BottomSheetNavigator.Destination(androidx.compose.material.navigation.BottomSheetNavigator navigator, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.ColumnScope,? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+ }
+
+ public final class BottomSheetNavigatorKt {
+ method @androidx.compose.runtime.Composable public static androidx.compose.material.navigation.BottomSheetNavigator rememberBottomSheetNavigator(optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec);
+ }
+
+ public final class BottomSheetNavigatorSheetState {
+ ctor public BottomSheetNavigatorSheetState(androidx.compose.material.ModalBottomSheetState sheetState);
+ method public androidx.compose.material.ModalBottomSheetValue getCurrentValue();
+ method public androidx.compose.material.ModalBottomSheetValue getTargetValue();
+ method public boolean isVisible();
+ property public final androidx.compose.material.ModalBottomSheetValue currentValue;
+ property public final boolean isVisible;
+ property public final androidx.compose.material.ModalBottomSheetValue targetValue;
+ }
+
+ public final class NavGraphBuilderKt {
+ method public static void bottomSheet(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.ColumnScope,? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+ }
+
+}
+
diff --git a/compose/material/material-navigation/api/res-current.txt b/compose/material/material-navigation/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/compose/material/material-navigation/api/res-current.txt
diff --git a/compose/material/material-navigation/api/restricted_current.txt b/compose/material/material-navigation/api/restricted_current.txt
new file mode 100644
index 0000000..8255b23
--- /dev/null
+++ b/compose/material/material-navigation/api/restricted_current.txt
@@ -0,0 +1,38 @@
+// Signature format: 4.0
+package androidx.compose.material.navigation {
+
+ public final class BottomSheetKt {
+ method @androidx.compose.runtime.Composable public static void ModalBottomSheetLayout(androidx.compose.material.navigation.BottomSheetNavigator bottomSheetNavigator, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape sheetShape, optional float sheetElevation, optional long sheetBackgroundColor, optional long sheetContentColor, optional long scrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ @androidx.navigation.Navigator.Name("bottomSheet") public final class BottomSheetNavigator extends androidx.navigation.Navigator<androidx.compose.material.navigation.BottomSheetNavigator.Destination> {
+ ctor public BottomSheetNavigator(androidx.compose.material.ModalBottomSheetState sheetState);
+ method public androidx.compose.material.navigation.BottomSheetNavigator.Destination createDestination();
+ method public androidx.compose.material.navigation.BottomSheetNavigatorSheetState getNavigatorSheetState();
+ property public final androidx.compose.material.navigation.BottomSheetNavigatorSheetState navigatorSheetState;
+ }
+
+ @androidx.navigation.NavDestination.ClassType(Composable::class) public static final class BottomSheetNavigator.Destination extends androidx.navigation.NavDestination implements androidx.navigation.FloatingWindow {
+ ctor public BottomSheetNavigator.Destination(androidx.compose.material.navigation.BottomSheetNavigator navigator, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.ColumnScope,? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+ }
+
+ public final class BottomSheetNavigatorKt {
+ method @androidx.compose.runtime.Composable public static androidx.compose.material.navigation.BottomSheetNavigator rememberBottomSheetNavigator(optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec);
+ }
+
+ public final class BottomSheetNavigatorSheetState {
+ ctor public BottomSheetNavigatorSheetState(androidx.compose.material.ModalBottomSheetState sheetState);
+ method public androidx.compose.material.ModalBottomSheetValue getCurrentValue();
+ method public androidx.compose.material.ModalBottomSheetValue getTargetValue();
+ method public boolean isVisible();
+ property public final androidx.compose.material.ModalBottomSheetValue currentValue;
+ property public final boolean isVisible;
+ property public final androidx.compose.material.ModalBottomSheetValue targetValue;
+ }
+
+ public final class NavGraphBuilderKt {
+ method public static void bottomSheet(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.ColumnScope,? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+ }
+
+}
+
diff --git a/compose/material/material-navigation/build.gradle b/compose/material/material-navigation/build.gradle
new file mode 100644
index 0000000..b32975c
--- /dev/null
+++ b/compose/material/material-navigation/build.gradle
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.Publish
+import androidx.build.RunApiTasks
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("AndroidXComposePlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ api("androidx.navigation:navigation-compose:2.7.7")
+ implementation(project(":compose:material:material"))
+ implementation(libs.kotlinStdlib)
+
+ androidTestImplementation project(":compose:test-utils")
+ androidTestImplementation("androidx.navigation:navigation-testing:2.7.7")
+ androidTestImplementation(project(":compose:ui:ui-test-junit4"))
+ androidTestImplementation(project(":compose:ui:ui-test-manifest"))
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.junit)
+ androidTestImplementation(libs.truth)
+ androidTestImplementation(libs.testRules)
+}
+
+androidx {
+ name = "Compose Material Navigation"
+ publish = Publish.SNAPSHOT_AND_RELEASE
+ inceptionYear = "2024"
+ description = "Compose Material integration with Navigation"
+ samples(projectOrArtifact(":compose:material:material-navigation-samples"))
+}
+
+android {
+ namespace "androidx.compose.material.navigation"
+}
diff --git a/compose/material/material-navigation/samples/build.gradle b/compose/material/material-navigation/samples/build.gradle
new file mode 100644
index 0000000..7e3acd6
--- /dev/null
+++ b/compose/material/material-navigation/samples/build.gradle
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file was created using the `create_project.py` script located in the
+ * `<AndroidX root>/development/project-creator` directory.
+ *
+ * Please use that script when creating a new project, rather than copying an existing project and
+ * modifying its settings.
+ */
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("AndroidXComposePlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ implementation(libs.kotlinStdlib)
+
+ compileOnly(project(":annotation:annotation-sampled"))
+ implementation(project(":compose:foundation:foundation"))
+ implementation(project(":compose:material:material"))
+ implementation(project(":compose:material:material-navigation"))
+}
+
+androidx {
+ name = "Compose Material Navigation Integration Samples"
+ type = LibraryType.SAMPLES
+ inceptionYear = "2024"
+ description = "Samples for Compose Material integration with Navigation"
+}
+
+android {
+ namespace "androidx.compose.material.navigation.samples"
+}
diff --git a/compose/material/material-navigation/samples/src/main/java/androidx/compose/material/navigation/samples/ComposeMaterialNavigationSamples.kt b/compose/material/material-navigation/samples/src/main/java/androidx/compose/material/navigation/samples/ComposeMaterialNavigationSamples.kt
new file mode 100644
index 0000000..bc17bef
--- /dev/null
+++ b/compose/material/material-navigation/samples/src/main/java/androidx/compose/material/navigation/samples/ComposeMaterialNavigationSamples.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.navigation.samples
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.material.navigation.ModalBottomSheetLayout
+import androidx.compose.material.navigation.bottomSheet
+import androidx.compose.material.navigation.rememberBottomSheetNavigator
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import java.util.UUID
+
+private object Destinations {
+ const val Home = "HOME"
+ const val Feed = "FEED"
+ const val Sheet = "SHEET"
+}
+
+@Composable
+fun BottomSheetNavDemo() {
+ val bottomSheetNavigator = rememberBottomSheetNavigator()
+ val navController = rememberNavController(bottomSheetNavigator)
+
+ ModalBottomSheetLayout(bottomSheetNavigator) {
+ NavHost(navController, Destinations.Home) {
+ composable(Destinations.Home) {
+ HomeScreen(
+ showSheet = {
+ navController.navigate(Destinations.Sheet + "?arg=From Home Screen")
+ },
+ showFeed = { navController.navigate(Destinations.Feed) }
+ )
+ }
+ composable(Destinations.Feed) { Text("Feed!") }
+ bottomSheet(Destinations.Sheet + "?arg={arg}") { backstackEntry ->
+ val arg = backstackEntry.arguments?.getString("arg") ?: "Missing argument :("
+ BottomSheet(
+ showFeed = { navController.navigate(Destinations.Feed) },
+ showAnotherSheet = {
+ navController.navigate(Destinations.Sheet + "?arg=${UUID.randomUUID()}")
+ },
+ arg = arg
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun HomeScreen(showSheet: () -> Unit, showFeed: () -> Unit) {
+ Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
+ Text("Body")
+ Button(onClick = showSheet) {
+ Text("Show sheet!")
+ }
+ Button(onClick = showFeed) {
+ Text("Navigate to Feed")
+ }
+ }
+}
+
+@Composable
+private fun BottomSheet(showFeed: () -> Unit, showAnotherSheet: () -> Unit, arg: String) {
+ Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
+ Text("Sheet with arg: $arg")
+ Button(onClick = showFeed) {
+ Text("Click me to navigate!")
+ }
+ Button(onClick = showAnotherSheet) {
+ Text("Click me to show another sheet!")
+ }
+ }
+}
diff --git a/compose/material/material-navigation/src/androidTest/java/androidx/compose/material/navigation/BottomSheetNavigatorTest.kt b/compose/material/material-navigation/src/androidTest/java/androidx/compose/material/navigation/BottomSheetNavigatorTest.kt
new file mode 100644
index 0000000..964957f
--- /dev/null
+++ b/compose/material/material-navigation/src/androidTest/java/androidx/compose/material/navigation/BottomSheetNavigatorTest.kt
@@ -0,0 +1,862 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.navigation
+
+import android.os.Bundle
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Button
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.ModalBottomSheetState
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.Text
+import androidx.compose.runtime.DisposableEffect
+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.platform.testTag
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.height
+import androidx.lifecycle.Lifecycle
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import androidx.navigation.testing.TestNavigatorState
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.math.roundToLong
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+internal class BottomSheetNavigatorTest {
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun testNavigateAddsDestinationToBackStack(): Unit = runBlocking {
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density)
+ val navigatorState = TestNavigatorState()
+ val navigator = BottomSheetNavigator(sheetState)
+
+ navigator.onAttach(navigatorState)
+ val entry = navigatorState.createBackStackEntry(navigator.createFakeDestination(), null)
+ navigator.navigate(listOf(entry), null, null)
+
+ assertWithMessage("The back stack entry has been added to the back stack")
+ .that(navigatorState.backStack.value)
+ .containsExactly(entry)
+ }
+
+ @Test
+ fun testNavigateAddsDestinationToBackStackAndKeepsPrevious(): Unit = runBlocking {
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density)
+ val navigator = BottomSheetNavigator(sheetState)
+ val navigatorState = TestNavigatorState()
+
+ navigator.onAttach(navigatorState)
+ val firstEntry =
+ navigatorState.createBackStackEntry(navigator.createFakeDestination(), null)
+ val secondEntry =
+ navigatorState.createBackStackEntry(navigator.createFakeDestination(), null)
+
+ navigator.navigate(listOf(firstEntry), null, null)
+ assertWithMessage("The first entry has been added to the back stack")
+ .that(navigatorState.backStack.value)
+ .containsExactly(firstEntry)
+
+ navigator.navigate(listOf(secondEntry), null, null)
+ assertWithMessage(
+ "The second entry has been added to the back stack and it still " +
+ "contains the first entry"
+ )
+ .that(navigatorState.backStack.value)
+ .containsExactly(firstEntry, secondEntry)
+ .inOrder()
+ }
+
+ @Test
+ fun testNavigateComposesDestinationAndDisposesPreviousDestination(): Unit = runBlocking {
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density)
+ val navigator = BottomSheetNavigator(sheetState)
+ val navigatorState = TestNavigatorState()
+ navigator.onAttach(navigatorState)
+
+ composeTestRule.setContent {
+ Column { navigator.sheetContent(this) }
+ }
+
+ var firstDestinationCompositions = 0
+ val firstDestinationContentTag = "firstSheetContentTest"
+ val firstDestination = BottomSheetNavigator.Destination(navigator) {
+ DisposableEffect(Unit) {
+ firstDestinationCompositions++
+ onDispose { firstDestinationCompositions = 0 }
+ }
+ Text("Fake Sheet Content", Modifier.testTag(firstDestinationContentTag))
+ }
+ val firstEntry = navigatorState.createBackStackEntry(firstDestination, null)
+
+ var secondDestinationCompositions = 0
+ val secondDestinationContentTag = "secondSheetContentTest"
+ val secondDestination = BottomSheetNavigator.Destination(navigator) {
+ DisposableEffect(Unit) {
+ secondDestinationCompositions++
+ onDispose { secondDestinationCompositions = 0 }
+ }
+ Box(
+ Modifier
+ .size(64.dp)
+ .testTag(secondDestinationContentTag)
+ )
+ }
+ val secondEntry = navigatorState.createBackStackEntry(secondDestination, null)
+
+ navigator.navigate(listOf(firstEntry), null, null)
+ composeTestRule.awaitIdle()
+
+ composeTestRule.onNodeWithTag(firstDestinationContentTag).assertExists()
+ composeTestRule.onNodeWithTag(secondDestinationContentTag).assertDoesNotExist()
+ assertWithMessage("First destination should have been composed exactly once")
+ .that(firstDestinationCompositions).isEqualTo(1)
+ assertWithMessage("Second destination should not have been composed yet")
+ .that(secondDestinationCompositions).isEqualTo(0)
+
+ navigator.navigate(listOf(secondEntry), null, null)
+ composeTestRule.awaitIdle()
+
+ composeTestRule.onNodeWithTag(firstDestinationContentTag).assertDoesNotExist()
+ composeTestRule.onNodeWithTag(secondDestinationContentTag).assertExists()
+ assertWithMessage("First destination has not been disposed")
+ .that(firstDestinationCompositions).isEqualTo(0)
+ assertWithMessage("Second destination should have been composed exactly once")
+ .that(secondDestinationCompositions).isEqualTo(1)
+ }
+
+ @Test
+ fun testBackStackEntryPoppedAfterManualSheetDismiss(): Unit = runBlocking {
+ val navigatorState = TestNavigatorState()
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density)
+ val navigator = BottomSheetNavigator(sheetState = sheetState)
+ navigator.onAttach(navigatorState)
+
+ val bodyContentTag = "testBodyContent"
+
+ composeTestRule.setContent {
+ ModalBottomSheetLayout(
+ bottomSheetNavigator = navigator,
+ content = {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .testTag(bodyContentTag)
+ )
+ }
+ )
+ }
+
+ val destination = BottomSheetNavigator.Destination(
+ navigator = navigator,
+ content = { Box(Modifier.height(20.dp)) }
+ )
+ val backStackEntry = navigatorState.createBackStackEntry(destination, null)
+ navigator.navigate(listOf(backStackEntry), null, null)
+ composeTestRule.awaitIdle()
+
+ assertWithMessage("Navigated to destination")
+ .that(navigatorState.backStack.value)
+ .containsExactly(backStackEntry)
+ assertWithMessage("Bottom sheet shown")
+ .that(sheetState.isVisible).isTrue()
+
+ composeTestRule.onNodeWithTag(bodyContentTag).performClick()
+ composeTestRule.awaitIdle()
+ assertWithMessage("Sheet should be hidden")
+ .that(sheetState.isVisible).isFalse()
+ assertThat(navigatorState.transitionsInProgress.value).isEmpty()
+ assertWithMessage("Back stack entry should be popped off the back stack")
+ .that(navigatorState.backStack.value)
+ .isEmpty()
+ }
+
+ @Test
+ fun testSheetShownAfterNavControllerRestoresState() = runBlocking {
+ lateinit var navController: NavHostController
+ lateinit var navigator: BottomSheetNavigator
+ var savedState: Bundle? = null
+ var compositionState by mutableStateOf(0)
+
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density)
+ val textInSheetTag = "textInSheet"
+
+ composeTestRule.setContent {
+ navigator = remember { BottomSheetNavigator(sheetState) }
+ navController = rememberNavController(navigator)
+ if (savedState != null) navController.restoreState(savedState)
+ if (compositionState == 0) {
+ ModalBottomSheetLayout(
+ bottomSheetNavigator = navigator
+ ) {
+ NavHost(navController, startDestination = "first") {
+ bottomSheet("first") {
+ Text("Hello!", Modifier.testTag(textInSheetTag))
+ }
+ }
+ }
+ }
+ }
+
+ savedState = navController.saveState()
+
+ // Dispose the ModalBottomSheetLayout
+ compositionState = 1
+ composeTestRule.awaitIdle()
+
+ composeTestRule.onNodeWithTag(textInSheetTag).assertDoesNotExist()
+
+ // Recompose with the ModalBottomSheetLayout
+ compositionState = 0
+ composeTestRule.awaitIdle()
+
+ assertWithMessage("Destination is first destination")
+ .that(navController.currentDestination?.route)
+ .isEqualTo("first")
+ assertWithMessage("Bottom sheet is visible")
+ .that(sheetState.isVisible).isTrue()
+ }
+
+ @Test
+ fun testNavigateCompletesEntriesTransitions() = runBlocking {
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density)
+ val navigator = BottomSheetNavigator(sheetState)
+ val navigatorState = TestNavigatorState()
+
+ navigator.onAttach(navigatorState)
+
+ composeTestRule.setContent {
+ ModalBottomSheetLayout(
+ bottomSheetNavigator = navigator,
+ content = { Box(Modifier.fillMaxSize()) }
+ )
+ }
+
+ val backStackEntry1 = navigatorState.createBackStackEntry(
+ navigator.createFakeDestination(), null
+ )
+ val backStackEntry2 = navigatorState.createBackStackEntry(
+ navigator.createFakeDestination(), null
+ )
+
+ navigator.navigate(
+ entries = listOf(backStackEntry1, backStackEntry2),
+ navOptions = null,
+ navigatorExtras = null
+ )
+
+ composeTestRule.awaitIdle()
+
+ assertThat(navigatorState.transitionsInProgress.value).doesNotContain(backStackEntry1)
+ assertThat(navigatorState.transitionsInProgress.value).doesNotContain(backStackEntry2)
+ assertThat(backStackEntry2.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+ }
+
+ @Test
+ fun testComposeSheetContentBeforeNavigatorAttached(): Unit = runBlocking {
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density)
+ val navigator = BottomSheetNavigator(sheetState)
+ val navigatorState = TestNavigatorState()
+
+ composeTestRule.setContent {
+ ModalBottomSheetLayout(
+ bottomSheetNavigator = navigator,
+ content = { Box(Modifier.fillMaxSize()) }
+ )
+ }
+
+ // Attach the state only after accessing the navigator's sheetContent in
+ // ModalBottomSheetLayout
+ navigator.onAttach(navigatorState)
+
+ val entry = navigatorState.createBackStackEntry(
+ navigator.createFakeDestination(), null
+ )
+
+ navigator.navigate(
+ entries = listOf(entry),
+ navOptions = null,
+ navigatorExtras = null
+ )
+
+ composeTestRule.awaitIdle()
+
+ assertWithMessage("The back stack entry has been added to the back stack")
+ .that(navigatorState.backStack.value)
+ .containsExactly(entry)
+ }
+
+ @Test
+ fun testBackPressedDestroysEntry() {
+ lateinit var onBackPressedDispatcher: OnBackPressedDispatcher
+ lateinit var navController: NavHostController
+
+ composeTestRule.setContent {
+ val bottomSheetNavigator = rememberBottomSheetNavigator()
+ navController = rememberNavController(bottomSheetNavigator)
+ onBackPressedDispatcher =
+ LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher!!
+
+ ModalBottomSheetLayout(bottomSheetNavigator) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ NavHost(
+ navController = navController,
+ startDestination = "mainScreen"
+ ) {
+
+ composable(
+ route = "mainScreen",
+ content = {
+ Button(onClick = { navController.navigate("bottomSheet") }) {
+ Text(text = "open drawer")
+ }
+ }
+ )
+
+ bottomSheet(
+ route = "bottomSheet",
+ content = {
+ Box(modifier = Modifier.fillMaxSize()) {
+ Text(
+ text = "bottomSheet"
+ )
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+
+ composeTestRule.onNodeWithText("open drawer").performClick()
+
+ lateinit var bottomSheetEntry: NavBackStackEntry
+
+ composeTestRule.runOnIdle {
+ bottomSheetEntry = navController.currentBackStackEntry!!
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ composeTestRule.runOnIdle {
+ assertThat(bottomSheetEntry.lifecycle.currentState).isEqualTo(Lifecycle.State.DESTROYED)
+ }
+ }
+
+ @Test
+ fun testSheetContentSizeChangeDuringAnimation_opensSheet_shortSheetToShortSheet() {
+ lateinit var navigator: BottomSheetNavigator
+ lateinit var navController: NavHostController
+ var height: Dp by mutableStateOf(20.dp)
+ lateinit var sheetNavBackStackEntry: NavBackStackEntry
+ val homeDestination = "home"
+ val sheetDestination = "sheet"
+
+ composeTestRule.setContent {
+ navigator = rememberBottomSheetNavigator()
+ navController = rememberNavController(navigator)
+ ModalBottomSheetLayout(navigator) {
+ NavHost(navController, homeDestination) {
+ composable(homeDestination) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(Color.Blue)
+ )
+ }
+ bottomSheet(sheetDestination) { backStackEntry ->
+ sheetNavBackStackEntry = backStackEntry
+ Box(
+ Modifier
+ .height(height)
+ .fillMaxWidth()
+ .background(Color.Red)
+ )
+ }
+ }
+ }
+ }
+
+ composeTestRule.mainClock.autoAdvance = false
+ composeTestRule.runOnUiThread { navController.navigate(sheetDestination) }
+ composeTestRule.mainClock.advanceTimeBy(100)
+
+ assertThat(navigator.transitionsInProgress.value.lastOrNull())
+ .isEqualTo(sheetNavBackStackEntry)
+
+ height = (composeTestRule.onRoot().getUnclippedBoundsInRoot().height) / 3
+
+ composeTestRule.mainClock.autoAdvance = true
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.isVisible).isTrue()
+
+ assertThat(navigator.transitionsInProgress.value).isEmpty()
+
+ composeTestRule.runOnUiThread { navController.navigate(homeDestination) }
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.isVisible).isFalse()
+ }
+
+ @Test
+ fun testSheetContentSizeChangeDuringAnimation_opensSheet_shortSheetToTallSheet() {
+ lateinit var navigator: BottomSheetNavigator
+ lateinit var navController: NavHostController
+ var height: Dp by mutableStateOf(20.dp)
+ lateinit var sheetNavBackStackEntry: NavBackStackEntry
+ val homeDestination = "home"
+ val sheetDestination = "sheet"
+
+ composeTestRule.setContent {
+ navigator = rememberBottomSheetNavigator()
+ navController = rememberNavController(navigator)
+ ModalBottomSheetLayout(navigator) {
+ NavHost(navController, homeDestination) {
+ composable(homeDestination) {
+ Box(Modifier.fillMaxSize().background(Color.Blue))
+ }
+ bottomSheet(sheetDestination) { backStackEntry ->
+ sheetNavBackStackEntry = backStackEntry
+ Box(Modifier.height(height).fillMaxWidth().background(Color.Red))
+ }
+ }
+ }
+ }
+
+ composeTestRule.mainClock.autoAdvance = false
+ composeTestRule.runOnUiThread { navController.navigate(sheetDestination) }
+ composeTestRule.mainClock.advanceTimeBy(100)
+ assertThat(navigator.transitionsInProgress.value.lastOrNull())
+ .isEqualTo(sheetNavBackStackEntry)
+
+ height = (composeTestRule.onRoot().getUnclippedBoundsInRoot().height) / 0.9f
+
+ composeTestRule.mainClock.autoAdvance = true
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.isVisible).isTrue()
+
+ assertThat(navigator.transitionsInProgress.value).isEmpty()
+
+ composeTestRule.runOnUiThread { navController.navigate(homeDestination) }
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.isVisible).isFalse()
+ }
+
+ @Test
+ fun testSheetContentSizeChangeDuringAnimation_opensSheet_tallSheetToTallSheet() {
+ lateinit var navigator: BottomSheetNavigator
+ lateinit var navController: NavHostController
+ lateinit var sheetNavBackStackEntry: NavBackStackEntry
+ var height: Dp by mutableStateOf(0.dp)
+ val homeDestination = "home"
+ val sheetDestination = "sheet"
+
+ composeTestRule.setContent {
+ navigator = rememberBottomSheetNavigator()
+ navController = rememberNavController(navigator)
+ ModalBottomSheetLayout(navigator) {
+ NavHost(navController, homeDestination) {
+ composable(homeDestination) {
+ Box(Modifier.fillMaxSize().background(Color.Blue))
+ }
+ bottomSheet(sheetDestination) { backStackEntry ->
+ sheetNavBackStackEntry = backStackEntry
+ Box(Modifier.height(height).fillMaxWidth().background(Color.Red))
+ }
+ }
+ }
+ }
+
+ val rootHeight = composeTestRule.onRoot().getUnclippedBoundsInRoot().height
+ height = rootHeight
+
+ composeTestRule.mainClock.autoAdvance = false
+ composeTestRule.runOnUiThread { navController.navigate(sheetDestination) }
+ composeTestRule.mainClock.advanceTimeBy(100)
+ assertThat(navigator.transitionsInProgress.value.lastOrNull())
+ .isEqualTo(sheetNavBackStackEntry)
+
+ height = (composeTestRule.onRoot().getUnclippedBoundsInRoot().height) / 0.9f
+
+ composeTestRule.mainClock.autoAdvance = true
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.isVisible).isTrue()
+
+ assertThat(navigator.transitionsInProgress.value).isEmpty()
+
+ composeTestRule.runOnUiThread { navController.navigate(homeDestination) }
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.isVisible).isFalse()
+ }
+
+ @Test
+ fun testSheetContentSizeChangeDuringAnimation_opensSheet_tallSheetToShortSheet() {
+ lateinit var navigator: BottomSheetNavigator
+ lateinit var navController: NavHostController
+ var height: Dp by mutableStateOf(0.dp)
+ lateinit var sheetNavBackStackEntry: NavBackStackEntry
+ val homeDestination = "home"
+ val sheetDestination = "sheet"
+
+ composeTestRule.setContent {
+ navigator = rememberBottomSheetNavigator()
+ navController = rememberNavController(navigator)
+ ModalBottomSheetLayout(navigator) {
+ NavHost(navController, homeDestination) {
+ composable(homeDestination) {
+ Box(Modifier.fillMaxSize().background(Color.Blue))
+ }
+ bottomSheet(sheetDestination) { backStackEntry ->
+ sheetNavBackStackEntry = backStackEntry
+ Box(Modifier.height(height).fillMaxWidth().background(Color.Red))
+ }
+ }
+ }
+ }
+
+ val rootHeight = composeTestRule.onRoot().getUnclippedBoundsInRoot().height
+ height = rootHeight
+
+ composeTestRule.mainClock.autoAdvance = false
+ composeTestRule.runOnUiThread { navController.navigate(sheetDestination) }
+ composeTestRule.mainClock.advanceTimeBy(100)
+ assertThat(navigator.transitionsInProgress.value.lastOrNull())
+ .isEqualTo(sheetNavBackStackEntry)
+
+ height = (composeTestRule.onRoot().getUnclippedBoundsInRoot().height) / 3f
+
+ composeTestRule.mainClock.autoAdvance = true
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.isVisible).isTrue()
+
+ assertThat(navigator.transitionsInProgress.value).isEmpty()
+
+ composeTestRule.runOnUiThread { navController.navigate(homeDestination) }
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.isVisible).isFalse()
+ }
+
+ @OptIn(ExperimentalMaterialApi::class)
+ @Test
+ fun testPopBackStackHidesSheetWithAnimation() {
+ val animationDuration = 2000
+ val animationSpec = tween<Float>(animationDuration)
+ lateinit var navigator: BottomSheetNavigator
+ lateinit var navController: NavHostController
+
+ composeTestRule.setContent {
+ navigator = rememberBottomSheetNavigator(animationSpec)
+ navController = rememberNavController(navigator)
+ ModalBottomSheetLayout(navigator) {
+ NavHost(navController, "first") {
+ composable("first") {
+ Box(Modifier.fillMaxSize())
+ }
+ bottomSheet("sheet") {
+ Box(Modifier.height(200.dp))
+ }
+ }
+ }
+ }
+
+ composeTestRule.runOnUiThread { navController.navigate("sheet") }
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.isVisible).isTrue()
+
+ composeTestRule.mainClock.autoAdvance = false
+ composeTestRule.runOnUiThread { navController.popBackStack() }
+
+ val firstAnimationTimeBreakpoint = (animationDuration * 0.9).roundToLong()
+
+ composeTestRule.mainClock.advanceTimeBy(firstAnimationTimeBreakpoint)
+ assertThat(navigator.navigatorSheetState.currentValue)
+ .isAnyOf(ModalBottomSheetValue.HalfExpanded, ModalBottomSheetValue.Expanded)
+ assertThat(navigator.navigatorSheetState.targetValue)
+ .isEqualTo(ModalBottomSheetValue.Hidden)
+
+ composeTestRule.runOnUiThread { navController.navigate("first") }
+
+ composeTestRule.mainClock.autoAdvance = true
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.currentValue)
+ .isEqualTo(ModalBottomSheetValue.Hidden)
+ }
+
+ @Test
+ fun testTapOnScrimDismissesSheetAndPopsBackStack() {
+ val animationDuration = 2000
+ val animationSpec = tween<Float>(animationDuration)
+ lateinit var navigator: BottomSheetNavigator
+ lateinit var navController: NavHostController
+ val sheetLayoutTestTag = "sheetLayout"
+ val homeDestination = "home"
+ val sheetDestination = "sheet"
+
+ composeTestRule.setContent {
+ navigator = rememberBottomSheetNavigator(animationSpec)
+ navController = rememberNavController(navigator)
+ ModalBottomSheetLayout(navigator, Modifier.testTag(sheetLayoutTestTag)) {
+ NavHost(navController, homeDestination) {
+ composable(homeDestination) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(Color.Red)
+ )
+ }
+ bottomSheet(sheetDestination) {
+ Box(
+ Modifier
+ .height(200.dp)
+ .fillMaxWidth()
+ .background(Color.Green)
+ ) {
+ Text("Hello!")
+ }
+ }
+ }
+ }
+ }
+
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo(
+ homeDestination
+ )
+ assertThat(navigator.navigatorSheetState.isVisible).isFalse()
+
+ composeTestRule.runOnUiThread { navController.navigate(sheetDestination) }
+ composeTestRule.waitForIdle()
+
+ assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo(
+ sheetDestination
+ )
+ assertThat(navController.currentBackStackEntry?.lifecycle?.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+ assertThat(navigator.navigatorSheetState.isVisible).isTrue()
+
+ composeTestRule.onNodeWithTag(sheetLayoutTestTag)
+ .performTouchInput { click(position = topCenter) }
+
+ composeTestRule.waitForIdle()
+ assertThat(navigator.navigatorSheetState.isVisible).isFalse()
+ }
+
+ @Test
+ fun testNavigatingFromSheetToSheetDismissesAndThenShowsSheet() {
+ val animationDuration = 2000
+ val animationSpec = tween<Float>(animationDuration)
+ lateinit var navigator: BottomSheetNavigator
+ lateinit var navController: NavHostController
+ val sheetLayoutTestTag = "sheetLayout"
+ val homeDestination = "home"
+ val firstSheetDestination = "sheet1"
+ val secondSheetDestination = "sheet2"
+
+ composeTestRule.setContent {
+ navigator = rememberBottomSheetNavigator(animationSpec)
+ navController = rememberNavController(navigator)
+ ModalBottomSheetLayout(navigator, Modifier.testTag(sheetLayoutTestTag)) {
+ NavHost(navController, homeDestination) {
+ composable(homeDestination) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(Color.Red)
+ )
+ }
+ bottomSheet(firstSheetDestination) {
+ Box(
+ Modifier
+ .height(200.dp)
+ .fillMaxWidth()
+ .background(Color.Green)
+ ) {
+ Text("Hello!")
+ }
+ }
+ bottomSheet(secondSheetDestination) {
+ Box(
+ Modifier
+ .height(200.dp)
+ .fillMaxWidth()
+ .background(Color.Blue)
+ ) {
+ Text("Hello!")
+ }
+ }
+ }
+ }
+ }
+
+ assertThat(navController.currentBackStackEntry?.destination?.route)
+ .isEqualTo(homeDestination)
+
+ composeTestRule.runOnUiThread { navController.navigate(firstSheetDestination) }
+ composeTestRule.waitForIdle()
+
+ assertThat(navController.currentBackStackEntry?.destination?.route)
+ .isEqualTo(firstSheetDestination)
+ assertThat(navigator.sheetState.currentValue)
+ .isAnyOf(ModalBottomSheetValue.HalfExpanded, ModalBottomSheetValue.Expanded)
+
+ composeTestRule.mainClock.autoAdvance = false
+ composeTestRule.runOnUiThread { navController.navigate(secondSheetDestination) }
+
+ composeTestRule.mainClock.advanceTimeUntil { navigator.sheetState.isAnimationRunning }
+ composeTestRule.mainClock.advanceTimeBy(animationDuration.toLong())
+ composeTestRule.mainClock.advanceTimeByFrame()
+
+ assertThat(navigator.sheetState.currentValue).isEqualTo(ModalBottomSheetValue.Hidden)
+
+ composeTestRule.mainClock.advanceTimeUntil { navigator.sheetState.isAnimationRunning }
+ composeTestRule.mainClock.advanceTimeBy(animationDuration.toLong())
+ composeTestRule.mainClock.advanceTimeByFrame()
+
+ assertThat(navigator.sheetState.currentValue)
+ .isAnyOf(ModalBottomSheetValue.HalfExpanded, ModalBottomSheetValue.Expanded)
+ assertThat(navController.currentBackStackEntry?.destination?.route)
+ .isEqualTo(secondSheetDestination)
+
+ composeTestRule.runOnUiThread {
+ navController.popBackStack(firstSheetDestination, inclusive = false)
+ }
+ composeTestRule.mainClock.advanceTimeBy(animationDuration.toLong())
+ composeTestRule.mainClock.advanceTimeByFrame()
+
+ assertThat(navigator.sheetState.currentValue).isEqualTo(ModalBottomSheetValue.Hidden)
+
+ composeTestRule.mainClock.autoAdvance = true
+ composeTestRule.waitForIdle()
+
+ assertThat(navigator.sheetState.currentValue)
+ .isAnyOf(ModalBottomSheetValue.HalfExpanded, ModalBottomSheetValue.Expanded)
+ assertThat(navController.currentBackStackEntry?.destination?.route)
+ .isEqualTo(firstSheetDestination)
+ }
+
+ @Test
+ fun testBackPressWithNestedGraphBehind() {
+ lateinit var navigator: BottomSheetNavigator
+ lateinit var navController: NavHostController
+ lateinit var nestedNavController: NavHostController
+ lateinit var backDispatcher: OnBackPressedDispatcher
+ val homeDestination = "home"
+ val firstSheetDestination = "sheet1"
+ val firstNestedDestination = "nested1"
+ val secondNestedDestination = "nested2"
+
+ composeTestRule.setContent {
+ backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher!!
+ navigator = rememberBottomSheetNavigator()
+ navController = rememberNavController(navigator)
+ ModalBottomSheetLayout(navigator) {
+ NavHost(navController, homeDestination) {
+ composable(homeDestination) {
+ nestedNavController = rememberNavController()
+ NavHost(nestedNavController, "nested1") {
+ composable(firstNestedDestination) { }
+ composable(secondNestedDestination) { }
+ }
+ }
+ bottomSheet(firstSheetDestination) {
+ Text("SheetDestination")
+ }
+ }
+ }
+ }
+
+ assertThat(navController.currentBackStackEntry?.destination?.route)
+ .isEqualTo(homeDestination)
+
+ composeTestRule.runOnUiThread {
+ nestedNavController.navigate(secondNestedDestination)
+ }
+ composeTestRule.waitForIdle()
+
+ assertThat(navController.currentBackStackEntry?.destination?.route)
+ .isEqualTo(homeDestination)
+ assertThat(nestedNavController.currentBackStackEntry?.destination?.route)
+ .isEqualTo(secondNestedDestination)
+
+ composeTestRule.runOnUiThread {
+ navController.navigate(firstSheetDestination)
+ }
+ composeTestRule.waitForIdle()
+
+ assertThat(navigator.sheetState.currentValue)
+ .isAnyOf(ModalBottomSheetValue.HalfExpanded, ModalBottomSheetValue.Expanded)
+
+ composeTestRule.runOnUiThread {
+ backDispatcher.onBackPressed()
+ }
+ composeTestRule.waitForIdle()
+
+ assertThat(navController.currentBackStackEntry?.destination?.route)
+ .isEqualTo(homeDestination)
+ assertThat(nestedNavController.currentBackStackEntry?.destination?.route)
+ .isEqualTo(secondNestedDestination)
+
+ assertThat(navigator.sheetState.currentValue).isEqualTo(ModalBottomSheetValue.Hidden)
+ }
+
+ private fun BottomSheetNavigator.createFakeDestination() =
+ BottomSheetNavigator.Destination(this) {
+ Text("Fake Sheet Content")
+ }
+
+ private val ModalBottomSheetState.isAnimationRunning get() = currentValue != targetValue
+}
diff --git a/compose/material/material-navigation/src/androidTest/java/androidx/compose/material/navigation/NavGraphBuilderTest.kt b/compose/material/material-navigation/src/androidTest/java/androidx/compose/material/navigation/NavGraphBuilderTest.kt
new file mode 100644
index 0000000..77192b7
--- /dev/null
+++ b/compose/material/material-navigation/src/androidTest/java/androidx/compose/material/navigation/NavGraphBuilderTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.navigation
+
+import android.net.Uri
+import androidx.compose.material.ModalBottomSheetState
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.core.net.toUri
+import androidx.navigation.NavDeepLinkRequest
+import androidx.navigation.compose.NavHost
+import androidx.navigation.navArgument
+import androidx.navigation.navDeepLink
+import androidx.navigation.plusAssign
+import androidx.navigation.testing.TestNavHostController
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+internal class NavGraphBuilderTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun testCurrentBackStackEntryNavigate() {
+ lateinit var navController: TestNavHostController
+ val key = "key"
+ val arg = "myarg"
+ composeTestRule.setContent {
+ navController = TestNavHostController(LocalContext.current)
+ navController.navigatorProvider += createBottomSheetNavigator()
+
+ NavHost(navController, startDestination = firstRoute) {
+ bottomSheet(firstRoute) { }
+ bottomSheet("$secondRoute/{$key}") { }
+ }
+ }
+
+ composeTestRule.runOnUiThread {
+ navController.navigate("$secondRoute/$arg")
+ assertThat(navController.currentBackStackEntry!!.arguments!!.getString(key))
+ .isEqualTo(arg)
+ }
+ }
+
+ @Test
+ fun testDefaultArguments() {
+ lateinit var navController: TestNavHostController
+ val key = "key"
+ val defaultArg = "default"
+ composeTestRule.setContent {
+ navController = TestNavHostController(LocalContext.current)
+ navController.navigatorProvider += createBottomSheetNavigator()
+
+ NavHost(navController, startDestination = firstRoute) {
+ bottomSheet(firstRoute) { }
+ bottomSheet(
+ secondRoute,
+ arguments = listOf(navArgument(key) { defaultValue = defaultArg })
+ ) { }
+ }
+ }
+
+ composeTestRule.runOnUiThread {
+ navController.navigate(secondRoute)
+ assertThat(navController.currentBackStackEntry!!.arguments!!.getString(key))
+ .isEqualTo(defaultArg)
+ }
+ }
+
+ @Test
+ fun testDeepLink() {
+ lateinit var navController: TestNavHostController
+ val uriString = "https://www.example.com"
+ val deeplink = NavDeepLinkRequest.Builder.fromUri(Uri.parse(uriString)).build()
+ composeTestRule.setContent {
+ navController = TestNavHostController(LocalContext.current)
+ navController.navigatorProvider += createBottomSheetNavigator()
+
+ NavHost(navController, startDestination = firstRoute) {
+ bottomSheet(firstRoute) { }
+ bottomSheet(
+ secondRoute,
+ deepLinks = listOf(navDeepLink { uriPattern = uriString })
+ ) { }
+ }
+ }
+
+ composeTestRule.runOnUiThread {
+ navController.navigate(uriString.toUri())
+ assertThat(navController.currentBackStackEntry!!.destination.hasDeepLink(deeplink))
+ .isTrue()
+ }
+ }
+
+ private fun createBottomSheetNavigator() =
+ BottomSheetNavigator(sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density))
+}
+
+private const val firstRoute = "first"
+private const val secondRoute = "second"
diff --git a/compose/material/material-navigation/src/androidTest/java/androidx/compose/material/navigation/SheetContentHostTest.kt b/compose/material/material-navigation/src/androidTest/java/androidx/compose/material/navigation/SheetContentHostTest.kt
new file mode 100644
index 0000000..0b03487d
--- /dev/null
+++ b/compose/material/material-navigation/src/androidTest/java/androidx/compose/material/navigation/SheetContentHostTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.navigation
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetState
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveableStateHolder
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.testing.TestNavigatorState
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.test.runTest
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+internal class SheetContentHostTest {
+ private val bodyContentTag = "testBodyContent"
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun testOnSheetDismissedCalled_ManualDismiss() = runTest {
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density)
+ val backStackEntry = createBackStackEntry(sheetState)
+
+ val dismissedBackStackEntries = mutableListOf<NavBackStackEntry>()
+
+ composeTestRule.setBottomSheetContent(
+ mutableStateOf(backStackEntry),
+ sheetState,
+ onSheetShown = { },
+ onSheetDismissed = { entry -> dismissedBackStackEntries.add(entry) }
+ )
+
+ assertThat(sheetState.currentValue == ModalBottomSheetValue.Expanded).isTrue()
+ composeTestRule.onNodeWithTag(bodyContentTag).performClick()
+ composeTestRule.runOnIdle {
+ assertWithMessage("Sheet is visible")
+ .that(sheetState.isVisible).isFalse()
+ assertWithMessage("Back stack entry should be in the dismissed entries list")
+ .that(dismissedBackStackEntries)
+ .containsExactly(backStackEntry)
+ }
+ }
+
+ @Ignore // b/326117689 to address ModalBottomSheet.show() changing target state to HIDDEN
+ @Test
+ fun testOnSheetDismissedCalled_initiallyExpanded() = runTest {
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Expanded, composeTestRule.density)
+ val backStackEntry = createBackStackEntry(sheetState)
+
+ val dismissedBackStackEntries = mutableListOf<NavBackStackEntry>()
+
+ composeTestRule.setBottomSheetContent(
+ mutableStateOf(backStackEntry),
+ sheetState,
+ onSheetShown = { },
+ onSheetDismissed = { entry -> dismissedBackStackEntries.add(entry) }
+ )
+
+ assertThat(sheetState.currentValue == ModalBottomSheetValue.Expanded).isTrue()
+ composeTestRule.onNodeWithTag(bodyContentTag).performClick()
+ composeTestRule.runOnIdle {
+ assertWithMessage("Sheet is not visible")
+ .that(sheetState.isVisible).isFalse()
+ assertWithMessage("Back stack entry should be in the dismissed entries list")
+ .that(dismissedBackStackEntries)
+ .containsExactly(backStackEntry)
+ }
+ }
+
+ @Test
+ fun testOnSheetShownCalled_onBackStackEntryEnter_shortSheet() = runTest {
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density)
+ val backStackEntryState = mutableStateOf<NavBackStackEntry?>(null)
+ val shownBackStackEntries = mutableListOf<NavBackStackEntry>()
+
+ composeTestRule.setBottomSheetContent(
+ backStackEntry = backStackEntryState,
+ sheetState = sheetState,
+ onSheetShown = { entry -> shownBackStackEntries.add(entry) },
+ onSheetDismissed = { }
+ )
+
+ val backStackEntry = createBackStackEntry(sheetState) {
+ Box(Modifier.height(50.dp))
+ }
+ backStackEntryState.value = backStackEntry
+
+ composeTestRule.runOnIdle {
+ assertWithMessage("Sheet is visible")
+ .that(sheetState.isVisible).isTrue()
+ assertWithMessage("Back stack entry should be in the shown entries list")
+ .that(shownBackStackEntries)
+ .containsExactly(backStackEntry)
+ }
+ }
+
+ @Test
+ fun testOnSheetShownCalled_onBackStackEntryEnter_tallSheet() = runTest {
+ val sheetState =
+ ModalBottomSheetState(ModalBottomSheetValue.Hidden, composeTestRule.density)
+ val backStackEntryState = mutableStateOf<NavBackStackEntry?>(null)
+ val shownBackStackEntries = mutableListOf<NavBackStackEntry>()
+
+ composeTestRule.setBottomSheetContent(
+ backStackEntry = backStackEntryState,
+ sheetState = sheetState,
+ onSheetShown = { entry -> shownBackStackEntries.add(entry) },
+ onSheetDismissed = { }
+ )
+
+ val backStackEntry = createBackStackEntry(sheetState) {
+ Box(Modifier.fillMaxSize())
+ }
+ backStackEntryState.value = backStackEntry
+
+ composeTestRule.runOnIdle {
+ assertWithMessage("Sheet is visible")
+ .that(sheetState.isVisible).isTrue()
+ assertWithMessage("Back stack entry should be in the shown entries list")
+ .that(shownBackStackEntries)
+ .containsExactly(backStackEntry)
+ }
+ }
+
+ private fun ComposeContentTestRule.setBottomSheetContent(
+ backStackEntry: State<NavBackStackEntry?>,
+ sheetState: ModalBottomSheetState,
+ onSheetShown: (NavBackStackEntry) -> Unit,
+ onSheetDismissed: (NavBackStackEntry) -> Unit
+ ) {
+ setContent {
+ val saveableStateHolder = rememberSaveableStateHolder()
+ LaunchedEffect(backStackEntry.value) {
+ if (backStackEntry.value == null) sheetState.hide() else sheetState.show()
+ }
+ ModalBottomSheetLayout(
+ sheetContent = {
+ SheetContentHost(
+ backStackEntry = backStackEntry.value,
+ sheetState = sheetState,
+ saveableStateHolder = saveableStateHolder,
+ onSheetShown = onSheetShown,
+ onSheetDismissed = onSheetDismissed
+ )
+ },
+ sheetState = sheetState,
+ content = {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .testTag(bodyContentTag)
+ )
+ }
+ )
+ }
+ }
+
+ private fun createBackStackEntry(
+ sheetState: ModalBottomSheetState,
+ sheetContent:
+ @Composable ColumnScope.(NavBackStackEntry) -> Unit = { Text("Fake Sheet Content") }
+ ): NavBackStackEntry {
+ val navigatorState = TestNavigatorState()
+ val navigator = BottomSheetNavigator(sheetState)
+ navigator.onAttach(navigatorState)
+
+ val destination = BottomSheetNavigator.Destination(navigator, sheetContent)
+ val backStackEntry = navigatorState.createBackStackEntry(destination, null)
+ navigator.navigate(listOf(backStackEntry), null, null)
+ return backStackEntry
+ }
+}
diff --git a/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/BottomSheet.kt b/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/BottomSheet.kt
new file mode 100644
index 0000000..39ccd43
--- /dev/null
+++ b/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/BottomSheet.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.navigation
+
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.ModalBottomSheetDefaults
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+
+/**
+ * Helper function to create a [ModalBottomSheetLayout] from a [BottomSheetNavigator].
+ *
+ * @see [ModalBottomSheetLayout]
+ */
+@Suppress("MissingJvmstatic")
+@Composable
+// Keep defaults in sync with androidx.compose.material.ModalBottomSheetLayout
+public fun ModalBottomSheetLayout(
+ bottomSheetNavigator: BottomSheetNavigator,
+ modifier: Modifier = Modifier,
+ sheetShape: Shape = MaterialTheme.shapes.large,
+ sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
+ sheetBackgroundColor: Color = MaterialTheme.colors.surface,
+ sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
+ scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
+ content: @Composable () -> Unit
+) {
+ ModalBottomSheetLayout(
+ sheetState = bottomSheetNavigator.sheetState,
+ sheetContent = bottomSheetNavigator.sheetContent,
+ modifier = modifier,
+ sheetShape = sheetShape,
+ sheetElevation = sheetElevation,
+ sheetBackgroundColor = sheetBackgroundColor,
+ sheetContentColor = sheetContentColor,
+ scrimColor = scrimColor,
+ content = content
+ )
+}
diff --git a/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/BottomSheetNavigator.kt b/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/BottomSheetNavigator.kt
new file mode 100644
index 0000000..92a2db1
--- /dev/null
+++ b/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/BottomSheetNavigator.kt
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.navigation
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.material.ModalBottomSheetState
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveableStateHolder
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.util.fastForEach
+import androidx.navigation.FloatingWindow
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDestination
+import androidx.navigation.NavOptions
+import androidx.navigation.Navigator
+import androidx.navigation.NavigatorState
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.transform
+
+/**
+ * The state of a [ModalBottomSheetLayout] that the [BottomSheetNavigator] drives
+ *
+ * @param sheetState The sheet state that is driven by the [BottomSheetNavigator]
+ */
+public class BottomSheetNavigatorSheetState(private val sheetState: ModalBottomSheetState) {
+ /**
+ * @see ModalBottomSheetState.isVisible
+ */
+ public val isVisible: Boolean
+ get() = sheetState.isVisible
+
+ /**
+ * @see ModalBottomSheetState.currentValue
+ */
+ public val currentValue: ModalBottomSheetValue
+ get() = sheetState.currentValue
+
+ /**
+ * @see ModalBottomSheetState.targetValue
+ */
+ public val targetValue: ModalBottomSheetValue
+ get() = sheetState.targetValue
+}
+
+/**
+ * Create and remember a [BottomSheetNavigator]
+ */
+@Composable
+public fun rememberBottomSheetNavigator(
+ animationSpec: AnimationSpec<Float> = SpringSpec()
+): BottomSheetNavigator {
+ val sheetState = rememberModalBottomSheetState(
+ ModalBottomSheetValue.Hidden,
+ animationSpec = animationSpec
+ )
+ return remember(sheetState) { BottomSheetNavigator(sheetState) }
+}
+
+/**
+ * Navigator that drives a [ModalBottomSheetState] for use of [ModalBottomSheetLayout]s
+ * with the navigation library. Every destination using this Navigator must set a valid
+ * [Composable] by setting it directly on an instantiated [Destination] or calling
+ * [androidx.compose.material.navigation.bottomSheet].
+ *
+ * <b>The [sheetContent] [Composable] will always host the latest entry of the back stack. When
+ * navigating from a [BottomSheetNavigator.Destination] to another
+ * [BottomSheetNavigator.Destination], the content of the sheet will be replaced instead of a
+ * new bottom sheet being shown.</b>
+ *
+ * When the sheet is dismissed by the user, the [state]'s [NavigatorState.backStack] will be popped.
+ *
+ * The primary constructor is not intended for public use. Please refer to
+ * [rememberBottomSheetNavigator] instead.
+ *
+ * @param sheetState The [ModalBottomSheetState] that the [BottomSheetNavigator] will use to
+ * drive the sheet state
+ */
+@Navigator.Name("bottomSheet")
+public class BottomSheetNavigator(
+ internal val sheetState: ModalBottomSheetState
+) : Navigator<BottomSheetNavigator.Destination>() {
+
+ private var attached by mutableStateOf(false)
+
+ /**
+ * Get the back stack from the [state]. In some cases, the [sheetContent] might be composed
+ * before the Navigator is attached, so we specifically return an empty flow if we aren't
+ * attached yet.
+ */
+ private val backStack: StateFlow<List<NavBackStackEntry>>
+ get() = if (attached) {
+ state.backStack
+ } else {
+ MutableStateFlow(emptyList())
+ }
+
+ /**
+ * Get the transitionsInProgress from the [state]. In some cases, the [sheetContent] might be
+ * composed before the Navigator is attached, so we specifically return an empty flow if we
+ * aren't attached yet.
+ */
+ internal val transitionsInProgress: StateFlow<Set<NavBackStackEntry>>
+ get() = if (attached) {
+ state.transitionsInProgress
+ } else {
+ MutableStateFlow(emptySet())
+ }
+
+ /**
+ * Access properties of the [ModalBottomSheetLayout]'s [ModalBottomSheetState]
+ */
+ public val navigatorSheetState: BottomSheetNavigatorSheetState =
+ BottomSheetNavigatorSheetState(sheetState)
+
+ /**
+ * A [Composable] function that hosts the current sheet content. This should be set as
+ * sheetContent of your [ModalBottomSheetLayout].
+ */
+ internal val sheetContent: @Composable ColumnScope.() -> Unit = {
+ val saveableStateHolder = rememberSaveableStateHolder()
+ val transitionsInProgressEntries by transitionsInProgress.collectAsState()
+
+ // The latest back stack entry, retained until the sheet is completely hidden
+ // While the back stack is updated immediately, we might still be hiding the sheet, so
+ // we keep the entry around until the sheet is hidden
+ val retainedEntry by produceState<NavBackStackEntry?>(
+ initialValue = null,
+ key1 = backStack
+ ) {
+ backStack
+ .transform { backStackEntries ->
+ // Always hide the sheet when the back stack is updated
+ // Regardless of whether we're popping or pushing, we always want to hide
+ // the sheet first before deciding whether to re-show it or keep it hidden
+ try {
+ sheetState.hide()
+ } catch (_: CancellationException) {
+ // We catch but ignore possible cancellation exceptions as we don't want
+ // them to bubble up and cancel the whole produceState coroutine
+ } finally {
+ emit(backStackEntries.lastOrNull())
+ }
+ }
+ .collect {
+ value = it
+ }
+ }
+
+ if (retainedEntry != null) {
+ LaunchedEffect(retainedEntry) {
+ sheetState.show()
+ }
+
+ BackHandler {
+ state.popWithTransition(popUpTo = retainedEntry!!, saveState = false)
+ }
+ }
+
+ SheetContentHost(
+ backStackEntry = retainedEntry,
+ sheetState = sheetState,
+ saveableStateHolder = saveableStateHolder,
+ onSheetShown = {
+ transitionsInProgressEntries.forEach(state::markTransitionComplete)
+ },
+ onSheetDismissed = { backStackEntry ->
+ // Sheet dismissal can be started through popBackStack in which case we have a
+ // transition that we'll want to complete
+ if (transitionsInProgressEntries.contains(backStackEntry)) {
+ state.markTransitionComplete(backStackEntry)
+ }
+ // If there is no transition in progress, the sheet has been dimissed by the
+ // user (for example by tapping on the scrim or through an accessibility action)
+ // In this case, we will immediately pop without a transition as the sheet has
+ // already been hidden
+ else {
+ state.pop(popUpTo = backStackEntry, saveState = false)
+ }
+ }
+ )
+ }
+
+ override fun onAttach(state: NavigatorState) {
+ super.onAttach(state)
+ attached = true
+ }
+
+ override fun createDestination(): Destination = Destination(
+ navigator = this,
+ content = {}
+ )
+
+ override fun navigate(
+ entries: List<NavBackStackEntry>,
+ navOptions: NavOptions?,
+ navigatorExtras: Extras?
+ ) {
+ entries.fastForEach { entry ->
+ state.pushWithTransition(entry)
+ }
+ }
+
+ override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
+ state.popWithTransition(popUpTo, savedState)
+ }
+
+ /**
+ * [NavDestination] specific to [BottomSheetNavigator]
+ */
+ @NavDestination.ClassType(Composable::class)
+ public class Destination(
+ navigator: BottomSheetNavigator,
+ internal val content: @Composable ColumnScope.(NavBackStackEntry) -> Unit
+ ) : NavDestination(navigator), FloatingWindow
+}
diff --git a/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/NavGraphBuilder.kt b/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/NavGraphBuilder.kt
new file mode 100644
index 0000000..4f1590a
--- /dev/null
+++ b/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/NavGraphBuilder.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.navigation
+
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.util.fastForEach
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDeepLink
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.get
+
+/**
+ * Add the [content] [Composable] as bottom sheet content to the [NavGraphBuilder]
+ *
+ * @param route route for the destination
+ * @param arguments list of arguments to associate with destination
+ * @param deepLinks list of deep links to associate with the destinations
+ * @param content the sheet content at the given destination
+ */
+public fun NavGraphBuilder.bottomSheet(
+ route: String,
+ arguments: List<NamedNavArgument> = emptyList(),
+ deepLinks: List<NavDeepLink> = emptyList(),
+ content: @Composable ColumnScope.(backstackEntry: NavBackStackEntry) -> Unit
+) {
+ addDestination(
+ BottomSheetNavigator.Destination(
+ provider[BottomSheetNavigator::class],
+ content
+ ).apply {
+ this.route = route
+ arguments.fastForEach { (argumentName, argument) ->
+ addArgument(argumentName, argument)
+ }
+ deepLinks.fastForEach { deepLink ->
+ addDeepLink(deepLink)
+ }
+ }
+ )
+}
diff --git a/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/SheetContentHost.kt b/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/SheetContentHost.kt
new file mode 100644
index 0000000..c8c3bc7
--- /dev/null
+++ b/compose/material/material-navigation/src/main/java/androidx/compose/material/navigation/SheetContentHost.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.navigation
+
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.material.ModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.saveable.SaveableStateHolder
+import androidx.compose.runtime.snapshotFlow
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.compose.LocalOwnersProvider
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.drop
+
+/**
+ * Hosts a [BottomSheetNavigator.Destination]'s [NavBackStackEntry] and its
+ * [BottomSheetNavigator.Destination.content] and provides a [onSheetDismissed] callback. It also
+ * shows and hides the [ModalBottomSheetLayout] through the [sheetState] when the sheet content
+ * enters or leaves the composition.
+ *
+ * @param backStackEntry The [NavBackStackEntry] holding the [BottomSheetNavigator.Destination],
+ * or null if there is no [NavBackStackEntry]
+ * @param sheetState The [ModalBottomSheetState] used to observe and control the sheet visibility
+ * @param onSheetDismissed Callback when the sheet has been dismissed. Typically, you'll want to
+ * pop the back stack here.
+ */
+@Composable
+internal fun ColumnScope.SheetContentHost(
+ backStackEntry: NavBackStackEntry?,
+ sheetState: ModalBottomSheetState,
+ saveableStateHolder: SaveableStateHolder,
+ onSheetShown: (entry: NavBackStackEntry) -> Unit,
+ onSheetDismissed: (entry: NavBackStackEntry) -> Unit,
+) {
+ if (backStackEntry != null) {
+ val currentOnSheetShown by rememberUpdatedState(onSheetShown)
+ val currentOnSheetDismissed by rememberUpdatedState(onSheetDismissed)
+ LaunchedEffect(sheetState, backStackEntry) {
+ snapshotFlow { sheetState.isVisible }
+ // We are only interested in changes in the sheet's visibility
+ .distinctUntilChanged()
+ // distinctUntilChanged emits the initial value which we don't need
+ .drop(1)
+ .collect { visible ->
+ if (visible) {
+ currentOnSheetShown(backStackEntry)
+ } else {
+ currentOnSheetDismissed(backStackEntry)
+ }
+ }
+ }
+ backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+ val content =
+ (backStackEntry.destination as BottomSheetNavigator.Destination).content
+ content(backStackEntry)
+ }
+ }
+}
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index 1bdfdec..ee16cb3 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -41,7 +41,7 @@
dependencies {
implementation(libs.kotlinStdlibCommon)
api("androidx.compose.foundation:foundation:1.6.0")
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
implementation("androidx.collection:collection:1.4.0")
implementation("androidx.compose.animation:animation:1.6.0")
@@ -117,7 +117,7 @@
androidx {
name = "Compose Material Ripple"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Material ripple used to build interactive components"
// Disable strict API mode for MPP builds as it will fail to compile androidAndroidTest
diff --git a/compose/material/material/api/current.ignore b/compose/material/material/api/current.ignore
index e3b3000..fb345af 100644
--- a/compose/material/material/api/current.ignore
+++ b/compose/material/material/api/current.ignore
@@ -3,11 +3,5 @@
Class androidx.compose.material.BackdropScaffoldState superclass changed from androidx.compose.material.SwipeableState to java.lang.Object
-ChangedType: androidx.compose.material.BackdropScaffoldState#getCurrentValue():
- Method androidx.compose.material.BackdropScaffoldState.getCurrentValue has changed return type from T (extends java.lang.Object) to androidx.compose.material.BackdropValue
-ChangedType: androidx.compose.material.BackdropScaffoldState#getTargetValue():
- Method androidx.compose.material.BackdropScaffoldState.getTargetValue has changed return type from T (extends java.lang.Object) to androidx.compose.material.BackdropValue
-
-
ParameterNameChange: androidx.compose.material.BackdropScaffoldState#BackdropScaffoldState(androidx.compose.material.BackdropValue, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean>, androidx.compose.material.SnackbarHostState) parameter #2:
Attempted to change parameter name from confirmStateChange to confirmValueChange in constructor androidx.compose.material.BackdropScaffoldState
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index 87b9b57..4606989 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -83,8 +83,6 @@
}
public enum BackdropValue {
- method public static androidx.compose.material.BackdropValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.BackdropValue[] values();
enum_constant public static final androidx.compose.material.BackdropValue Concealed;
enum_constant public static final androidx.compose.material.BackdropValue Revealed;
}
@@ -122,8 +120,6 @@
}
public enum BottomDrawerValue {
- method public static androidx.compose.material.BottomDrawerValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.BottomDrawerValue[] values();
enum_constant public static final androidx.compose.material.BottomDrawerValue Closed;
enum_constant public static final androidx.compose.material.BottomDrawerValue Expanded;
enum_constant public static final androidx.compose.material.BottomDrawerValue Open;
@@ -191,8 +187,6 @@
}
public enum BottomSheetValue {
- method public static androidx.compose.material.BottomSheetValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.BottomSheetValue[] values();
enum_constant public static final androidx.compose.material.BottomSheetValue Collapsed;
enum_constant public static final androidx.compose.material.BottomSheetValue Expanded;
}
@@ -351,8 +345,6 @@
}
public enum DismissDirection {
- method public static androidx.compose.material.DismissDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.DismissDirection[] values();
enum_constant public static final androidx.compose.material.DismissDirection EndToStart;
enum_constant public static final androidx.compose.material.DismissDirection StartToEnd;
}
@@ -372,8 +364,6 @@
}
public enum DismissValue {
- method public static androidx.compose.material.DismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.DismissValue[] values();
enum_constant public static final androidx.compose.material.DismissValue Default;
enum_constant public static final androidx.compose.material.DismissValue DismissedToEnd;
enum_constant public static final androidx.compose.material.DismissValue DismissedToStart;
@@ -425,8 +415,6 @@
}
public enum DrawerValue {
- method public static androidx.compose.material.DrawerValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.DrawerValue[] values();
enum_constant public static final androidx.compose.material.DrawerValue Closed;
enum_constant public static final androidx.compose.material.DrawerValue Open;
}
@@ -581,8 +569,6 @@
}
public enum ModalBottomSheetValue {
- method public static androidx.compose.material.ModalBottomSheetValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.ModalBottomSheetValue[] values();
enum_constant public static final androidx.compose.material.ModalBottomSheetValue Expanded;
enum_constant public static final androidx.compose.material.ModalBottomSheetValue HalfExpanded;
enum_constant public static final androidx.compose.material.ModalBottomSheetValue Hidden;
@@ -759,8 +745,6 @@
}
public enum SnackbarDuration {
- method public static androidx.compose.material.SnackbarDuration valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.SnackbarDuration[] values();
enum_constant public static final androidx.compose.material.SnackbarDuration Indefinite;
enum_constant public static final androidx.compose.material.SnackbarDuration Long;
enum_constant public static final androidx.compose.material.SnackbarDuration Short;
@@ -783,8 +767,6 @@
}
public enum SnackbarResult {
- method public static androidx.compose.material.SnackbarResult valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.SnackbarResult[] values();
enum_constant public static final androidx.compose.material.SnackbarResult ActionPerformed;
enum_constant public static final androidx.compose.material.SnackbarResult Dismissed;
}
diff --git a/compose/material/material/api/restricted_current.ignore b/compose/material/material/api/restricted_current.ignore
index e3b3000..fb345af 100644
--- a/compose/material/material/api/restricted_current.ignore
+++ b/compose/material/material/api/restricted_current.ignore
@@ -3,11 +3,5 @@
Class androidx.compose.material.BackdropScaffoldState superclass changed from androidx.compose.material.SwipeableState to java.lang.Object
-ChangedType: androidx.compose.material.BackdropScaffoldState#getCurrentValue():
- Method androidx.compose.material.BackdropScaffoldState.getCurrentValue has changed return type from T (extends java.lang.Object) to androidx.compose.material.BackdropValue
-ChangedType: androidx.compose.material.BackdropScaffoldState#getTargetValue():
- Method androidx.compose.material.BackdropScaffoldState.getTargetValue has changed return type from T (extends java.lang.Object) to androidx.compose.material.BackdropValue
-
-
ParameterNameChange: androidx.compose.material.BackdropScaffoldState#BackdropScaffoldState(androidx.compose.material.BackdropValue, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean>, androidx.compose.material.SnackbarHostState) parameter #2:
Attempted to change parameter name from confirmStateChange to confirmValueChange in constructor androidx.compose.material.BackdropScaffoldState
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index 87b9b57..4606989 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -83,8 +83,6 @@
}
public enum BackdropValue {
- method public static androidx.compose.material.BackdropValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.BackdropValue[] values();
enum_constant public static final androidx.compose.material.BackdropValue Concealed;
enum_constant public static final androidx.compose.material.BackdropValue Revealed;
}
@@ -122,8 +120,6 @@
}
public enum BottomDrawerValue {
- method public static androidx.compose.material.BottomDrawerValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.BottomDrawerValue[] values();
enum_constant public static final androidx.compose.material.BottomDrawerValue Closed;
enum_constant public static final androidx.compose.material.BottomDrawerValue Expanded;
enum_constant public static final androidx.compose.material.BottomDrawerValue Open;
@@ -191,8 +187,6 @@
}
public enum BottomSheetValue {
- method public static androidx.compose.material.BottomSheetValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.BottomSheetValue[] values();
enum_constant public static final androidx.compose.material.BottomSheetValue Collapsed;
enum_constant public static final androidx.compose.material.BottomSheetValue Expanded;
}
@@ -351,8 +345,6 @@
}
public enum DismissDirection {
- method public static androidx.compose.material.DismissDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.DismissDirection[] values();
enum_constant public static final androidx.compose.material.DismissDirection EndToStart;
enum_constant public static final androidx.compose.material.DismissDirection StartToEnd;
}
@@ -372,8 +364,6 @@
}
public enum DismissValue {
- method public static androidx.compose.material.DismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.DismissValue[] values();
enum_constant public static final androidx.compose.material.DismissValue Default;
enum_constant public static final androidx.compose.material.DismissValue DismissedToEnd;
enum_constant public static final androidx.compose.material.DismissValue DismissedToStart;
@@ -425,8 +415,6 @@
}
public enum DrawerValue {
- method public static androidx.compose.material.DrawerValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.DrawerValue[] values();
enum_constant public static final androidx.compose.material.DrawerValue Closed;
enum_constant public static final androidx.compose.material.DrawerValue Open;
}
@@ -581,8 +569,6 @@
}
public enum ModalBottomSheetValue {
- method public static androidx.compose.material.ModalBottomSheetValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.ModalBottomSheetValue[] values();
enum_constant public static final androidx.compose.material.ModalBottomSheetValue Expanded;
enum_constant public static final androidx.compose.material.ModalBottomSheetValue HalfExpanded;
enum_constant public static final androidx.compose.material.ModalBottomSheetValue Hidden;
@@ -759,8 +745,6 @@
}
public enum SnackbarDuration {
- method public static androidx.compose.material.SnackbarDuration valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.SnackbarDuration[] values();
enum_constant public static final androidx.compose.material.SnackbarDuration Indefinite;
enum_constant public static final androidx.compose.material.SnackbarDuration Long;
enum_constant public static final androidx.compose.material.SnackbarDuration Short;
@@ -783,8 +767,6 @@
}
public enum SnackbarResult {
- method public static androidx.compose.material.SnackbarResult valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material.SnackbarResult[] values();
enum_constant public static final androidx.compose.material.SnackbarResult ActionPerformed;
enum_constant public static final androidx.compose.material.SnackbarResult Dismissed;
}
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 21f35fb2..335d309 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -44,7 +44,7 @@
api(project(":compose:foundation:foundation"))
api(project(":compose:material:material-icons-core"))
api(project(":compose:material:material-ripple"))
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
api("androidx.compose.ui:ui:1.6.0")
api("androidx.compose.ui:ui-text:1.6.0")
@@ -148,7 +148,7 @@
androidx {
name = "Compose Material Components"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2018"
description = "Compose Material Design Components library"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/material/material/integration-tests/material-demos/build.gradle b/compose/material/material/integration-tests/material-demos/build.gradle
index da885ff..c51c55c 100644
--- a/compose/material/material/integration-tests/material-demos/build.gradle
+++ b/compose/material/material/integration-tests/material-demos/build.gradle
@@ -22,6 +22,7 @@
implementation(project(":compose:integration-tests:demos:common"))
implementation(project(":compose:material:material"))
implementation(project(":compose:material:material:material-samples"))
+ implementation project(':compose:material:material-navigation-samples')
implementation(project(":compose:runtime:runtime"))
implementation(project(":compose:ui:ui"))
implementation(project(":compose:ui:ui-text"))
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
index a3c0f64..4ca1db4 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
@@ -19,6 +19,7 @@
import androidx.compose.integration.demos.common.ActivityDemo
import androidx.compose.integration.demos.common.ComposableDemo
import androidx.compose.integration.demos.common.DemoCategory
+import androidx.compose.material.navigation.samples.BottomSheetNavDemo
import androidx.compose.material.samples.AlertDialogSample
import androidx.compose.material.samples.BackdropScaffoldSample
import androidx.compose.material.samples.BottomDrawerSample
@@ -58,6 +59,7 @@
BottomSheetScaffoldWithDrawerSample()
},
ComposableDemo("Modal Bottom Sheet") { ModalBottomSheetSample() },
+ ComposableDemo("Modal Bottom Sheet In Navigation") { BottomSheetNavDemo() },
)
),
ComposableDemo("Buttons & FABs") { ButtonDemo() },
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt
index 35fa145..6719fad 100644
--- a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt
@@ -146,7 +146,7 @@
private val ExpectedBaselineOffset = 20.dp
private val TopPaddingFilledTextfield = 2.dp
private val IconColorAlpha = 0.54f
- private val TextfieldTag = "textField"
+ private val TextFieldTag = "textField"
@get:Rule
val rule = createComposeRule()
@@ -255,7 +255,7 @@
rule.setMaterialContent {
scope = rememberCoroutineScope()
TextField(
- modifier = Modifier.testTag(TextfieldTag),
+ modifier = Modifier.testTag(TextFieldTag),
value = "input",
onValueChange = {},
interactionSource = interactionSource
@@ -273,7 +273,7 @@
}
// Click on (2, 2) which is Surface area and outside input area
- rule.onNodeWithTag(TextfieldTag).performTouchInput {
+ rule.onNodeWithTag(TextFieldTag).performTouchInput {
click(Offset(2f, 2f))
}
@@ -283,7 +283,6 @@
}
}
- @ExperimentalComposeUiApi
@Test
fun testTextField_showHideKeyboardBasedOnFocus() {
val (focusRequester, parentFocusRequester) = FocusRequester.createRefs()
@@ -296,7 +295,7 @@
.focusRequester(parentFocusRequester)
.focusTarget()
.focusRequester(focusRequester)
- .testTag(TextfieldTag),
+ .testTag(TextFieldTag),
value = "input",
onValueChange = {}
)
@@ -327,7 +326,7 @@
.focusRequester(parentFocusRequester)
.focusTarget()
.focusRequester(focusRequester)
- .testTag(TextfieldTag),
+ .testTag(TextFieldTag),
value = "input",
onValueChange = {}
)
@@ -342,7 +341,7 @@
rule.runOnIdle { softwareKeyboardController?.hide() }
// Clicking on the text field shows the keyboard.
- rule.onNodeWithTag(TextfieldTag).performClick()
+ rule.onNodeWithTag(TextFieldTag).performClick()
rule.runOnIdle { assertThat(hostView.isSoftwareKeyboardShown).isTrue() }
}
@@ -473,7 +472,7 @@
rule.setMaterialContent {
Box {
TextField(
- modifier = Modifier.testTag(TextfieldTag),
+ modifier = Modifier.testTag(TextFieldTag),
value = "",
onValueChange = {},
label = {
@@ -492,7 +491,7 @@
}
// click to focus
- rule.onNodeWithTag(TextfieldTag).performClick()
+ rule.onNodeWithTag(TextFieldTag).performClick()
rule.runOnIdleWithDensity {
// size
@@ -558,7 +557,7 @@
TextField(
modifier = Modifier
.height(60.dp)
- .testTag(TextfieldTag),
+ .testTag(TextFieldTag),
value = "",
onValueChange = {},
label = { Text("label") },
@@ -575,7 +574,7 @@
}
}
// click to focus
- rule.onNodeWithTag(TextfieldTag).performClick()
+ rule.onNodeWithTag(TextFieldTag).performClick()
rule.runOnIdleWithDensity {
// size
@@ -602,7 +601,7 @@
rule.setMaterialContent {
Box {
TextField(
- modifier = Modifier.height(height).testTag(TextfieldTag),
+ modifier = Modifier.height(height).testTag(TextFieldTag),
value = "",
onValueChange = {},
placeholder = {
@@ -619,7 +618,7 @@
}
}
// click to focus
- rule.onNodeWithTag(TextfieldTag).performClick()
+ rule.onNodeWithTag(TextFieldTag).performClick()
rule.runOnIdleWithDensity {
// size
@@ -643,7 +642,7 @@
rule.setMaterialContent {
Column {
TextField(
- modifier = Modifier.testTag(TextfieldTag),
+ modifier = Modifier.testTag(TextFieldTag),
value = "input",
onValueChange = {},
@@ -661,7 +660,7 @@
}
// click to focus
- rule.onNodeWithTag(TextfieldTag).performClick()
+ rule.onNodeWithTag(TextFieldTag).performClick()
rule.runOnIdleWithDensity {
assertThat(placeholderSize.value).isNull()
@@ -673,7 +672,7 @@
fun testTextField_placeholderColorAndTextStyle() {
rule.setMaterialContent {
TextField(
- modifier = Modifier.testTag(TextfieldTag),
+ modifier = Modifier.testTag(TextFieldTag),
value = "",
onValueChange = {},
placeholder = {
@@ -695,7 +694,7 @@
}
// click to focus
- rule.onNodeWithTag(TextfieldTag).performClick()
+ rule.onNodeWithTag(TextFieldTag).performClick()
}
@Test
@@ -1036,7 +1035,7 @@
PlatformTextInputMethodTestOverride(sessionHandler) {
val text = remember { mutableStateOf("") }
TextField(
- modifier = Modifier.testTag(TextfieldTag),
+ modifier = Modifier.testTag(TextFieldTag),
value = text.value,
onValueChange = { text.value = it },
keyboardOptions = KeyboardOptions(
@@ -1047,7 +1046,7 @@
}
}
- rule.onNodeWithTag(TextfieldTag).performClick()
+ rule.onNodeWithTag(TextFieldTag).performClick()
rule.runOnIdle {
@Suppress("NAME_SHADOWING")
@@ -1065,7 +1064,7 @@
fun testTextField_visualTransformationPropagated() {
rule.setMaterialContent {
TextField(
- modifier = Modifier.testTag(TextfieldTag),
+ modifier = Modifier.testTag(TextFieldTag),
value = "qwerty",
onValueChange = {},
visualTransformation = PasswordVisualTransformation('\u0020'),
@@ -1074,7 +1073,7 @@
)
}
- rule.onNodeWithTag(TextfieldTag)
+ rule.onNodeWithTag(TextFieldTag)
.captureToImage()
.assertShape(
density = rule.density,
@@ -1094,7 +1093,7 @@
rule.setMaterialContent {
Box(Modifier.background(color = Color.White)) {
TextField(
- modifier = Modifier.testTag(TextfieldTag),
+ modifier = Modifier.testTag(TextFieldTag),
value = "test",
onValueChange = {},
label = { Text("label") },
@@ -1118,7 +1117,7 @@
}
}
- rule.onNodeWithTag(TextfieldTag)
+ rule.onNodeWithTag(TextFieldTag)
.captureToImage()
.assertShape(
density = rule.density,
@@ -1129,9 +1128,9 @@
shapeOverlapPixelCount = with(rule.density) { 1.dp.toPx() }
)
- rule.onNodeWithTag(TextfieldTag).performClick()
+ rule.onNodeWithTag(TextFieldTag).performClick()
- rule.onNodeWithTag(TextfieldTag)
+ rule.onNodeWithTag(TextFieldTag)
.captureToImage()
.assertShape(
density = rule.density,
@@ -1206,7 +1205,7 @@
}
rule.setMaterialContent {
TextField(
- modifier = Modifier.testTag(TextfieldTag),
+ modifier = Modifier.testTag(TextFieldTag),
value = "",
onValueChange = {},
visualTransformation = prefixTransformation,
@@ -1224,7 +1223,7 @@
)
)
}
- rule.onNodeWithTag(TextfieldTag)
+ rule.onNodeWithTag(TextFieldTag)
.captureToImage()
.assertPixels {
Color.White
@@ -1258,19 +1257,19 @@
value = "test",
onValueChange = {},
modifier = Modifier
- .testTag(TextfieldTag)
+ .testTag(TextFieldTag)
.semantics { if (isError.value) error(errorMessage) },
isError = isError.value
)
defaultErrorMessage = getString(DefaultErrorMessage)
}
- rule.onNodeWithTag(TextfieldTag)
+ rule.onNodeWithTag(TextFieldTag)
.assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Error))
.assert(SemanticsMatcher.expectValue(SemanticsProperties.Error, errorMessage))
// Check that default error message is overwritten and not lingering in a child node
- rule.onNodeWithTag(TextfieldTag, useUnmergedTree = true)
+ rule.onNodeWithTag(TextFieldTag, useUnmergedTree = true)
.onChildren()
.fetchSemanticsNodes()
.forEach { node ->
@@ -1347,7 +1346,7 @@
TextField(
value = "",
onValueChange = {},
- modifier = Modifier.testTag(TextfieldTag),
+ modifier = Modifier.testTag(TextFieldTag),
label = { Text("Label") },
isError = true,
colors = TextFieldDefaults.textFieldColors(
@@ -1360,7 +1359,7 @@
}
}
- rule.onNodeWithTag(TextfieldTag).captureToImage().assertPixels {
+ rule.onNodeWithTag(TextFieldTag).captureToImage().assertPixels {
Color.White
}
}
@@ -1640,15 +1639,15 @@
// causes TextFieldValue's composition clearing
focusManager.clearFocus(true)
},
- modifier = Modifier.testTag(TextfieldTag)
+ modifier = Modifier.testTag(TextFieldTag)
)
}
- rule.onNodeWithTag(TextfieldTag)
+ rule.onNodeWithTag(TextFieldTag)
.performClick()
rule.waitForIdle()
- rule.onNodeWithTag(TextfieldTag)
+ rule.onNodeWithTag(TextFieldTag)
.performTextClearance()
rule.runOnIdle {
diff --git a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.android.kt b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.android.kt
index 15a7bf7..e810b09 100644
--- a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.android.kt
+++ b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.android.kt
@@ -28,7 +28,6 @@
import android.view.ViewOutlineProvider
import android.view.ViewTreeObserver
import android.view.WindowManager
-import android.view.accessibility.AccessibilityManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.DisposableEffect
@@ -45,13 +44,12 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.R
import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.graphics.toComposeIntRect
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.AbstractComposeView
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalView
@@ -88,22 +86,17 @@
val density = LocalDensity.current
val testTag = LocalPopupTestTag.current
val layoutDirection = LocalLayoutDirection.current
- val context = LocalContext.current
- val a11yManager =
- context.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
-
val parentComposition = rememberCompositionContext()
val currentContent by rememberUpdatedState(content)
val popupId = rememberSaveable { UUID.randomUUID() }
- val popupLayout = remember(a11yManager) {
+ val popupLayout = remember {
PopupLayout(
onDismissRequest = onDismissRequest,
testTag = testTag,
composeView = view,
- focusable = a11yManager?.isTouchExplorationEnabled == true,
density = density,
initialPositionProvider = popupPositionProvider,
- popupId = popupId,
+ popupId = popupId
).apply {
setContent(parentComposition) {
SimpleStack(
@@ -222,7 +215,6 @@
private var onDismissRequest: (() -> Unit)?,
var testTag: String,
private val composeView: View,
- private val focusable: Boolean,
density: Density,
initialPositionProvider: PopupPositionProvider,
popupId: UUID
@@ -254,6 +246,15 @@
override val subCompositionView: AbstractComposeView get() = this
+ // Specific to exposed dropdown menus.
+ private val dismissOnOutsideClick = { offset: Offset?, bounds: IntRect ->
+ if (offset == null) false
+ else {
+ offset.x < bounds.left || offset.x > bounds.right ||
+ offset.y < bounds.top || offset.y > bounds.bottom
+ }
+ }
+
init {
id = android.R.id.content
setViewTreeLifecycleOwner(composeView.findViewTreeLifecycleOwner())
@@ -342,10 +343,10 @@
val parentBounds = parentBounds ?: return
val popupContentSize = popupContentSize ?: return
- val windowSize = previousWindowVisibleFrame.let { rect ->
- composeView.getWindowVisibleDisplayFrame(rect)
- val bounds = rect.toComposeIntRect()
- bounds.size
+ val windowSize = previousWindowVisibleFrame.let {
+ composeView.getWindowVisibleDisplayFrame(it)
+ val bounds = it.toIntBounds()
+ IntSize(width = bounds.width, height = bounds.height)
}
val popupPosition = positionProvider.calculatePosition(
@@ -385,11 +386,13 @@
(event.x < 0 || event.x >= width || event.y < 0 || event.y >= height)) ||
event.action == MotionEvent.ACTION_OUTSIDE
) {
- // If an event has raw coordinates of (0, 0), it means it belongs to another owner,
- // e.g., the soft keyboard or other window, so we want to keep the menu open.
- val isOutsideClickOnKeyboard = event.rawX == 0f && event.rawY == 0f
-
- val shouldDismiss = parentBounds == null || !isOutsideClickOnKeyboard
+ val parentBounds = parentBounds
+ val shouldDismiss = parentBounds == null || dismissOnOutsideClick(
+ // Keep menu open if ACTION_OUTSIDE event is reported as raw coordinates of (0, 0).
+ // This means it belongs to another owner, e.g., the soft keyboard or other window.
+ if (event.rawX != 0f && event.rawY != 0f) Offset(event.rawX, event.rawY) else null,
+ parentBounds
+ )
if (shouldDismiss) {
onDismissRequest?.invoke()
return true
@@ -421,15 +424,9 @@
gravity = Gravity.START or Gravity.TOP
// Flags specific to exposed dropdown menu.
- flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH and
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL.inv() or
+ flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
- flags = if (focusable) {
- flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv()
- } else {
- flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- }
-
softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
@@ -449,6 +446,13 @@
}
}
+ private fun Rect.toIntBounds() = IntRect(
+ left = left,
+ top = top,
+ right = right,
+ bottom = bottom
+ )
+
override fun onGlobalLayout() {
// Update the position of the popup, in case getWindowVisibleDisplayFrame has changed.
composeView.getWindowVisibleDisplayFrame(tmpWindowVisibleFrame)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index b03f6e0..86e9f43 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -345,6 +345,7 @@
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
+// Keep defaults in sync with androidx.compose.material.navigation.ModalBottomSheetLayout
fun ModalBottomSheetLayout(
sheetContent: @Composable ColumnScope.() -> Unit,
modifier: Modifier = Modifier,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeToDismiss.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeToDismiss.kt
index 4df2b6c..e4ab162 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeToDismiss.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeToDismiss.kt
@@ -171,6 +171,7 @@
*/
@Composable
@ExperimentalMaterialApi
+@SuppressWarnings("ReferencesDeprecated")
fun SwipeToDismiss(
state: DismissState,
modifier: Modifier = Modifier,
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index 34e7c93..f1636b4 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -126,8 +126,6 @@
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ThreePaneScaffoldRole {
- method public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole[] values();
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Primary;
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Secondary;
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Tertiary;
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index 34e7c93..f1636b4 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -126,8 +126,6 @@
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ThreePaneScaffoldRole {
- method public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole[] values();
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Primary;
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Secondary;
enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Tertiary;
diff --git a/compose/material3/adaptive/adaptive-layout/build.gradle b/compose/material3/adaptive/adaptive-layout/build.gradle
index ec87135..3f3aa99 100644
--- a/compose/material3/adaptive/adaptive-layout/build.gradle
+++ b/compose/material3/adaptive/adaptive-layout/build.gradle
@@ -45,8 +45,7 @@
api(project(":compose:material3:adaptive:adaptive"))
implementation("androidx.compose.foundation:foundation-layout:1.6.0-rc01")
implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
- // TODO(conradchen): pin the depe when the change required is released to public
- implementation(project(":window:window-core"))
+ implementation("androidx.window:window-core:1.3.0-alpha02")
}
}
@@ -114,7 +113,7 @@
androidx {
name = "Material Adaptive"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2023"
description = "Compose Material Design Adaptive Library"
}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
index 0e97207..f09e944 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt
@@ -46,7 +46,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-@Suppress("IllegalExperimentalApiUsage") // TODO: address before moving to beta
@OptIn(ExperimentalComposeUiApi::class)
internal fun Modifier.animateBounds(
modifier: Modifier = Modifier,
@@ -138,7 +137,6 @@
}
context(LookaheadScope, Placeable.PlacementScope, CoroutineScope)
-@Suppress("IllegalExperimentalApiUsage") // TODO: address before moving to beta
@OptIn(ExperimentalComposeUiApi::class)
internal fun DeferredAnimation<IntOffset, AnimationVector2D>.updateTargetBasedOnCoordinates(
animationSpec: FiniteAnimationSpec<IntOffset>,
diff --git a/compose/material3/adaptive/adaptive-navigation/api/current.txt b/compose/material3/adaptive/adaptive-navigation/api/current.txt
index f3c507c..0744ac2 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/current.txt
@@ -2,8 +2,6 @@
package androidx.compose.material3.adaptive.navigation {
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum BackNavigationBehavior {
- method public static androidx.compose.material3.adaptive.navigation.BackNavigationBehavior valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.adaptive.navigation.BackNavigationBehavior[] values();
enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopLatest;
enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopUntilContentChange;
enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopUntilCurrentDestinationChange;
diff --git a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
index f3c507c..0744ac2 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
@@ -2,8 +2,6 @@
package androidx.compose.material3.adaptive.navigation {
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum BackNavigationBehavior {
- method public static androidx.compose.material3.adaptive.navigation.BackNavigationBehavior valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.adaptive.navigation.BackNavigationBehavior[] values();
enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopLatest;
enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopUntilContentChange;
enum_constant public static final androidx.compose.material3.adaptive.navigation.BackNavigationBehavior PopUntilCurrentDestinationChange;
diff --git a/compose/material3/adaptive/adaptive-navigation/build.gradle b/compose/material3/adaptive/adaptive-navigation/build.gradle
index 3736767..a2794d7 100644
--- a/compose/material3/adaptive/adaptive-navigation/build.gradle
+++ b/compose/material3/adaptive/adaptive-navigation/build.gradle
@@ -45,8 +45,7 @@
implementation("androidx.compose.foundation:foundation-layout:1.6.0-rc01")
implementation("androidx.compose.ui:ui-util:1.6.0-rc01")
implementation(project(":compose:material3:adaptive:adaptive-layout"))
- // TODO(conradchen): pin the depe when the change required is released to public
- implementation(project(":window:window-core"))
+ implementation("androidx.window:window-core:1.3.0-alpha02")
}
}
@@ -114,7 +113,7 @@
androidx {
name = "Material Adaptive"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2023"
description = "Compose Material Design Adaptive Library"
}
diff --git a/compose/material3/adaptive/adaptive/build.gradle b/compose/material3/adaptive/adaptive/build.gradle
index 17f440f..02827a6 100644
--- a/compose/material3/adaptive/adaptive/build.gradle
+++ b/compose/material3/adaptive/adaptive/build.gradle
@@ -43,8 +43,7 @@
implementation(libs.kotlinStdlibCommon)
api("androidx.compose.foundation:foundation:1.6.0")
implementation("androidx.compose.ui:ui-util:1.6.0")
- // TODO(conradchen): pin the depe when the change required is released to public
- implementation(project(":window:window-core"))
+ implementation("androidx.window:window-core:1.3.0-alpha02")
}
}
@@ -113,7 +112,7 @@
androidx {
name = "Material Adaptive"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2023"
description = "Compose Material Design Adaptive Library"
}
diff --git a/compose/material3/material3-adaptive-navigation-suite/api/current.txt b/compose/material3/material3-adaptive-navigation-suite/api/current.txt
index a4d597b..71aa3e9 100644
--- a/compose/material3/material3-adaptive-navigation-suite/api/current.txt
+++ b/compose/material3/material3-adaptive-navigation-suite/api/current.txt
@@ -25,6 +25,7 @@
}
@SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public final class NavigationSuiteItemColors {
+ ctor public NavigationSuiteItemColors(androidx.compose.material3.NavigationBarItemColors navigationBarItemColors, androidx.compose.material3.NavigationRailItemColors navigationRailItemColors, androidx.compose.material3.NavigationDrawerItemColors navigationDrawerItemColors);
method public androidx.compose.material3.NavigationBarItemColors getNavigationBarItemColors();
method public androidx.compose.material3.NavigationDrawerItemColors getNavigationDrawerItemColors();
method public androidx.compose.material3.NavigationRailItemColors getNavigationRailItemColors();
diff --git a/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt b/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
index a4d597b..71aa3e9 100644
--- a/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
+++ b/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
@@ -25,6 +25,7 @@
}
@SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public final class NavigationSuiteItemColors {
+ ctor public NavigationSuiteItemColors(androidx.compose.material3.NavigationBarItemColors navigationBarItemColors, androidx.compose.material3.NavigationRailItemColors navigationRailItemColors, androidx.compose.material3.NavigationDrawerItemColors navigationDrawerItemColors);
method public androidx.compose.material3.NavigationBarItemColors getNavigationBarItemColors();
method public androidx.compose.material3.NavigationDrawerItemColors getNavigationDrawerItemColors();
method public androidx.compose.material3.NavigationRailItemColors getNavigationRailItemColors();
diff --git a/compose/material3/material3-adaptive-navigation-suite/build.gradle b/compose/material3/material3-adaptive-navigation-suite/build.gradle
index 769a941..b638cf8 100644
--- a/compose/material3/material3-adaptive-navigation-suite/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/build.gradle
@@ -21,11 +21,9 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.AndroidXComposePlugin
-import androidx.build.KmpPlatformsKt
+
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
-import androidx.build.Publish
plugins {
id("AndroidXPlugin")
@@ -46,8 +44,7 @@
implementation("androidx.compose.material3:material3:1.2.0")
implementation(project(":compose:material3:adaptive:adaptive"))
implementation("androidx.compose.ui:ui-util:1.6.0")
- // TODO(conradchen): pin the depe when the change required is released to public
- implementation(project(":window:window-core"))
+ implementation("androidx.window:window-core:1.3.0-alpha02")
}
}
@@ -110,15 +107,12 @@
android {
namespace "androidx.compose.material3.adaptive.navigationsuite"
- lintOptions {
- disable 'IllegalExperimentalApiUsage' // TODO (conradchen): Address before moving to beta
- }
}
androidx {
name = "Material Adaptive Navigation Suite"
mavenVersion = LibraryVersions.COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2023"
description = "Compose Material Design Adaptive Navigation Suite Library"
}
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 815e138..4497779 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
@@ -427,7 +427,8 @@
navigationBarContentColor: Color = contentColorFor(navigationBarContainerColor),
navigationRailContainerColor: Color = NavigationRailDefaults.ContainerColor,
navigationRailContentColor: Color = contentColorFor(navigationRailContainerColor),
- navigationDrawerContainerColor: Color = DrawerDefaults.containerColor,
+ navigationDrawerContainerColor: Color =
+ @Suppress("DEPRECATION") DrawerDefaults.containerColor,
navigationDrawerContentColor: Color = contentColorFor(navigationDrawerContainerColor),
): NavigationSuiteColors =
NavigationSuiteColors(
@@ -484,8 +485,7 @@
* [NavigationDrawerItem] of the [NavigationSuiteScope.item]
*/
@ExperimentalMaterial3AdaptiveNavigationSuiteApi
-class NavigationSuiteItemColors
-internal constructor(
+class NavigationSuiteItemColors constructor(
val navigationBarItemColors: NavigationBarItemColors,
val navigationRailItemColors: NavigationRailItemColors,
val navigationDrawerItemColors: NavigationDrawerItemColors,
diff --git a/compose/material3/material3-common/build.gradle b/compose/material3/material3-common/build.gradle
index 804a2d2..cb14811 100644
--- a/compose/material3/material3-common/build.gradle
+++ b/compose/material3/material3-common/build.gradle
@@ -45,7 +45,7 @@
implementation(project(":compose:ui:ui-util"))
api("androidx.compose.foundation:foundation:1.6.0")
api("androidx.compose.foundation:foundation-layout:1.6.0")
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
api("androidx.compose.ui:ui-graphics:1.6.0")
api("androidx.compose.ui:ui-text:1.6.0")
}
@@ -109,10 +109,10 @@
}
androidx {
- name = "Compose Material 3 Common components"
+ name = "Compose Material 3 Common"
mavenVersion = LibraryVersions.COMPOSE_MATERIAL3_COMMON
- type = LibraryType.PUBLISHED_LIBRARY
- publish = Publish.NONE
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+ publish = Publish.SNAPSHOT_AND_RELEASE
inceptionYear = "2023"
description = "Compose Material 3 Common Library. This library contains foundational, themeless " +
"components that can be shared between different Material libraries or used by app" +
diff --git a/compose/material3/material3-window-size-class/build.gradle b/compose/material3/material3-window-size-class/build.gradle
index 78970ad..f3d4c53 100644
--- a/compose/material3/material3-window-size-class/build.gradle
+++ b/compose/material3/material3-window-size-class/build.gradle
@@ -41,7 +41,7 @@
dependencies {
implementation(libs.kotlinStdlibCommon)
implementation("androidx.compose.ui:ui-util:1.6.0")
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
api("androidx.compose.ui:ui:1.6.0")
api("androidx.compose.ui:ui-unit:1.6.0")
}
@@ -119,7 +119,7 @@
androidx {
name = "Compose Material 3 Window Size Class"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2022"
description = "Provides window size classes for building responsive UIs"
}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 5505320..259081d 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -623,15 +623,11 @@
}
@Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissDirection {
- method @Deprecated public static androidx.compose.material3.DismissDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.compose.material3.DismissDirection[] values();
enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection EndToStart;
enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection StartToEnd;
}
@Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
- method @Deprecated public static androidx.compose.material3.DismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.compose.material3.DismissValue[] values();
enum_constant @Deprecated public static final androidx.compose.material3.DismissValue Default;
enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToEnd;
enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToStart;
@@ -663,21 +659,25 @@
}
public final class DrawerDefaults {
- method @androidx.compose.runtime.Composable public long getContainerColor();
+ method @Deprecated @androidx.compose.runtime.Composable public long getContainerColor();
method public float getDismissibleDrawerElevation();
method public float getMaximumDrawerWidth();
+ method @androidx.compose.runtime.Composable public long getModalContainerColor();
method public float getModalDrawerElevation();
method public float getPermanentDrawerElevation();
method @androidx.compose.runtime.Composable public long getScrimColor();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
+ method @androidx.compose.runtime.Composable public long getStandardContainerColor();
method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
property public final float DismissibleDrawerElevation;
property public final float MaximumDrawerWidth;
property public final float ModalDrawerElevation;
property public final float PermanentDrawerElevation;
- property @androidx.compose.runtime.Composable public final long containerColor;
+ property @Deprecated @androidx.compose.runtime.Composable public final long containerColor;
+ property @androidx.compose.runtime.Composable public final long modalContainerColor;
property @androidx.compose.runtime.Composable public final long scrimColor;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
+ property @androidx.compose.runtime.Composable public final long standardContainerColor;
property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
field public static final androidx.compose.material3.DrawerDefaults INSTANCE;
}
@@ -710,8 +710,6 @@
}
public enum DrawerValue {
- method public static androidx.compose.material3.DrawerValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.DrawerValue[] values();
enum_constant public static final androidx.compose.material3.DrawerValue Closed;
enum_constant public static final androidx.compose.material3.DrawerValue Open;
}
@@ -1189,6 +1187,7 @@
method @Deprecated @androidx.compose.runtime.Composable public static void LinearProgressIndicator(float progress, optional androidx.compose.ui.Modifier modifier, optional long color, optional long trackColor, optional int strokeCap);
method @Deprecated @androidx.compose.runtime.Composable public static void LinearProgressIndicator(kotlin.jvm.functions.Function0<java.lang.Float> progress, optional androidx.compose.ui.Modifier modifier, optional long color, optional long trackColor, optional int strokeCap);
method @androidx.compose.runtime.Composable public static void LinearProgressIndicator(kotlin.jvm.functions.Function0<java.lang.Float> progress, optional androidx.compose.ui.Modifier modifier, optional long color, optional long trackColor, optional int strokeCap, optional float gapSize, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit>? drawStopIndicator);
+ method public static void drawStopIndicator(androidx.compose.ui.graphics.drawscope.DrawScope, float stopSize, long color, int strokeCap);
}
@androidx.compose.runtime.Immutable public final class RadioButtonColors {
@@ -1214,7 +1213,7 @@
method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.RadioButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
}
- @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RangeSliderState {
+ @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class RangeSliderState {
ctor public RangeSliderState(optional float activeRangeStart, optional float activeRangeEnd, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
method public float getActiveRangeEnd();
method public float getActiveRangeStart();
@@ -1223,6 +1222,7 @@
method public kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> getValueRange();
method public void setActiveRangeEnd(float);
method public void setActiveRangeStart(float);
+ method public void setOnValueChangeFinished(kotlin.jvm.functions.Function0<kotlin.Unit>?);
property public final float activeRangeEnd;
property public final float activeRangeStart;
property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished;
@@ -1426,8 +1426,7 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SheetState {
- ctor @Deprecated public SheetState(boolean skipPartiallyExpanded, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
- ctor @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+ ctor public SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
method public suspend Object? expand(kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public androidx.compose.material3.SheetValue getCurrentValue();
method public boolean getHasExpandedState();
@@ -1447,13 +1446,10 @@
}
public static final class SheetState.Companion {
- method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
- method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density);
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density, boolean skipHiddenState);
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SheetValue {
- method public static androidx.compose.material3.SheetValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.SheetValue[] values();
enum_constant public static final androidx.compose.material3.SheetValue Expanded;
enum_constant public static final androidx.compose.material3.SheetValue Hidden;
enum_constant public static final androidx.compose.material3.SheetValue PartiallyExpanded;
@@ -1490,10 +1486,10 @@
@androidx.compose.runtime.Stable public final class SliderDefaults {
method @androidx.compose.runtime.Composable public void Thumb(androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional long thumbSize);
method @Deprecated @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
- method @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional float thumbTrackGapSize, optional float trackInsideCornerSize, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator);
+ method @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator, optional float thumbTrackGapSize, optional float trackInsideCornerSize);
method @Deprecated @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderPositions sliderPositions, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional float thumbTrackGapSize, optional float trackInsideCornerSize, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator, optional float thumbTrackGapSize, optional float trackInsideCornerSize);
method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors(optional long thumbColor, optional long activeTrackColor, optional long activeTickColor, optional long inactiveTrackColor, optional long inactiveTickColor, optional long disabledThumbColor, optional long disabledActiveTrackColor, optional long disabledActiveTickColor, optional long disabledInactiveTrackColor, optional long disabledInactiveTickColor);
field public static final androidx.compose.material3.SliderDefaults INSTANCE;
@@ -1516,7 +1512,7 @@
property @Deprecated public final float[] tickFractions;
}
- @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SliderState implements androidx.compose.foundation.gestures.DraggableState {
+ @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SliderState implements androidx.compose.foundation.gestures.DraggableState {
ctor public SliderState(optional float value, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
method public void dispatchRawDelta(float delta);
method public suspend Object? drag(androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.DragScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -1524,6 +1520,7 @@
method public int getSteps();
method public float getValue();
method public kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> getValueRange();
+ method public void setOnValueChangeFinished(kotlin.jvm.functions.Function0<kotlin.Unit>?);
method public void setValue(float);
property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished;
property public final int steps;
@@ -1555,8 +1552,6 @@
}
public enum SnackbarDuration {
- method public static androidx.compose.material3.SnackbarDuration valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.SnackbarDuration[] values();
enum_constant public static final androidx.compose.material3.SnackbarDuration Indefinite;
enum_constant public static final androidx.compose.material3.SnackbarDuration Long;
enum_constant public static final androidx.compose.material3.SnackbarDuration Short;
@@ -1580,8 +1575,6 @@
}
public enum SnackbarResult {
- method public static androidx.compose.material3.SnackbarResult valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.SnackbarResult[] values();
enum_constant public static final androidx.compose.material3.SnackbarResult ActionPerformed;
enum_constant public static final androidx.compose.material3.SnackbarResult Dismissed;
}
@@ -1659,8 +1652,6 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SwipeToDismissBoxValue {
- method public static androidx.compose.material3.SwipeToDismissBoxValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.SwipeToDismissBoxValue[] values();
enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue EndToStart;
enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue Settled;
enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue StartToEnd;
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 5505320..259081d 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -623,15 +623,11 @@
}
@Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissDirection {
- method @Deprecated public static androidx.compose.material3.DismissDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.compose.material3.DismissDirection[] values();
enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection EndToStart;
enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection StartToEnd;
}
@Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
- method @Deprecated public static androidx.compose.material3.DismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.compose.material3.DismissValue[] values();
enum_constant @Deprecated public static final androidx.compose.material3.DismissValue Default;
enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToEnd;
enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToStart;
@@ -663,21 +659,25 @@
}
public final class DrawerDefaults {
- method @androidx.compose.runtime.Composable public long getContainerColor();
+ method @Deprecated @androidx.compose.runtime.Composable public long getContainerColor();
method public float getDismissibleDrawerElevation();
method public float getMaximumDrawerWidth();
+ method @androidx.compose.runtime.Composable public long getModalContainerColor();
method public float getModalDrawerElevation();
method public float getPermanentDrawerElevation();
method @androidx.compose.runtime.Composable public long getScrimColor();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
+ method @androidx.compose.runtime.Composable public long getStandardContainerColor();
method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
property public final float DismissibleDrawerElevation;
property public final float MaximumDrawerWidth;
property public final float ModalDrawerElevation;
property public final float PermanentDrawerElevation;
- property @androidx.compose.runtime.Composable public final long containerColor;
+ property @Deprecated @androidx.compose.runtime.Composable public final long containerColor;
+ property @androidx.compose.runtime.Composable public final long modalContainerColor;
property @androidx.compose.runtime.Composable public final long scrimColor;
property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
+ property @androidx.compose.runtime.Composable public final long standardContainerColor;
property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
field public static final androidx.compose.material3.DrawerDefaults INSTANCE;
}
@@ -710,8 +710,6 @@
}
public enum DrawerValue {
- method public static androidx.compose.material3.DrawerValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.DrawerValue[] values();
enum_constant public static final androidx.compose.material3.DrawerValue Closed;
enum_constant public static final androidx.compose.material3.DrawerValue Open;
}
@@ -1189,6 +1187,7 @@
method @Deprecated @androidx.compose.runtime.Composable public static void LinearProgressIndicator(float progress, optional androidx.compose.ui.Modifier modifier, optional long color, optional long trackColor, optional int strokeCap);
method @Deprecated @androidx.compose.runtime.Composable public static void LinearProgressIndicator(kotlin.jvm.functions.Function0<java.lang.Float> progress, optional androidx.compose.ui.Modifier modifier, optional long color, optional long trackColor, optional int strokeCap);
method @androidx.compose.runtime.Composable public static void LinearProgressIndicator(kotlin.jvm.functions.Function0<java.lang.Float> progress, optional androidx.compose.ui.Modifier modifier, optional long color, optional long trackColor, optional int strokeCap, optional float gapSize, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit>? drawStopIndicator);
+ method public static void drawStopIndicator(androidx.compose.ui.graphics.drawscope.DrawScope, float stopSize, long color, int strokeCap);
}
@androidx.compose.runtime.Immutable public final class RadioButtonColors {
@@ -1214,7 +1213,7 @@
method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.RadioButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
}
- @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RangeSliderState {
+ @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class RangeSliderState {
ctor public RangeSliderState(optional float activeRangeStart, optional float activeRangeEnd, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
method public float getActiveRangeEnd();
method public float getActiveRangeStart();
@@ -1223,6 +1222,7 @@
method public kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> getValueRange();
method public void setActiveRangeEnd(float);
method public void setActiveRangeStart(float);
+ method public void setOnValueChangeFinished(kotlin.jvm.functions.Function0<kotlin.Unit>?);
property public final float activeRangeEnd;
property public final float activeRangeStart;
property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished;
@@ -1426,8 +1426,7 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SheetState {
- ctor @Deprecated public SheetState(boolean skipPartiallyExpanded, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
- ctor @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+ ctor public SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
method public suspend Object? expand(kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public androidx.compose.material3.SheetValue getCurrentValue();
method public boolean getHasExpandedState();
@@ -1447,13 +1446,10 @@
}
public static final class SheetState.Companion {
- method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
- method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density);
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density, boolean skipHiddenState);
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SheetValue {
- method public static androidx.compose.material3.SheetValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.SheetValue[] values();
enum_constant public static final androidx.compose.material3.SheetValue Expanded;
enum_constant public static final androidx.compose.material3.SheetValue Hidden;
enum_constant public static final androidx.compose.material3.SheetValue PartiallyExpanded;
@@ -1490,10 +1486,10 @@
@androidx.compose.runtime.Stable public final class SliderDefaults {
method @androidx.compose.runtime.Composable public void Thumb(androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional long thumbSize);
method @Deprecated @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
- method @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional float thumbTrackGapSize, optional float trackInsideCornerSize, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator);
+ method @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.RangeSliderState rangeSliderState, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator, optional float thumbTrackGapSize, optional float trackInsideCornerSize);
method @Deprecated @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderPositions sliderPositions, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional float thumbTrackGapSize, optional float trackInsideCornerSize, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderState sliderState, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.drawscope.DrawScope,? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? drawStopIndicator, optional float thumbTrackGapSize, optional float trackInsideCornerSize);
method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors(optional long thumbColor, optional long activeTrackColor, optional long activeTickColor, optional long inactiveTrackColor, optional long inactiveTickColor, optional long disabledThumbColor, optional long disabledActiveTrackColor, optional long disabledActiveTickColor, optional long disabledInactiveTrackColor, optional long disabledInactiveTickColor);
field public static final androidx.compose.material3.SliderDefaults INSTANCE;
@@ -1516,7 +1512,7 @@
property @Deprecated public final float[] tickFractions;
}
- @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SliderState implements androidx.compose.foundation.gestures.DraggableState {
+ @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SliderState implements androidx.compose.foundation.gestures.DraggableState {
ctor public SliderState(optional float value, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
method public void dispatchRawDelta(float delta);
method public suspend Object? drag(androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.DragScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -1524,6 +1520,7 @@
method public int getSteps();
method public float getValue();
method public kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> getValueRange();
+ method public void setOnValueChangeFinished(kotlin.jvm.functions.Function0<kotlin.Unit>?);
method public void setValue(float);
property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished;
property public final int steps;
@@ -1555,8 +1552,6 @@
}
public enum SnackbarDuration {
- method public static androidx.compose.material3.SnackbarDuration valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.SnackbarDuration[] values();
enum_constant public static final androidx.compose.material3.SnackbarDuration Indefinite;
enum_constant public static final androidx.compose.material3.SnackbarDuration Long;
enum_constant public static final androidx.compose.material3.SnackbarDuration Short;
@@ -1580,8 +1575,6 @@
}
public enum SnackbarResult {
- method public static androidx.compose.material3.SnackbarResult valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.SnackbarResult[] values();
enum_constant public static final androidx.compose.material3.SnackbarResult ActionPerformed;
enum_constant public static final androidx.compose.material3.SnackbarResult Dismissed;
}
@@ -1659,8 +1652,6 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SwipeToDismissBoxValue {
- method public static androidx.compose.material3.SwipeToDismissBoxValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.SwipeToDismissBoxValue[] values();
enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue EndToStart;
enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue Settled;
enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue StartToEnd;
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index aa50925..331e5e4 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -48,7 +48,7 @@
api("androidx.compose.foundation:foundation-layout:1.6.0")
api("androidx.compose.material:material-icons-core:1.6.0")
api(project(":compose:material:material-ripple"))
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
api("androidx.compose.ui:ui:1.6.0")
api("androidx.compose.ui:ui-text:1.6.0")
}
@@ -148,7 +148,7 @@
androidx {
name = "Compose Material3 Components"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2021"
description = "Compose Material You Design Components library"
}
diff --git a/compose/material3/material3/integration-tests/material3-catalog/build.gradle b/compose/material3/material3/integration-tests/material3-catalog/build.gradle
index 699cb55..7fb4a83 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/build.gradle
+++ b/compose/material3/material3/integration-tests/material3-catalog/build.gradle
@@ -33,18 +33,18 @@
dependencies {
implementation(libs.kotlinStdlib)
- implementation project(":core:core")
+ implementation("androidx.core:core:1.12.0")
implementation project(":compose:runtime:runtime")
- implementation project(":compose:foundation:foundation-layout")
- implementation project(":compose:ui:ui")
- implementation project(":compose:material:material")
- implementation project(":compose:material:material-icons-extended")
- implementation project(":compose:material3:adaptive:adaptive-samples")
- implementation project(":compose:material3:material3")
- implementation project(":compose:material3:material3:material3-samples")
- implementation project(":compose:material3:material3-adaptive-navigation-suite:material3-adaptive-navigation-suite-samples")
- implementation project(":datastore:datastore-preferences")
- implementation project(":navigation:navigation-compose")
+ implementation("androidx.compose.foundation:foundation-layout:1.6.0")
+ implementation("androidx.compose.ui:ui:1.6.0")
+ implementation("androidx.compose.material:material:1.6.0")
+ implementation("androidx.compose.material:material-icons-extended:1.6.0")
+ implementation(project(":compose:material3:adaptive:adaptive-samples"))
+ implementation(project(":compose:material3:material3"))
+ implementation(project(":compose:material3:material3:material3-samples"))
+ implementation(project(":compose:material3:material3-adaptive-navigation-suite:material3-adaptive-navigation-suite-samples"))
+ implementation(project(":datastore:datastore-preferences"))
+ implementation("androidx.navigation:navigation-compose:2.7.7")
}
androidx {
diff --git a/compose/material3/material3/samples/build.gradle b/compose/material3/material3/samples/build.gradle
index d791cf2..98b58d0 100644
--- a/compose/material3/material3/samples/build.gradle
+++ b/compose/material3/material3/samples/build.gradle
@@ -38,10 +38,10 @@
implementation("androidx.activity:activity-compose:1.5.0")
implementation("androidx.compose.animation:animation:1.2.1")
- implementation(project(":compose:foundation:foundation"))
- implementation(project(":compose:foundation:foundation-layout"))
+ implementation("androidx.compose.foundation:foundation:1.6.0")
+ implementation("androidx.compose.foundation:foundation-layout:1.6.0")
implementation("androidx.compose.material:material:1.2.1")
- implementation(project(":compose:material:material-icons-extended"))
+ implementation("androidx.compose.material:material-icons-extended:1.6.0")
implementation(project(":compose:material3:material3"))
implementation("androidx.compose.runtime:runtime:1.2.1")
implementation("androidx.compose.ui:ui:1.2.1")
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt
index 434f412..3495bb6 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt
@@ -42,6 +42,8 @@
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
+import androidx.compose.material3.ListItemDefaults
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
@@ -155,7 +157,10 @@
Icons.Default.Favorite,
contentDescription = "Localized description"
)
- }
+ },
+ colors = ListItemDefaults.colors(
+ containerColor = MaterialTheme.colorScheme.surfaceContainerLow
+ ),
)
}
}
@@ -238,7 +243,10 @@
Icons.Default.Favorite,
contentDescription = "Localized description"
)
- }
+ },
+ colors = ListItemDefaults.colors(
+ containerColor = MaterialTheme.colorScheme.surfaceContainerLow
+ ),
)
}
}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt
index 2374cf6..05c71ce 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt
@@ -17,6 +17,7 @@
import android.os.Build
import androidx.annotation.Sampled
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -27,6 +28,7 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.DatePicker
+import androidx.compose.material3.DatePickerDefaults
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.DateRangePicker
import androidx.compose.material3.DisplayMode
@@ -196,6 +198,7 @@
Row(
modifier = Modifier
.fillMaxWidth()
+ .background(DatePickerDefaults.colors().containerColor)
.padding(start = 12.dp, end = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
@@ -206,10 +209,9 @@
TextButton(
onClick = {
snackScope.launch {
- snackState.showSnackbar(
- "Saved range (timestamps): " +
- "${state.selectedStartDateMillis!!..state.selectedEndDateMillis!!}"
- )
+ val range =
+ state.selectedStartDateMillis!!..state.selectedEndDateMillis!!
+ snackState.showSnackbar("Saved range (timestamps): $range")
}
},
enabled = state.selectedEndDateMillis != null
@@ -217,7 +219,6 @@
Text(text = "Save")
}
}
-
DateRangePicker(state = state, modifier = Modifier.weight(1f))
}
}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt
index 43d0a6b..fc063f8 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt
@@ -21,10 +21,9 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.ListItem
+import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.SwipeToDismissBox
import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.Text
@@ -33,12 +32,13 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.tooling.preview.Preview
@Preview
@Sampled
@Composable
-@ExperimentalMaterial3Api
+@OptIn(ExperimentalMaterial3Api::class)
fun SwipeToDismissListItems() {
val dismissState = rememberSwipeToDismissBoxState()
SwipeToDismissBox(
@@ -51,17 +51,20 @@
SwipeToDismissBoxValue.EndToStart -> Color.Red
}
)
- Box(Modifier.fillMaxSize().background(color))
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(color)
+ )
}
) {
- Card {
+ OutlinedCard(shape = RectangleShape) {
ListItem(
headlineContent = {
Text("Cupcake")
},
supportingContent = { Text("Swipe me left or right!") }
)
- HorizontalDivider()
}
}
}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
index a5390d8..e14185a 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
@@ -94,7 +94,6 @@
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import junit.framework.TestCase
-import kotlin.IllegalStateException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@@ -864,7 +863,6 @@
rule.setContent {
dragHandleContentDescription = getString(Strings.BottomSheetDragHandleDescription)
dragHandleColor = SheetBottomTokens.DockedDragHandleColor.value
- .copy(SheetBottomTokens.DockedDragHandleOpacity)
surface = MaterialTheme.colorScheme.surface
density = LocalDensity.current
BottomSheetScaffold(
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/NavigationBarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/NavigationBarTest.kt
index 5e9c9e5..0eb695c 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/NavigationBarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/NavigationBarTest.kt
@@ -29,6 +29,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.testutils.assertIsEqualTo
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
@@ -265,6 +266,77 @@
}
@Test
+ fun navigationBarItem_defaultColors() {
+ rule.setMaterialContent(lightColorScheme()) {
+ NavigationBar {
+ NavigationBarItem(
+ icon = {
+ assertThat(LocalContentColor.current)
+ .isEqualTo(NavigationBarTokens.ActiveIconColor.value)
+ },
+ label = {
+ assertThat(LocalContentColor.current)
+ .isEqualTo(NavigationBarTokens.ActiveLabelTextColor.value)
+ },
+ selected = true,
+ onClick = {}
+ )
+ NavigationBarItem(
+ icon = {
+ assertThat(LocalContentColor.current)
+ .isEqualTo(NavigationBarTokens.InactiveIconColor.value)
+ },
+ label = {
+ assertThat(LocalContentColor.current)
+ .isEqualTo(NavigationBarTokens.InactiveLabelTextColor.value)
+ },
+ selected = false,
+ onClick = {}
+ )
+ }
+ }
+ }
+
+ @Test
+ fun navigationBarItem_customColors() {
+ rule.setMaterialContent(lightColorScheme()) {
+ val customNavigationBarItemColors = NavigationBarItemDefaults.colors(
+ selectedIconColor = Color.Red,
+ unselectedTextColor = Color.Green,
+ )
+
+ NavigationBar {
+ NavigationBarItem(
+ colors = customNavigationBarItemColors,
+ icon = {
+ assertThat(LocalContentColor.current)
+ .isEqualTo(Color.Red)
+ },
+ label = {
+ assertThat(LocalContentColor.current)
+ .isEqualTo(NavigationBarTokens.ActiveLabelTextColor.value)
+ },
+ selected = true,
+ onClick = {}
+ )
+ NavigationBarItem(
+ colors = customNavigationBarItemColors,
+ icon = {
+ assertThat(LocalContentColor.current)
+ .isEqualTo(NavigationBarTokens.InactiveIconColor.value)
+ },
+ label = {
+ assertThat(LocalContentColor.current)
+ .isEqualTo(Color.Green)
+ },
+ selected = false,
+ onClick = {}
+ )
+ }
+ }
+ }
+
+ @Test
fun navigationBarItem_withLongLabel_automaticallyResizesHeight() {
val defaultHeight = NavigationBarTokens.ContainerHeight
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ProgressIndicatorScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ProgressIndicatorScreenshotTest.kt
index 55e25b2..84c6532 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ProgressIndicatorScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ProgressIndicatorScreenshotTest.kt
@@ -113,6 +113,18 @@
}
@Test
+ fun linearProgressIndicator_lightTheme_indeterminate_start() {
+ rule.mainClock.autoAdvance = false
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(wrap.testTag(wrapperTestTag)) {
+ LinearProgressIndicator()
+ }
+ }
+ rule.mainClock.advanceTimeBy(0)
+ assertIndicatorAgainstGolden("linearProgressIndicator_lightTheme_indeterminate_start")
+ }
+
+ @Test
fun linearProgressIndicator_lightTheme_indeterminate_no_gap() {
rule.mainClock.autoAdvance = false
rule.setMaterialContent(lightColorScheme()) {
@@ -182,6 +194,18 @@
}
@Test
+ fun circularProgressIndicator_lightTheme_indeterminate_start() {
+ rule.mainClock.autoAdvance = false
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(wrap.testTag(wrapperTestTag)) {
+ CircularProgressIndicator()
+ }
+ }
+ rule.mainClock.advanceTimeBy(0)
+ assertIndicatorAgainstGolden("circularProgressIndicator_lightTheme_indeterminate_start")
+ }
+
+ @Test
fun circularProgressIndicator_darkTheme_determinate() {
rule.setMaterialContent(darkColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
index 58e8956..9a9ef7d 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
@@ -20,15 +20,18 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.testutils.assertAgainstGolden
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -49,7 +52,7 @@
val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3)
val wrap = Modifier
- .requiredWidth(70.dp)
+ .requiredWidth(200.dp)
.wrapContentSize(Alignment.TopStart)
private val wrapperTestTag = "sliderWrapper"
@@ -69,6 +72,21 @@
@OptIn(ExperimentalMaterial3Api::class)
@Test
+ fun sliderTest_origin_rtl() {
+ rule.setMaterialContent(lightColorScheme()) {
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ Box(wrap.testTag(wrapperTestTag)) {
+ Slider(
+ remember { SliderState(0f) }
+ )
+ }
+ }
+ }
+ assertSliderAgainstGolden("slider_origin_rtl")
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Test
fun sliderTest_origin_disabled() {
rule.setMaterialContent(lightColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
@@ -193,6 +211,21 @@
@OptIn(ExperimentalMaterial3Api::class)
@Test
+ fun sliderTest_end_rtl() {
+ rule.setMaterialContent(lightColorScheme()) {
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ Box(wrap.testTag(wrapperTestTag)) {
+ Slider(
+ remember { SliderState(1f) }
+ )
+ }
+ }
+ }
+ assertSliderAgainstGolden("slider_end_rtl")
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Test
fun sliderTest_middle_steps() {
rule.setMaterialContent(lightColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
@@ -349,6 +382,32 @@
@OptIn(ExperimentalMaterial3Api::class)
@Test
+ fun rangeSliderTest_middle_no_stop_indicator_rtl() {
+ rule.setMaterialContent(lightColorScheme()) {
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ Box(wrap.testTag(wrapperTestTag)) {
+ RangeSlider(
+ state = remember {
+ RangeSliderState(
+ 0.5f,
+ 1f
+ )
+ },
+ track = {
+ SliderDefaults.Track(
+ rangeSliderState = it,
+ drawStopIndicator = null
+ )
+ }
+ )
+ }
+ }
+ }
+ assertSliderAgainstGolden("rangeSlider_middle_no_stop_indicator_rtl")
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Test
fun rangeSliderTest_middle_steps_disabled() {
rule.setMaterialContent(lightColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
@@ -449,6 +508,38 @@
@OptIn(ExperimentalMaterial3Api::class)
@Test
+ fun rangeSliderTest_asymmetric_startEnd() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Box(wrap.testTag(wrapperTestTag)) {
+ RangeSlider(
+ remember {
+ RangeSliderState(0.25f, 0.6f)
+ }
+ )
+ }
+ }
+ assertSliderAgainstGolden("rangeSliderTest_asymmetric_startEnd")
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Test
+ fun rangeSliderTest_asymmetric_startEnd_rtl() {
+ rule.setMaterialContent(lightColorScheme()) {
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ Box(wrap.testTag(wrapperTestTag)) {
+ RangeSlider(
+ remember {
+ RangeSliderState(0.25f, 0.6f)
+ }
+ )
+ }
+ }
+ }
+ assertSliderAgainstGolden("rangeSliderTest_asymmetric_startEnd_rtl")
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Test
fun rangeSliderTest_steps_customColors() {
rule.setMaterialContent(lightColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt
index e118d99..102eb3c 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt
@@ -753,6 +753,55 @@
@OptIn(ExperimentalMaterial3Api::class)
@Test
+ fun slider_onValueChangeFinishedWithSnackbar() {
+ lateinit var state: SliderState
+ var slop = 0f
+
+ rule.setMaterialContent(lightColorScheme()) {
+ val snackbarHostState = remember { SnackbarHostState() }
+ val scope = rememberCoroutineScope()
+ Scaffold(
+ snackbarHost = { SnackbarHost(snackbarHostState) },
+ content = { _ ->
+ state = remember {
+ SliderState(
+ value = 0f,
+ onValueChangeFinished = {
+ scope.launch {
+ snackbarHostState.showSnackbar("Snackbar Description")
+ }
+ }
+ )
+ }
+ slop = LocalViewConfiguration.current.touchSlop
+ Slider(
+ state = state,
+ modifier = Modifier.testTag(tag)
+ )
+ }
+ )
+ }
+
+ rule.runOnUiThread {
+ Truth.assertThat(state.value).isEqualTo(0f)
+ }
+
+ var expected = 0f
+
+ rule.onNodeWithTag(tag)
+ .performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 0f))
+ up()
+ expected = calculateFraction(left, right, centerX + 100 - slop)
+ }
+ rule.runOnIdle {
+ Truth.assertThat(state.value).isWithin(SliderTolerance).of(expected)
+ }
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Test
fun rangeSlider_dragThumb() {
val state = RangeSliderState(0f, 1f)
var slop = 0f
@@ -1355,6 +1404,59 @@
}
}
}
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Test
+ fun rangeSlider_onValueChangeFinishedWithSnackbar() {
+ lateinit var state: RangeSliderState
+ var slop = 0f
+
+ rule.setMaterialContent(lightColorScheme()) {
+ val snackbarHostState = remember { SnackbarHostState() }
+ val scope = rememberCoroutineScope()
+ Scaffold(
+ snackbarHost = { SnackbarHost(snackbarHostState) },
+ content = { _ ->
+ state = remember {
+ RangeSliderState(
+ activeRangeStart = 0f,
+ activeRangeEnd = 1f,
+ onValueChangeFinished = {
+ scope.launch {
+ snackbarHostState.showSnackbar("Snackbar Description")
+ }
+ }
+ )
+ }
+ slop = LocalViewConfiguration.current.touchSlop
+ RangeSlider(
+ state = state,
+ modifier = Modifier.testTag(tag)
+ )
+ }
+ )
+ }
+
+ rule.runOnUiThread {
+ Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+ Truth.assertThat(state.activeRangeEnd).isEqualTo(1f)
+ }
+
+ var expected = 0f
+
+ rule.onNodeWithTag(tag)
+ .performTouchInput {
+ down(center)
+ moveBy(Offset(slop, 0f))
+ moveBy(Offset(100f, 0f))
+ up()
+ expected = calculateFraction(left, right, centerX + 100)
+ }
+ rule.runOnIdle {
+ Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+ Truth.assertThat(state.activeRangeEnd).isWithin(SliderTolerance).of(expected)
+ }
+ }
}
@OptIn(ExperimentalMaterial3Api::class)
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt
index 3997f34..425848b 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt
@@ -90,11 +90,12 @@
state = rememberSwipeToDismissBoxState(SwipeToDismissBoxValue.StartToEnd),
backgroundContent = { }
) {
- Box(
- Modifier
- .fillMaxSize()
- .testTag(dismissContentTag)
- ) }
+ Box(
+ Modifier
+ .fillMaxSize()
+ .testTag(dismissContentTag)
+ )
+ }
}
val width = rule.rootWidth()
@@ -103,17 +104,40 @@
}
@Test
+ fun swipeDismiss_testOffset_whenDismissedToEnd_rtl() {
+ rule.setContent {
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ SwipeToDismissBox(
+ state = rememberSwipeToDismissBoxState(SwipeToDismissBoxValue.StartToEnd),
+ backgroundContent = { }
+ ) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .testTag(dismissContentTag)
+ )
+ }
+ }
+ }
+
+ val width = rule.rootWidth()
+ rule.onNodeWithTag(dismissContentTag)
+ .assertLeftPositionInRootIsEqualTo(-width)
+ }
+
+ @Test
fun swipeDismiss_testOffset_whenDismissedToStart() {
rule.setContent {
SwipeToDismissBox(
state = rememberSwipeToDismissBoxState(SwipeToDismissBoxValue.EndToStart),
backgroundContent = { },
) {
- Box(
- Modifier
- .fillMaxSize()
- .testTag(dismissContentTag)
- ) }
+ Box(
+ Modifier
+ .fillMaxSize()
+ .testTag(dismissContentTag)
+ )
+ }
}
val width = rule.rootWidth()
@@ -122,6 +146,28 @@
}
@Test
+ fun swipeDismiss_testOffset_whenDismissedToStart_rtl() {
+ rule.setContent {
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ SwipeToDismissBox(
+ state = rememberSwipeToDismissBoxState(SwipeToDismissBoxValue.EndToStart),
+ backgroundContent = { },
+ ) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .testTag(dismissContentTag)
+ )
+ }
+ }
+ }
+
+ val width = rule.rootWidth()
+ rule.onNodeWithTag(dismissContentTag)
+ .assertLeftPositionInRootIsEqualTo(width)
+ }
+
+ @Test
fun swipeDismiss_testBackgroundMatchesContentSize() {
rule.setContent {
SwipeToDismissBox(
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldTest.kt
index a9fec36..6c1771f4 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldTest.kt
@@ -272,7 +272,6 @@
}
}
- @ExperimentalComposeUiApi
@Test
fun testTextField_showHideKeyboardBasedOnFocus() {
val (focusRequester, parentFocusRequester) = FocusRequester.createRefs()
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
index 83c03b3..124a0db 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
@@ -100,6 +100,36 @@
}
}
+ @Test
+ fun carousel_snapsToPage() {
+ // Arrange
+ createCarousel()
+
+ // Act
+ rule.onNodeWithTag(CarouselTestTag)
+ .performTouchInput { swipeWithVelocity(centerRight, centerLeft, 1000f) }
+
+ // Assert
+ rule.runOnIdle {
+ assertThat(carouselState.pagerState.currentPageOffsetFraction).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun uncontainedCarousel_doesntSnapToPage() {
+ // Arrange
+ createUncontainedCarousel()
+
+ // Act
+ rule.onNodeWithTag(CarouselTestTag)
+ .performTouchInput { swipeWithVelocity(centerRight, centerLeft, 1000f) }
+
+ // Assert
+ rule.runOnIdle {
+ assertThat(carouselState.pagerState.currentPageOffsetFraction).isNotEqualTo(0)
+ }
+ }
+
@Composable
internal fun Item(index: Int) {
Box(
@@ -143,6 +173,26 @@
)
}
}
+
+ private fun createUncontainedCarousel(
+ initialItem: Int = 0,
+ itemCount: () -> Int = { DefaultItemCount },
+ modifier: Modifier = Modifier.width(412.dp).height(221.dp),
+ content: @Composable CarouselScope.(item: Int) -> Unit = { Item(index = it) }
+ ) {
+ rule.setMaterialContent(lightColorScheme()) {
+ val state = rememberCarouselState(initialItem, itemCount).also {
+ carouselState = it
+ }
+ HorizontalUncontainedCarousel(
+ state = state,
+ itemSize = 150.dp,
+ modifier = modifier.testTag(CarouselTestTag),
+ itemSpacing = 0.dp,
+ content = content,
+ )
+ }
+ }
}
internal const val DefaultItemCount = 10
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidAlertDialog.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidAlertDialog.android.kt
index cf9558b..70479cd 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidAlertDialog.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidAlertDialog.android.kt
@@ -229,7 +229,7 @@
val textContentColor: Color @Composable get() = DialogTokens.SupportingTextColor.value
/** The default tonal elevation for alert dialogs */
- val TonalElevation: Dp = DialogTokens.ContainerElevation
+ val TonalElevation: Dp = 0.dp
}
private val ButtonsMainAxisSpacing = 8.dp
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt
index 813f8ad..78b4d18 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt
@@ -86,7 +86,14 @@
tonalElevation = tonalElevation,
) {
Column(verticalArrangement = Arrangement.SpaceBetween) {
- content()
+ // Wrap the content with a Box and Modifier.weight(1f) to ensure that any "confirm"
+ // and "dismiss" buttons are not pushed out of view when running on small screens,
+ // or when nesting a DateRangePicker.
+ // Fill is false to support collapsing the dialog's height when switching to input
+ // mode.
+ Box(Modifier.weight(1f, fill = false)) {
+ this@Column.content()
+ }
// Buttons
Box(
modifier = Modifier
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
index d1a058f..9762c3f 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
@@ -58,7 +58,6 @@
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -78,7 +77,6 @@
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.AbstractComposeView
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.ViewRootForInspector
@@ -132,7 +130,9 @@
* @param contentColor The preferred color for content inside this bottom sheet. Defaults to either
* the matching content color for [containerColor], or to the current [LocalContentColor] if
* [containerColor] is not a color from the theme.
- * @param tonalElevation The tonal elevation of this bottom sheet.
+ * @param tonalElevation when [containerColor] is [ColorScheme.surface], a translucent primary color
+ * overlay is applied on top of the container. A higher tonal elevation value will result in a
+ * darker color in light theme and lighter color in dark theme. See also: [Surface].
* @param scrimColor Color of the scrim that obscures content when the bottom sheet is open.
* @param dragHandle Optional visual marker to swipe the bottom sheet.
* @param windowInsets window insets to be passed to the bottom sheet window via [PaddingValues]
@@ -151,18 +151,13 @@
shape: Shape = BottomSheetDefaults.ExpandedShape,
containerColor: Color = BottomSheetDefaults.ContainerColor,
contentColor: Color = contentColorFor(containerColor),
- tonalElevation: Dp = BottomSheetDefaults.Elevation,
+ tonalElevation: Dp = 0.dp,
scrimColor: Color = BottomSheetDefaults.ScrimColor,
dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
windowInsets: WindowInsets = BottomSheetDefaults.windowInsets,
properties: ModalBottomSheetProperties = ModalBottomSheetDefaults.properties(),
content: @Composable ColumnScope.() -> Unit,
) {
- // b/291735717 Remove this once deprecated methods without density are removed
- val density = LocalDensity.current
- SideEffect {
- sheetState.density = density
- }
val scope = rememberCoroutineScope()
val animateToDismiss: () -> Unit = {
if (sheetState.anchoredDraggableState.confirmValueChange(Hidden)) {
@@ -470,7 +465,11 @@
fun rememberModalBottomSheetState(
skipPartiallyExpanded: Boolean = false,
confirmValueChange: (SheetValue) -> Boolean = { true },
-) = rememberSheetState(skipPartiallyExpanded, confirmValueChange, Hidden)
+) = rememberSheetState(
+ skipPartiallyExpanded = skipPartiallyExpanded,
+ confirmValueChange = confirmValueChange,
+ initialValue = Hidden,
+)
@Composable
private fun Scrim(
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.android.kt
index f24525f..150dd88 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.android.kt
@@ -705,7 +705,7 @@
@ExperimentalMaterial3Api
object SearchBarDefaults {
/** Default tonal elevation for a search bar. */
- val TonalElevation: Dp = SearchBarTokens.ContainerElevation
+ val TonalElevation: Dp = ElevationTokens.Level0
/** Default shadow elevation for a search bar. */
val ShadowElevation: Dp = ElevationTokens.Level0
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/ExposedDropdownMenuPopup.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/ExposedDropdownMenuPopup.android.kt
index 9089e62..0487b58 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/ExposedDropdownMenuPopup.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/ExposedDropdownMenuPopup.android.kt
@@ -28,7 +28,7 @@
import android.view.ViewOutlineProvider
import android.view.ViewTreeObserver
import android.view.WindowManager
-import android.view.accessibility.AccessibilityManager
+import androidx.compose.material3.touchExplorationState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.DisposableEffect
@@ -50,7 +50,6 @@
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.AbstractComposeView
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalView
@@ -87,19 +86,17 @@
val view = LocalView.current
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
- val context = LocalContext.current
- val a11yManager =
- context.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
+ val isTouchExplorationEnabled by touchExplorationState()
val parentComposition = rememberCompositionContext()
val currentContent by rememberUpdatedState(content)
val popupId = rememberSaveable { UUID.randomUUID() }
- val popupLayout = remember(a11yManager) {
+ val popupLayout = remember(isTouchExplorationEnabled) {
PopupLayout(
onDismissRequest = onDismissRequest,
composeView = view,
positionProvider = popupPositionProvider,
- focusable = a11yManager?.isTouchExplorationEnabled == true,
+ isTouchExplorationEnabled = isTouchExplorationEnabled,
density = density,
popupId = popupId,
).apply {
@@ -207,7 +204,7 @@
private var onDismissRequest: (() -> Unit)?,
private val composeView: View,
private val positionProvider: PopupPositionProvider,
- private val focusable: Boolean,
+ private val isTouchExplorationEnabled: Boolean,
density: Density,
popupId: UUID
) : AbstractComposeView(composeView.context),
@@ -398,13 +395,15 @@
gravity = Gravity.START or Gravity.TOP
// Flags specific to exposed dropdown menu.
- flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH and
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL.inv() or
+ flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
- flags = if (focusable) {
- flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv()
+ flags = if (isTouchExplorationEnabled) {
+ // In order for TalkBack focus to jump to the menu when opened, it needs to be
+ // focusable and touch modal (NOT_FOCUSABLE and NOT_TOUCH_MODAL are *not* set)
+ flags and WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL.inv()
} else {
- flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ flags
}
softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
diff --git a/compose/material3/material3/src/androidMain/res/values-de/strings.xml b/compose/material3/material3/src/androidMain/res/values-de/strings.xml
index fe8f168..cbe9197 100644
--- a/compose/material3/material3/src/androidMain/res/values-de/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-de/strings.xml
@@ -62,7 +62,7 @@
<string name="m3c_tooltip_pane_description" msgid="5460405025248574620">"Kurzinfo"</string>
<string name="m3c_tooltip_long_press_label" msgid="1805687647081129904">"Kurzinfo anzeigen"</string>
<string name="m3c_time_picker_pm" msgid="6616362054113087709">"Nachmittags"</string>
- <string name="m3c_time_picker_am" msgid="2786685010796619560">"Vormittags"</string>
+ <string name="m3c_time_picker_am" msgid="2786685010796619560">"AM"</string>
<string name="m3c_time_picker_period_toggle_description" msgid="5865171949528594571">"Vormittags oder nachmittags auswählen"</string>
<string name="m3c_time_picker_hour_selection" msgid="8876759303332837035">"Stunde auswählen"</string>
<string name="m3c_time_picker_minute_selection" msgid="4699133535056739733">"Minuten auswählen"</string>
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineSnapPositionTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineSnapPositionTest.kt
index 3b5d5c9..86a5b81 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineSnapPositionTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineSnapPositionTest.kt
@@ -40,6 +40,34 @@
repeat(itemCount) { i -> assertThat(map[i]).isEqualTo(expectedSnapPositions[i]) }
}
+ @Test
+ fun testSnapPosition_forStartAlignedStrategyWithMultipleFocal() {
+ val itemCount = 5
+ val map = calculateSnapPositions(testStartAlignedStrategyWithMultipleFocal(), itemCount)
+ val expectedSnapPositions = arrayListOf(0, 0, 75, 200, 200)
+ repeat(itemCount) { i -> assertThat(map[i]).isEqualTo(expectedSnapPositions[i]) }
+ }
+
+ @Test
+ fun testSnapPosition_forStartAlignedStrategyWithMultipleFocalAndLessItems() {
+ val strategy = testStartAlignedStrategyWithMultipleFocal()
+ // item count is the number of keylines minus anchor keylines
+ val itemCount = strategy.defaultKeylines.size - 2
+ val map = calculateSnapPositions(strategy, itemCount)
+ val expectedSnapPositions = arrayListOf(0, 75, 200, 200)
+ repeat(itemCount) { i -> assertThat(map[i]).isEqualTo(expectedSnapPositions[i]) }
+ }
+
+ @Test
+ fun testSnapPosition_forStartAlignedStrategyWithLessItemsThanFocal() {
+ val strategy = testStartAlignedStrategyWithMultipleFocal()
+ // item count is the number of focal keylines minus one
+ val itemCount = strategy.defaultKeylines.lastFocalIndex -
+ strategy.defaultKeylines.firstFocalIndex
+ val map = calculateSnapPositions(strategy, itemCount)
+ repeat(itemCount) { i -> assertThat(map[i]).isEqualTo(0) }
+ }
+
// Test strategy that is center aligned and has a complex keyline state, ie:
// [xsmall - small - medium - large - medium - small - xsmall]
// In this case, we expect this:
@@ -93,4 +121,27 @@
}
return Strategy { keylineList }.apply(1000f)
}
+
+ // Test strategy that is start aligned:
+ // [xs - large - large - medium - small - xsmall]
+ // In this case, we expect this:
+ // snap position at item 0: [xs-l-l-m-s-xs]
+ // snap position at third last item: [xs-s-l-l-m-xs]
+ // snap position at second last item: [xs-s-m-l-l-xs]
+ // snap position at last item: [xs-s-m-l-l-xs]
+ private fun testStartAlignedStrategyWithMultipleFocal(): Strategy {
+ val xSmallSize = 5f
+ val smallSize = 75f
+ val mediumSize = 125f
+ val largeSize = 400f
+ val keylineList = keylineListOf(carouselMainAxisSize = 1000f, CarouselAlignment.Start) {
+ add(xSmallSize, isAnchor = true)
+ add(largeSize)
+ add(largeSize)
+ add(mediumSize)
+ add(smallSize)
+ add(xSmallSize, isAnchor = true)
+ }
+ return Strategy { keylineList }.apply(1000f)
+ }
}
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
index f598ea6..afdc6d9 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
@@ -348,6 +348,19 @@
assertThat(strategy1.hashCode()).isNotEqualTo(strategy2.hashCode())
}
+ @Test
+ fun testStrategy_startAlignedStrategyWithNegativeMaxScroll() {
+ val itemCount = 1
+ val carouselMainAxisSize = large + medium + small
+ val maxScrollOffset = (itemCount * large) - carouselMainAxisSize
+ val defaultKeylineList = createStartAlignedKeylineList()
+
+ val strategy = Strategy { defaultKeylineList }.apply(carouselMainAxisSize)
+
+ assertThat(strategy.getKeylineListForScrollOffset(0f, maxScrollOffset))
+ .isEqualTo(defaultKeylineList)
+ }
+
private fun assertEqualWithFloatTolerance(
tolerance: Float,
actual: Keyline,
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt
index d1b08ed..f107932 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt
@@ -88,6 +88,7 @@
)
val strategy = Strategy { keylineList }.apply(carouselSize)
val keylines = strategy.defaultKeylines
+ val rightAnchorSize = with(Density) { StrategyDefaults.AnchorSize.toPx() }
// The layout should be [xSmall-large-large-large-medium-xSmall] where medium is a size
// such that a third of it is cut off.
@@ -101,8 +102,9 @@
assertThat(keylines[4].offset).isEqualTo(393.75f) // itemSize * 3 + (25 * 1.5f / 2)
assertThat(keylines[0].size).isEqualTo(18.75f) // half the med size is the anchor size
assertThat(keylines[0].offset).isEqualTo(-9.375f) // -18.75f/2
+ assertThat(keylines[5].size).isEqualTo(rightAnchorSize)
assertThat(keylines[5].offset)
- .isEqualTo(421.875f) // itemSize*3 + 25f * 1.5f + 18.75f/2
+ .isEqualTo(itemSize * 3 + (25f * 1.5f) + (rightAnchorSize / 2f))
}
@Test
@@ -121,6 +123,7 @@
)
val strategy = Strategy { keylineList }.apply(carouselSize)
val keylines = strategy.defaultKeylines
+ val rightAnchorSize = with(Density) { StrategyDefaults.AnchorSize.toPx() }
// The layout should be [xSmall-large-large-large-medium-xSmall]
assertThat(keylines.size).isEqualTo(6)
@@ -130,9 +133,9 @@
// remainingSpace * 120%
assertThat(keylines[4].size).isEqualTo(85 * 1.2f)
assertThat(keylines[0].size).isEqualTo(85 * 1.2f * 0.5f)
- assertThat(keylines[5].size).isEqualTo(85 * 1.2f * 0.5f)
+ assertThat(keylines[5].size).isEqualTo(rightAnchorSize)
assertThat(keylines[0].offset).isEqualTo(-(85 * 1.2f * 0.5f) / 2f)
assertThat(keylines[5].offset)
- .isEqualTo(itemSize * 3 + 85 * 1.2f + (85 * 1.2f * 0.5f) / 2f)
+ .isEqualTo(itemSize * 3 + 85 * 1.2f + rightAnchorSize / 2f)
}
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index 8aae404..6b97ee4 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -727,13 +727,7 @@
get() {
return defaultTopAppBarColorsCached ?: TopAppBarColors(
containerColor = fromToken(TopAppBarSmallTokens.ContainerColor),
- scrolledContainerColor =
- if (fromToken(TopAppBarSmallTokens.ContainerColor) == surface) {
- surfaceColorAtElevation(
- elevation = TopAppBarSmallTokens.OnScrollContainerElevation)
- } else {
- fromToken(TopAppBarSmallTokens.ContainerColor)
- },
+ scrolledContainerColor = fromToken(TopAppBarSmallTokens.OnScrollContainerColor),
navigationIconContentColor = fromToken(TopAppBarSmallTokens.LeadingIconColor),
titleContentColor = fromToken(TopAppBarSmallTokens.HeadlineColor),
actionIconContentColor = fromToken(TopAppBarSmallTokens.TrailingIconColor),
@@ -765,10 +759,7 @@
@Composable
fun smallTopAppBarColors(
containerColor: Color = TopAppBarSmallTokens.ContainerColor.value,
- scrolledContainerColor: Color = MaterialTheme.colorScheme.applyTonalElevation(
- backgroundColor = containerColor,
- elevation = TopAppBarSmallTokens.OnScrollContainerElevation
- ),
+ scrolledContainerColor: Color = TopAppBarSmallTokens.OnScrollContainerColor.value,
navigationIconContentColor: Color = TopAppBarSmallTokens.LeadingIconColor.value,
titleContentColor: Color = TopAppBarSmallTokens.HeadlineColor.value,
actionIconContentColor: Color = TopAppBarSmallTokens.TrailingIconColor.value,
@@ -829,15 +820,9 @@
return defaultCenterAlignedTopAppBarColorsCached ?: TopAppBarColors(
containerColor = fromToken(TopAppBarSmallCenteredTokens.ContainerColor),
scrolledContainerColor =
- if (fromToken(TopAppBarSmallCenteredTokens.ContainerColor) == surface) {
- surfaceColorAtElevation(
- elevation = TopAppBarSmallTokens.OnScrollContainerElevation
- )
- } else {
- fromToken(TopAppBarSmallCenteredTokens.ContainerColor)
- },
+ fromToken(TopAppBarSmallCenteredTokens.OnScrollContainerColor),
navigationIconContentColor =
- fromToken(TopAppBarSmallCenteredTokens.LeadingIconColor),
+ fromToken(TopAppBarSmallCenteredTokens.LeadingIconColor),
titleContentColor = fromToken(TopAppBarSmallCenteredTokens.HeadlineColor),
actionIconContentColor = fromToken(TopAppBarSmallCenteredTokens.TrailingIconColor),
).also {
@@ -884,12 +869,7 @@
get() {
return defaultMediumTopAppBarColorsCached ?: TopAppBarColors(
containerColor = fromToken(TopAppBarMediumTokens.ContainerColor),
- scrolledContainerColor =
- if (fromToken(TopAppBarMediumTokens.ContainerColor) == surface) {
- surfaceColorAtElevation(elevation = TopAppBarSmallTokens.OnScrollContainerElevation)
- } else {
- fromToken(TopAppBarMediumTokens.ContainerColor)
- },
+ scrolledContainerColor = fromToken(TopAppBarSmallTokens.OnScrollContainerColor),
navigationIconContentColor = fromToken(TopAppBarMediumTokens.LeadingIconColor),
titleContentColor = fromToken(TopAppBarMediumTokens.HeadlineColor),
actionIconContentColor = fromToken(TopAppBarMediumTokens.TrailingIconColor),
@@ -937,14 +917,7 @@
get() {
return defaultLargeTopAppBarColorsCached ?: TopAppBarColors(
containerColor = fromToken(TopAppBarLargeTokens.ContainerColor),
- scrolledContainerColor =
- if (fromToken(TopAppBarLargeTokens.ContainerColor) == surface) {
- surfaceColorAtElevation(
- elevation = TopAppBarSmallTokens.OnScrollContainerElevation
- )
- } else {
- fromToken(TopAppBarLargeTokens.ContainerColor)
- },
+ scrolledContainerColor = fromToken(TopAppBarSmallTokens.OnScrollContainerColor),
navigationIconContentColor = fromToken(TopAppBarLargeTokens.LeadingIconColor),
titleContentColor = fromToken(TopAppBarLargeTokens.HeadlineColor),
actionIconContentColor = fromToken(TopAppBarLargeTokens.TrailingIconColor),
@@ -1296,7 +1269,7 @@
val containerColor: Color @Composable get() = BottomAppBarTokens.ContainerColor.value
/** Default elevation used for [BottomAppBar] */
- val ContainerElevation: Dp = BottomAppBarTokens.ContainerElevation
+ val ContainerElevation: Dp = 0.dp
/**
* Default padding used for [BottomAppBar] when content are default size (24dp) icons in
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
index 79cdce2..6e90401 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
@@ -28,7 +28,6 @@
import androidx.compose.material3.SheetValue.Hidden
import androidx.compose.material3.SheetValue.PartiallyExpanded
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -44,6 +43,7 @@
import androidx.compose.ui.semantics.expand
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMaxOfOrNull
@@ -79,7 +79,9 @@
* @param sheetContentColor the preferred content color provided by the bottom sheet to its
* children. Defaults to the matching content color for [sheetContainerColor], or if that is
* not a color from the theme, this will keep the same content color set above the bottom sheet.
- * @param sheetTonalElevation the tonal elevation of the bottom sheet
+ * @param sheetTonalElevation when [sheetContainerColor] is [ColorScheme.surface], a translucent
+ * primary color overlay is applied on top of the container. A higher tonal elevation value will
+ * result in a darker color in light theme and lighter color in dark theme. See also: [Surface].
* @param sheetShadowElevation the shadow elevation of the bottom sheet
* @param sheetDragHandle optional visual marker to pull the scaffold's bottom sheet
* @param sheetSwipeEnabled whether the sheet swiping is enabled and should react to the user's
@@ -108,7 +110,7 @@
sheetShape: Shape = BottomSheetDefaults.ExpandedShape,
sheetContainerColor: Color = BottomSheetDefaults.ContainerColor,
sheetContentColor: Color = contentColorFor(sheetContainerColor),
- sheetTonalElevation: Dp = BottomSheetDefaults.Elevation,
+ sheetTonalElevation: Dp = 0.dp,
sheetShadowElevation: Dp = BottomSheetDefaults.Elevation,
sheetDragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
sheetSwipeEnabled: Boolean = true,
@@ -195,7 +197,11 @@
initialValue: SheetValue = PartiallyExpanded,
confirmValueChange: (SheetValue) -> Boolean = { true },
skipHiddenState: Boolean = true,
-) = rememberSheetState(false, confirmValueChange, initialValue, skipHiddenState)
+) = rememberSheetState(
+ confirmValueChange = confirmValueChange,
+ initialValue = initialValue,
+ skipHiddenState = skipHiddenState,
+)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -332,11 +338,6 @@
containerColor: Color,
contentColor: Color,
) {
- // b/291735717 Remove this once deprecated methods without density are removed
- val density = LocalDensity.current
- SideEffect {
- sheetState.density = density
- }
Layout(
contents = listOf<@Composable () -> Unit>(
topBar ?: { },
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
index 293b3d7..f811d96 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
@@ -90,9 +90,8 @@
* @param colors [ButtonColors] that will be used to resolve the colors for this button in different
* states. See [ButtonDefaults.buttonColors].
* @param elevation [ButtonElevation] used to resolve the elevation for this button in different
- * states. This controls the size of the shadow below the button. Additionally, when the container
- * color is [ColorScheme.surface], this controls the amount of primary color applied as an overlay.
- * See [ButtonElevation.shadowElevation] and [ButtonElevation.tonalElevation].
+ * states. This controls the size of the shadow below the button. See
+ * [ButtonElevation.shadowElevation].
* @param border the border to draw around the container of this button
* @param contentPadding the spacing values to apply internally between the container and the
* content
@@ -119,7 +118,6 @@
val containerColor = colors.containerColor(enabled)
val contentColor = colors.contentColor(enabled)
val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp
- val tonalElevation = elevation?.tonalElevation(enabled) ?: 0.dp
Surface(
onClick = onClick,
modifier = modifier.semantics { role = Role.Button },
@@ -127,7 +125,6 @@
shape = shape,
color = containerColor,
contentColor = contentColor,
- tonalElevation = tonalElevation,
shadowElevation = shadowElevation,
border = border,
interactionSource = interactionSource
@@ -865,28 +862,11 @@
private val disabledElevation: Dp,
) {
/**
- * Represents the tonal elevation used in a button, depending on its [enabled] state.
- *
- * Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
- * When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
- * color in light theme and lighter color in dark theme.
- *
- * See [shadowElevation] which controls the elevation of the shadow drawn around the button.
- *
- * @param enabled whether the button is enabled
- */
- internal fun tonalElevation(enabled: Boolean): Dp {
- return if (enabled) defaultElevation else disabledElevation
- }
-
- /**
* Represents the shadow elevation used in a button, depending on its [enabled] state and
* [interactionSource].
*
* Shadow elevation is used to apply a shadow around the button to give it higher emphasis.
*
- * See [tonalElevation] which controls the elevation with a color shift to the surface.
- *
* @param enabled whether the button is enabled
* @param interactionSource the [InteractionSource] for this button
*/
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Card.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Card.kt
index b5ffb09..187bef0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Card.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Card.kt
@@ -87,7 +87,6 @@
shape = shape,
color = colors.containerColor(enabled = true),
contentColor = colors.contentColor(enabled = true),
- tonalElevation = elevation.tonalElevation(enabled = true),
shadowElevation = elevation.shadowElevation(enabled = true, interactionSource = null).value,
border = border,
) {
@@ -149,7 +148,6 @@
shape = shape,
color = colors.containerColor(enabled),
contentColor = colors.contentColor(enabled),
- tonalElevation = elevation.tonalElevation(enabled),
shadowElevation = elevation.shadowElevation(enabled, interactionSource).value,
border = border,
interactionSource = interactionSource,
@@ -479,15 +477,9 @@
return defaultCardColorsCached ?: CardColors(
containerColor = fromToken(FilledCardTokens.ContainerColor),
contentColor = contentColorFor(fromToken(FilledCardTokens.ContainerColor)),
- disabledContainerColor = fromToken(
- FilledCardTokens.DisabledContainerColor
- )
+ disabledContainerColor = fromToken(FilledCardTokens.DisabledContainerColor)
.copy(alpha = FilledCardTokens.DisabledContainerOpacity)
- .compositeOver(
- surfaceColorAtElevation(
- FilledCardTokens.DisabledContainerElevation
- )
- ),
+ .compositeOver(fromToken(FilledCardTokens.ContainerColor)),
disabledContentColor =
contentColorFor(fromToken(FilledCardTokens.ContainerColor)).copy(DisabledAlpha),
).also {
@@ -532,11 +524,7 @@
disabledContainerColor =
fromToken(ElevatedCardTokens.DisabledContainerColor)
.copy(alpha = ElevatedCardTokens.DisabledContainerOpacity)
- .compositeOver(
- surfaceColorAtElevation(
- ElevatedCardTokens.DisabledContainerElevation
- )
- ),
+ .compositeOver(fromToken(ElevatedCardTokens.DisabledContainerColor)),
disabledContentColor =
contentColorFor(fromToken(ElevatedCardTokens.ContainerColor)).copy(DisabledAlpha),
).also {
@@ -598,11 +586,7 @@
} else {
OutlinedCardTokens.DisabledOutlineColor.value
.copy(alpha = OutlinedCardTokens.DisabledOutlineOpacity)
- .compositeOver(
- MaterialTheme.colorScheme.surfaceColorAtElevation(
- OutlinedCardTokens.DisabledContainerElevation
- )
- )
+ .compositeOver(ElevatedCardTokens.ContainerColor.value)
}
return remember(color) { BorderStroke(OutlinedCardTokens.OutlineWidth, color) }
}
@@ -625,27 +609,11 @@
private val disabledElevation: Dp
) {
/**
- * Represents the tonal elevation used in a card, depending on its [enabled].
- *
- * Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
- * When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
- * color in light theme and lighter color in dark theme.
- *
- * See [shadowElevation] which controls the elevation of the shadow drawn around the card.
- *
- * @param enabled whether the card is enabled
- */
- internal fun tonalElevation(enabled: Boolean): Dp =
- if (enabled) defaultElevation else disabledElevation
-
- /**
* Represents the shadow elevation used in a card, depending on its [enabled] state and
* [interactionSource].
*
* Shadow elevation is used to apply a shadow around the card to give it higher emphasis.
*
- * See [tonalElevation] which controls the elevation with a color shift to the surface.
- *
* @param enabled whether the card is enabled
* @param interactionSource the [InteractionSource] for this card
*/
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
index 87367dd..a645eb7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
@@ -1257,8 +1257,8 @@
return defaultFilterChipColorsCached ?: SelectableChipColors(
containerColor = Color.Transparent,
labelColor = fromToken(FilterChipTokens.UnselectedLabelTextColor),
- leadingIconColor = fromToken(FilterChipTokens.LeadingIconUnselectedColor),
- trailingIconColor = fromToken(FilterChipTokens.TrailingIconUnselectedColor),
+ leadingIconColor = fromToken(FilterChipTokens.UnselectedLeadingIconColor),
+ trailingIconColor = fromToken(FilterChipTokens.UnselectedTrailingIconColor),
disabledContainerColor = Color.Transparent,
disabledLabelColor = fromToken(FilterChipTokens.DisabledLabelTextColor)
.copy(alpha = FilterChipTokens.DisabledLabelTextOpacity),
@@ -1404,8 +1404,8 @@
return defaultElevatedFilterChipColorsCached ?: SelectableChipColors(
containerColor = fromToken(FilterChipTokens.ElevatedUnselectedContainerColor),
labelColor = fromToken(FilterChipTokens.UnselectedLabelTextColor),
- leadingIconColor = fromToken(FilterChipTokens.LeadingIconUnselectedColor),
- trailingIconColor = fromToken(FilterChipTokens.TrailingIconUnselectedColor),
+ leadingIconColor = fromToken(FilterChipTokens.UnselectedLeadingIconColor),
+ trailingIconColor = fromToken(FilterChipTokens.UnselectedTrailingIconColor),
disabledContainerColor = fromToken(FilterChipTokens.ElevatedDisabledContainerColor),
disabledLabelColor = fromToken(FilterChipTokens.DisabledLabelTextColor)
.copy(alpha = FilterChipTokens.DisabledLabelTextOpacity),
@@ -1873,7 +1873,6 @@
enabled = enabled,
shape = shape,
color = colors.containerColor(enabled),
- tonalElevation = elevation?.tonalElevation(enabled) ?: 0.dp,
shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp,
border = border,
interactionSource = interactionSource
@@ -1922,7 +1921,6 @@
enabled = enabled,
shape = shape,
color = colors.containerColor(enabled, selected).value,
- tonalElevation = elevation?.tonalElevation(enabled) ?: 0.dp,
shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp,
border = border,
interactionSource = interactionSource
@@ -2043,10 +2041,8 @@
* Represents the elevation used in a selectable chip in different states.
*
* Note that this default implementation does not take into consideration the `selectable` state
- * passed into its [tonalElevation] and [shadowElevation]. If you wish to apply that state, use a
- * different [SelectableChipElevation].
- *
- * Note that its [tonalElevation] implementation only depends on [elevation] and [disabledElevation]
+ * passed into its [shadowElevation]. If you wish to apply that state, use a different
+ * [SelectableChipElevation].
*
* @param elevation the elevation used when the chip is enabled.
* @param pressedElevation the elevation used when the chip is pressed.
@@ -2065,27 +2061,11 @@
val disabledElevation: Dp
) {
/**
- * Represents the tonal elevation used in a chip, depending on its [enabled] state.
- *
- * Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
- * When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
- * color in light theme and lighter color in dark theme.
- *
- * See [shadowElevation] which controls the elevation of the shadow drawn around the chip.
- *
- * @param enabled whether the chip is enabled
- */
- internal fun tonalElevation(enabled: Boolean): Dp {
- return if (enabled) elevation else disabledElevation
- }
-
- /**
* Represents the shadow elevation used in a chip, depending on its [enabled] state and
* [interactionSource].
*
* Shadow elevation is used to apply a shadow around the chip to give it higher emphasis.
*
- * See [tonalElevation] which controls the elevation with a color shift to the surface.
*
* @param enabled whether the chip is enabled
* @param interactionSource the [InteractionSource] for this chip
@@ -2210,8 +2190,6 @@
/**
* Represents the elevation used in a selectable chip in different states.
*
- * Note that its [tonalElevation] implementation only depends on [elevation] and [disabledElevation]
- *
* @param elevation the elevation used when the chip is enabled.
* @param pressedElevation the elevation used when the chip is pressed.
* @param focusedElevation the elevation used when the chip is focused
@@ -2229,28 +2207,11 @@
val disabledElevation: Dp
) {
/**
- * Represents the tonal elevation used in a chip, depending on [enabled].
- *
- * Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
- * When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
- * color in light theme and lighter color in dark theme.
- *
- * See [shadowElevation] which controls the elevation of the shadow drawn around the Chip.
- *
- * @param enabled whether the chip is enabled
- */
- internal fun tonalElevation(enabled: Boolean): Dp {
- return if (enabled) elevation else disabledElevation
- }
-
- /**
* Represents the shadow elevation used in a chip, depending on [enabled] and
* [interactionSource].
*
* Shadow elevation is used to apply a shadow around the surface to give it higher emphasis.
*
- * See [tonalElevation] which controls the elevation with a color shift to the surface.
- *
* @param enabled whether the chip is enabled
* @param interactionSource the [InteractionSource] for this chip
*/
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
index a24e72a..de12630 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
@@ -34,6 +34,8 @@
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.snapping.SnapFlingBehavior
+import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -65,6 +67,7 @@
import androidx.compose.material3.OutlinedTextFieldDefaults.defaultOutlinedTextFieldColors
import androidx.compose.material3.tokens.DatePickerModalTokens
import androidx.compose.material3.tokens.DividerTokens
+import androidx.compose.material3.tokens.ElevationTokens
import androidx.compose.material3.tokens.MotionTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -191,7 +194,7 @@
DatePickerModalTokens.HeaderHeadlineFont
),
headerMinHeight = DatePickerModalTokens.HeaderContainerHeight,
- colors = colors
+ colors = colors,
) {
SwitchableDateEntryContent(
selectedDateMillis = state.selectedDateMillis,
@@ -699,13 +702,18 @@
lazyListState: LazyListState,
decayAnimationSpec: DecayAnimationSpec<Float> = exponentialDecay()
): FlingBehavior {
- val density = LocalDensity.current
- return remember(density) {
+ return remember(decayAnimationSpec, lazyListState) {
+ val original = SnapLayoutInfoProvider(lazyListState)
+ val snapLayoutInfoProvider = object : SnapLayoutInfoProvider by original {
+ override fun calculateApproachOffset(initialVelocity: Float): Float {
+ return 0.0f
+ }
+ }
+
SnapFlingBehavior(
- lazyListState = lazyListState,
+ snapLayoutInfoProvider = snapLayoutInfoProvider,
decayAnimationSpec = decayAnimationSpec,
- snapAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow),
- density = density
+ snapAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow)
)
}
}
@@ -714,7 +722,7 @@
val YearRange: IntRange = IntRange(1900, 2100)
/** The default tonal elevation used for [DatePickerDialog]. */
- val TonalElevation: Dp = DatePickerModalTokens.ContainerElevation
+ val TonalElevation: Dp = ElevationTokens.Level0
/** The default shape for date picker dialogs. */
val shape: Shape @Composable get() = DatePickerModalTokens.ContainerShape.value
@@ -1307,7 +1315,7 @@
.semantics {
@Suppress("DEPRECATION")
isContainer = true
- }
+ }.background(colors.containerColor)
) {
DatePickerHeader(
modifier = Modifier,
@@ -1611,7 +1619,8 @@
)
ProvideContentColorTextStyle(
contentColor = titleContentColor,
- textStyle = textStyle) {
+ textStyle = textStyle
+ ) {
Box(contentAlignment = Alignment.BottomStart) {
title()
}
@@ -1657,8 +1666,6 @@
horizontalScrollAxisRange = ScrollAxisRange(value = { 0f }, maxValue = { 0f })
},
state = lazyListState,
- // TODO(b/264687693): replace with the framework's rememberSnapFlingBehavior
- // (lazyListState) when promoted to stable
flingBehavior = DatePickerDefaults.rememberSnapFlingBehavior(lazyListState)
) {
items(numberOfMonthsInRange(yearRange)) {
@@ -2006,10 +2013,7 @@
)
)
// Match the years container color to any elevated surface color that is composed under it.
- val containerColor = MaterialTheme.colorScheme.applyTonalElevation(
- backgroundColor = colors.containerColor,
- elevation = LocalAbsoluteTonalElevation.current
- )
+ val containerColor = colors.containerColor
val coroutineScope = rememberCoroutineScope()
val scrollToEarlierYearsLabel = getString(Strings.DatePickerScrollToShowEarlierYears)
val scrollToLaterYearsLabel = getString(Strings.DatePickerScrollToShowLaterYears)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt
index dcbf0fb..cd8c631 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt
@@ -125,7 +125,7 @@
),
headerMinHeight = DatePickerModalTokens.RangeSelectionHeaderContainerHeight -
HeaderHeightOffset,
- colors = colors
+ colors = colors,
) {
SwitchableDateEntryContent(
selectedStartDateMillis = state.selectedStartDateMillis,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
index 6a1a426..cf0010e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
@@ -216,7 +216,10 @@
interactionSource = interactionSource
) {
Box(
- modifier = Modifier.size(FilledIconButtonTokens.ContainerSize),
+ modifier = Modifier.size(
+ width = FilledIconButtonTokens.ContainerWidth,
+ height = FilledIconButtonTokens.ContainerHeight
+ ),
contentAlignment = Alignment.Center
) {
content()
@@ -277,7 +280,10 @@
interactionSource = interactionSource
) {
Box(
- modifier = Modifier.size(FilledTonalIconButtonTokens.ContainerSize),
+ modifier = Modifier.size(
+ width = FilledTonalIconButtonTokens.ContainerWidth,
+ height = FilledTonalIconButtonTokens.ContainerHeight
+ ),
contentAlignment = Alignment.Center
) {
content()
@@ -315,7 +321,6 @@
* interactions will still happen internally.
* @param content the content of this icon button, typically an [Icon]
*/
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FilledIconToggleButton(
checked: Boolean,
@@ -337,7 +342,10 @@
interactionSource = interactionSource
) {
Box(
- modifier = Modifier.size(FilledIconButtonTokens.ContainerSize),
+ modifier = Modifier.size(
+ width = FilledIconButtonTokens.ContainerWidth,
+ height = FilledIconButtonTokens.ContainerHeight
+ ),
contentAlignment = Alignment.Center
) {
content()
@@ -380,7 +388,6 @@
* interactions will still happen internally.
* @param content the content of this icon button, typically an [Icon]
*/
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FilledTonalIconToggleButton(
checked: Boolean,
@@ -402,7 +409,10 @@
interactionSource = interactionSource
) {
Box(
- modifier = Modifier.size(FilledTonalIconButtonTokens.ContainerSize),
+ modifier = Modifier.size(
+ width = FilledTonalIconButtonTokens.ContainerWidth,
+ height = FilledTonalIconButtonTokens.ContainerHeight
+ ),
contentAlignment = Alignment.Center
) {
content()
@@ -508,7 +518,6 @@
* interactions will still happen internally.
* @param content the content of this icon button, typically an [Icon]
*/
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OutlinedIconToggleButton(
checked: Boolean,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
index 5826971..81475be 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
@@ -37,6 +37,8 @@
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.tokens.ElevationTokens
+import androidx.compose.material3.tokens.ListTokens
import androidx.compose.material3.tokens.MenuTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -63,7 +65,7 @@
*/
object MenuDefaults {
/** The default tonal elevation for a menu. */
- val TonalElevation = MenuTokens.ContainerElevation
+ val TonalElevation = ElevationTokens.Level0
/** The default shadow elevation for a menu. */
val ShadowElevation = MenuTokens.ContainerElevation
@@ -115,15 +117,15 @@
internal val ColorScheme.defaultMenuItemColors: MenuItemColors
get() {
return defaultMenuItemColorsCached ?: MenuItemColors(
- textColor = fromToken(MenuTokens.ListItemLabelTextColor),
- leadingIconColor = fromToken(MenuTokens.ListItemLeadingIconColor),
- trailingIconColor = fromToken(MenuTokens.ListItemTrailingIconColor),
- disabledTextColor = fromToken(MenuTokens.ListItemDisabledLabelTextColor)
- .copy(alpha = MenuTokens.ListItemDisabledLabelTextOpacity),
- disabledLeadingIconColor = fromToken(MenuTokens.ListItemDisabledLeadingIconColor)
- .copy(alpha = MenuTokens.ListItemDisabledLeadingIconOpacity),
- disabledTrailingIconColor = fromToken(MenuTokens.ListItemDisabledTrailingIconColor)
- .copy(alpha = MenuTokens.ListItemDisabledTrailingIconOpacity),
+ textColor = fromToken(ListTokens.ListItemLabelTextColor),
+ leadingIconColor = fromToken(ListTokens.ListItemLeadingIconColor),
+ trailingIconColor = fromToken(ListTokens.ListItemTrailingIconColor),
+ disabledTextColor = fromToken(ListTokens.ListItemDisabledLabelTextColor)
+ .copy(alpha = ListTokens.ListItemDisabledLabelTextOpacity),
+ disabledLeadingIconColor = fromToken(ListTokens.ListItemDisabledLeadingIconColor)
+ .copy(alpha = ListTokens.ListItemDisabledLeadingIconOpacity),
+ disabledTrailingIconColor = fromToken(ListTokens.ListItemDisabledTrailingIconColor)
+ .copy(alpha = ListTokens.ListItemDisabledTrailingIconOpacity),
).also {
defaultMenuItemColorsCached = it
}
@@ -336,17 +338,18 @@
.sizeIn(
minWidth = DropdownMenuItemDefaultMinWidth,
maxWidth = DropdownMenuItemDefaultMaxWidth,
- minHeight = MenuTokens.ListItemContainerHeight
+ minHeight = MenuListItemContainerHeight
)
.padding(contentPadding),
verticalAlignment = Alignment.CenterVertically
) {
- ProvideTextStyle(MaterialTheme.typography.fromToken(MenuTokens.ListItemLabelTextFont)) {
+ // TODO(b/271818892): Align menu list item style with general list item style.
+ ProvideTextStyle(MaterialTheme.typography.labelLarge) {
if (leadingIcon != null) {
CompositionLocalProvider(
LocalContentColor provides colors.leadingIconColor(enabled),
) {
- Box(Modifier.defaultMinSize(minWidth = MenuTokens.ListItemLeadingIconSize)) {
+ Box(Modifier.defaultMinSize(minWidth = ListTokens.ListItemLeadingIconSize)) {
leadingIcon()
}
}
@@ -375,7 +378,7 @@
CompositionLocalProvider(
LocalContentColor provides colors.trailingIconColor(enabled)
) {
- Box(Modifier.defaultMinSize(minWidth = MenuTokens.ListItemTrailingIconSize)) {
+ Box(Modifier.defaultMinSize(minWidth = ListTokens.ListItemTrailingIconSize)) {
trailingIcon()
}
}
@@ -415,6 +418,7 @@
// Size defaults.
internal val MenuVerticalMargin = 48.dp
+private val MenuListItemContainerHeight = 48.dp
private val DropdownMenuItemHorizontalPadding = 12.dp
internal val DropdownMenuVerticalPadding = 8.dp
private val DropdownMenuItemDefaultMinWidth = 112.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
index 03c629f..67d1727 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
@@ -36,6 +36,7 @@
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material3.tokens.ElevationTokens
import androidx.compose.material3.tokens.NavigationBarTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -272,7 +273,7 @@
/** Defaults used in [NavigationBar]. */
object NavigationBarDefaults {
/** Default elevation for a navigation bar. */
- val Elevation: Dp = NavigationBarTokens.ContainerElevation
+ val Elevation: Dp = ElevationTokens.Level0
/** Default color for a navigation bar. */
val containerColor: Color @Composable get() = NavigationBarTokens.ContainerColor.value
@@ -318,7 +319,7 @@
unselectedTextColor: Color = Color.Unspecified,
disabledIconColor: Color = Color.Unspecified,
disabledTextColor: Color = Color.Unspecified,
- ): NavigationBarItemColors = NavigationBarItemColors(
+ ): NavigationBarItemColors = MaterialTheme.colorScheme.defaultNavigationBarItemColors.copy(
selectedIconColor = selectedIconColor,
selectedTextColor = selectedTextColor,
selectedIndicatorColor = indicatorColor,
@@ -340,8 +341,7 @@
fromToken(NavigationBarTokens.InactiveIconColor).copy(alpha = DisabledAlpha),
disabledTextColor =
fromToken(NavigationBarTokens.InactiveLabelTextColor).copy(alpha = DisabledAlpha),
-
- ).also {
+ ).also {
defaultNavigationBarItemColorsCached = it
}
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
index e9b873c..ca40239 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
@@ -42,6 +42,7 @@
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.tokens.ElevationTokens
import androidx.compose.material3.tokens.NavigationDrawerTokens
import androidx.compose.material3.tokens.ScrimTokens
import androidx.compose.runtime.Composable
@@ -527,7 +528,7 @@
fun ModalDrawerSheet(
modifier: Modifier = Modifier,
drawerShape: Shape = DrawerDefaults.shape,
- drawerContainerColor: Color = DrawerDefaults.containerColor,
+ drawerContainerColor: Color = DrawerDefaults.modalContainerColor,
drawerContentColor: Color = contentColorFor(drawerContainerColor),
drawerTonalElevation: Dp = DrawerDefaults.ModalDrawerElevation,
windowInsets: WindowInsets = DrawerDefaults.windowInsets,
@@ -564,7 +565,7 @@
fun DismissibleDrawerSheet(
modifier: Modifier = Modifier,
drawerShape: Shape = RectangleShape,
- drawerContainerColor: Color = DrawerDefaults.containerColor,
+ drawerContainerColor: Color = DrawerDefaults.standardContainerColor,
drawerContentColor: Color = contentColorFor(drawerContainerColor),
drawerTonalElevation: Dp = DrawerDefaults.DismissibleDrawerElevation,
windowInsets: WindowInsets = DrawerDefaults.windowInsets,
@@ -601,7 +602,7 @@
fun PermanentDrawerSheet(
modifier: Modifier = Modifier,
drawerShape: Shape = RectangleShape,
- drawerContainerColor: Color = DrawerDefaults.containerColor,
+ drawerContainerColor: Color = DrawerDefaults.standardContainerColor,
drawerContentColor: Color = contentColorFor(drawerContainerColor),
drawerTonalElevation: Dp = DrawerDefaults.PermanentDrawerElevation,
windowInsets: WindowInsets = DrawerDefaults.windowInsets,
@@ -626,7 +627,7 @@
windowInsets: WindowInsets,
modifier: Modifier = Modifier,
drawerShape: Shape = RectangleShape,
- drawerContainerColor: Color = DrawerDefaults.containerColor,
+ drawerContainerColor: Color = DrawerDefaults.standardContainerColor,
drawerContentColor: Color = contentColorFor(drawerContainerColor),
drawerTonalElevation: Dp = DrawerDefaults.PermanentDrawerElevation,
content: @Composable ColumnScope.() -> Unit
@@ -660,20 +661,17 @@
*/
object DrawerDefaults {
/**
- * Default Elevation for drawer container in the [ModalNavigationDrawer] as specified in the
- * Material specification.
+ * Default Elevation for drawer container in the [ModalNavigationDrawer].
*/
- val ModalDrawerElevation = NavigationDrawerTokens.ModalContainerElevation
+ val ModalDrawerElevation = ElevationTokens.Level0
/**
- * Default Elevation for drawer container in the [PermanentNavigationDrawer] as specified in the
- * Material specification.
+ * Default Elevation for drawer container in the [PermanentNavigationDrawer].
*/
val PermanentDrawerElevation = NavigationDrawerTokens.StandardContainerElevation
/**
- * Default Elevation for drawer container in the [DismissibleNavigationDrawer] as specified in
- * the Material specification.
+ * Default Elevation for drawer container in the [DismissibleNavigationDrawer].
*/
val DismissibleDrawerElevation = NavigationDrawerTokens.StandardContainerElevation
@@ -685,7 +683,23 @@
@Composable get() = ScrimTokens.ContainerColor.value.copy(ScrimTokens.ContainerOpacity)
/** Default container color for a navigation drawer */
- val containerColor: Color @Composable get() = NavigationDrawerTokens.ContainerColor.value
+ @Deprecated(
+ message = "Please use standardContainerColor or modalContainerColor instead.",
+ replaceWith = ReplaceWith("standardContainerColor"),
+ level = DeprecationLevel.WARNING,
+ )
+ val containerColor: Color @Composable get() =
+ NavigationDrawerTokens.StandardContainerColor.value
+
+ /**
+ * Default container color for a [DismissibleNavigationDrawer] and [PermanentNavigationDrawer]
+ */
+ val standardContainerColor: Color @Composable get() =
+ NavigationDrawerTokens.StandardContainerColor.value
+
+ /** Default container color for a [ModalNavigationDrawer] */
+ val modalContainerColor: Color @Composable get() =
+ NavigationDrawerTokens.ModalContainerColor.value
/** Default and maximum width of a navigation drawer */
val MaximumDrawerWidth = NavigationDrawerTokens.ContainerWidth
@@ -822,7 +836,7 @@
@Composable
fun colors(
selectedContainerColor: Color = NavigationDrawerTokens.ActiveIndicatorColor.value,
- unselectedContainerColor: Color = NavigationDrawerTokens.ContainerColor.value,
+ unselectedContainerColor: Color = Color.Transparent,
selectedIconColor: Color = NavigationDrawerTokens.ActiveIconColor.value,
unselectedIconColor: Color = NavigationDrawerTokens.InactiveIconColor.value,
selectedTextColor: Color = NavigationDrawerTokens.ActiveLabelTextColor.value,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
new file mode 100644
index 0000000..885bb0b
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
@@ -0,0 +1,504 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+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.clip
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.constrain
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.offset
+import androidx.compose.ui.util.fastFirst
+import kotlin.math.roundToInt
+
+/**
+ * Class that describes the different supported icon positions of the navigation item.
+ *
+ * TODO: Remove "internal".
+ * TODO: Add Start IconPosition.
+ */
+@JvmInline
+@ExperimentalMaterial3Api
+internal value class NavigationItemIconPosition private constructor(private val value: Int) {
+ companion object {
+ /* The icon is positioned on top of the label. */
+ val Top = NavigationItemIconPosition(0)
+ }
+
+ override fun toString() = when (this) {
+ Top -> "Top"
+ else -> "Unknown"
+ }
+}
+
+/**
+ * Represents the colors of the various elements of a navigation item.
+ *
+ * @constructor create an instance with arbitrary colors.
+ *
+ * @param selectedIconColor the color to use for the icon when the item is selected.
+ * @param selectedTextColor the color to use for the text label when the item is selected.
+ * @param selectedIndicatorColor the color to use for the indicator when the item is selected.
+ * @param unselectedIconColor the color to use for the icon when the item is unselected.
+ * @param unselectedTextColor the color to use for the text label when the item is unselected.
+ * @param disabledIconColor the color to use for the icon when the item is disabled.
+ * @param disabledTextColor the color to use for the text label when the item is disabled.
+ *
+ * TODO: Remove "internal".
+ */
+@Immutable
+internal class NavigationItemColors constructor(
+ val selectedIconColor: Color,
+ val selectedTextColor: Color,
+ val selectedIndicatorColor: Color,
+ val unselectedIconColor: Color,
+ val unselectedTextColor: Color,
+ val disabledIconColor: Color,
+ val disabledTextColor: Color,
+) {
+ /**
+ * Returns a copy of this NavigationItemColors, optionally overriding some of the values.
+ * This uses the Color.Unspecified to mean “use the value from the source”.
+ */
+ fun copy(
+ selectedIconColor: Color = this.selectedIconColor,
+ selectedTextColor: Color = this.selectedTextColor,
+ selectedIndicatorColor: Color = this.selectedIndicatorColor,
+ unselectedIconColor: Color = this.unselectedIconColor,
+ unselectedTextColor: Color = this.unselectedTextColor,
+ disabledIconColor: Color = this.disabledIconColor,
+ disabledTextColor: Color = this.disabledTextColor,
+ ) = NavigationItemColors(
+ selectedIconColor.takeOrElse { this.selectedIconColor },
+ selectedTextColor.takeOrElse { this.selectedTextColor },
+ selectedIndicatorColor.takeOrElse { this.selectedIndicatorColor },
+ unselectedIconColor.takeOrElse { this.unselectedIconColor },
+ unselectedTextColor.takeOrElse { this.unselectedTextColor },
+ disabledIconColor.takeOrElse { this.disabledIconColor },
+ disabledTextColor.takeOrElse { this.disabledTextColor },
+ )
+
+ /**
+ * Represents the icon color for this item, depending on whether it is [selected].
+ *
+ * @param selected whether the item is selected
+ * @param enabled whether the item is enabled
+ */
+ @Composable
+ fun iconColor(selected: Boolean, enabled: Boolean): Color {
+ return when {
+ !enabled -> disabledIconColor
+ selected -> selectedIconColor
+ else -> unselectedIconColor
+ }
+ }
+
+ /**
+ * Represents the text color for this item, depending on whether it is [selected].
+ *
+ * @param selected whether the item is selected
+ * @param enabled whether the item is enabled
+ */
+ @Composable
+ fun textColor(selected: Boolean, enabled: Boolean): Color {
+ return when {
+ !enabled -> disabledTextColor
+ selected -> selectedTextColor
+ else -> unselectedTextColor
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || other !is NavigationItemColors) return false
+
+ if (selectedIconColor != other.selectedIconColor) return false
+ if (unselectedIconColor != other.unselectedIconColor) return false
+ if (selectedTextColor != other.selectedTextColor) return false
+ if (unselectedTextColor != other.unselectedTextColor) return false
+ if (selectedIndicatorColor != other.selectedIndicatorColor) return false
+ if (disabledIconColor != other.disabledIconColor) return false
+ if (disabledTextColor != other.disabledTextColor) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = selectedIconColor.hashCode()
+ result = 31 * result + unselectedIconColor.hashCode()
+ result = 31 * result + selectedTextColor.hashCode()
+ result = 31 * result + unselectedTextColor.hashCode()
+ result = 31 * result + selectedIndicatorColor.hashCode()
+ result = 31 * result + disabledIconColor.hashCode()
+ result = 31 * result + disabledTextColor.hashCode()
+
+ return result
+ }
+}
+
+/**
+ * Internal function to make a navigation item to be used with the Navigation Bar item or the
+ * Navigation Rail item, depending on the passed in param values.
+ *
+ * @param selected whether this item is selected
+ * @param onClick called when this item is clicked
+ * @param icon icon for this item, typically an [Icon]
+ * @param labelTextStyle the text style of the label of this item
+ * @param indicatorShape the shape of the indicator when the item is selected
+ * @param indicatorWidth the width of the indicator when the item is selected
+ * @param indicatorHorizontalPadding the horizontal padding of the indicator
+ * @param indicatorVerticalPadding the vertical padding of the indicator
+ * @param colors [NavigationItemColors] that will be used to resolve the colors used for this item
+ * in different states
+ * @param modifier the [Modifier] to be applied to this item
+ * @param enabled controls the enabled state of this item. When `false`, this component will not
+ * respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services
+ * @param label the text label for this item
+ * @param badge optional badge to show on this item, typically a [Badge]
+ * @param iconPosition the [NavigationItemIconPosition] for this icon
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this item. You can create and pass in your own `remember`ed instance to observe
+ * [Interaction]s and customize the appearance / behavior of this item in different states
+ *
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun NavigationItem(
+ selected: Boolean,
+ onClick: () -> Unit,
+ icon: @Composable () -> Unit,
+ labelTextStyle: TextStyle,
+ indicatorShape: Shape,
+ indicatorWidth: Dp,
+ indicatorHorizontalPadding: Dp,
+ indicatorVerticalPadding: Dp,
+ colors: NavigationItemColors,
+ modifier: Modifier,
+ enabled: Boolean,
+ label: @Composable (() -> Unit)?,
+ badge: (@Composable () -> Unit)?,
+ iconPosition: NavigationItemIconPosition,
+ interactionSource: MutableInteractionSource
+) {
+ val styledIcon = @Composable {
+ val iconColor = colors.iconColor(selected = selected, enabled = enabled)
+ // If there's a label, don't have a11y services repeat the icon description.
+ val clearSemantics = label != null
+ Box(modifier = if (clearSemantics) Modifier.clearAndSetSemantics {} else Modifier) {
+ CompositionLocalProvider(LocalContentColor provides iconColor, content = icon)
+ }
+ }
+ val iconWithBadge =
+ if (badge != null) {
+ { BadgedBox(badge = { badge() }) { styledIcon() } }
+ } else {
+ styledIcon
+ }
+
+ val styledLabel: @Composable (() -> Unit)? = label?.let {
+ @Composable {
+ val textColor = colors.textColor(
+ selected = selected,
+ enabled = enabled
+ )
+ ProvideContentColorTextStyle(
+ contentColor = textColor,
+ textStyle = labelTextStyle,
+ content = label
+ )
+ }
+ }
+
+ var itemWidth by remember { mutableIntStateOf(0) }
+
+ Box(
+ modifier
+ .selectable(
+ selected = selected,
+ onClick = onClick,
+ enabled = enabled,
+ role = Role.Tab,
+ interactionSource = interactionSource,
+ indication = null,
+ )
+ .defaultMinSize(minWidth = NavigationItemMinWidth, minHeight = NavigationItemMinHeight)
+ .onSizeChanged { itemWidth = it.width },
+ contentAlignment = Alignment.Center,
+ propagateMinConstraints = true,
+ ) {
+ val animationProgress: State<Float> = animateFloatAsState(
+ targetValue = if (selected) 1f else 0f,
+ animationSpec = tween(ItemAnimationDurationMillis)
+ )
+
+ var offsetInteractionSource: MappedInteractionSource? = null
+ if (iconPosition == NavigationItemIconPosition.Top) {
+ // The entire item is selectable, but only the indicator pill shows the ripple. To
+ // achieve this, we re-map the coordinates of the item's InteractionSource into the
+ // coordinates of the indicator.
+ val deltaOffset: Offset
+ with(LocalDensity.current) {
+ deltaOffset = Offset(
+ (itemWidth - indicatorWidth.roundToPx()).toFloat() / 2,
+ IndicatorVerticalOffset.toPx()
+ )
+ }
+ offsetInteractionSource = remember(interactionSource, deltaOffset) {
+ MappedInteractionSource(interactionSource, deltaOffset)
+ }
+ }
+
+ NavigationItemLayout(
+ interactionSource = offsetInteractionSource ?: interactionSource,
+ indicatorColor = colors.selectedIndicatorColor,
+ indicatorShape = indicatorShape,
+ icon = iconWithBadge,
+ label = styledLabel,
+ animationProgress = { animationProgress.value },
+ indicatorHorizontalPadding = indicatorHorizontalPadding,
+ indicatorVerticalPadding = indicatorVerticalPadding,
+ )
+ }
+}
+
+@Composable
+private fun NavigationItemLayout(
+ interactionSource: InteractionSource,
+ indicatorColor: Color,
+ indicatorShape: Shape,
+ icon: @Composable () -> Unit,
+ label: @Composable (() -> Unit)?,
+ animationProgress: () -> Float,
+ indicatorHorizontalPadding: Dp,
+ indicatorVerticalPadding: Dp,
+) {
+ Layout({
+ // Create the indicator ripple.
+ Box(
+ Modifier
+ .layoutId(IndicatorRippleLayoutIdTag)
+ .clip(indicatorShape)
+ .indication(interactionSource, rippleOrFallbackImplementation())
+ )
+ // Create the indicator. The indicator has a width-expansion animation which interferes with
+ // the timing of the ripple, which is why they are separate composables.
+ Box(
+ Modifier
+ .layoutId(IndicatorLayoutIdTag)
+ .graphicsLayer { alpha = animationProgress() }
+ .background(
+ color = indicatorColor,
+ shape = indicatorShape,
+ )
+ )
+ Box(Modifier.layoutId(IconLayoutIdTag)) { icon() }
+ if (label != null) {
+ Box(Modifier.layoutId(LabelLayoutIdTag)) { label() }
+ }
+ }) { measurables, constraints ->
+ @Suppress("NAME_SHADOWING")
+ val animationProgress = animationProgress()
+ val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+ // When measuring icon, account for the indicator in its constraints.
+ val iconPlaceable =
+ measurables.fastFirst { it.layoutId == IconLayoutIdTag }.measure(
+ looseConstraints.offset(
+ horizontal = -indicatorHorizontalPadding.roundToPx() * 2,
+ vertical = -indicatorVerticalPadding.roundToPx() * 2)
+ )
+ // Next, when measuring the indicator and ripple, still need to obey looseConstraints.
+ val totalIndicatorWidth = iconPlaceable.width + (indicatorHorizontalPadding * 2).roundToPx()
+ val indicatorHeight =
+ iconPlaceable.height + (indicatorVerticalPadding * 2).roundToPx()
+ val animatedIndicatorWidth = (totalIndicatorWidth * animationProgress).roundToInt()
+ val indicatorRipplePlaceable =
+ measurables
+ .fastFirst { it.layoutId == IndicatorRippleLayoutIdTag }
+ .measure(
+ looseConstraints.constrain(
+ Constraints.fixed(
+ width = totalIndicatorWidth,
+ height = indicatorHeight))
+ )
+ val indicatorPlaceable =
+ measurables
+ .fastFirst { it.layoutId == IndicatorLayoutIdTag }
+ .measure(
+ looseConstraints.constrain(
+ Constraints.fixed(
+ width = animatedIndicatorWidth,
+ height = indicatorHeight
+ )
+ )
+ )
+ // Finally, when measuring label, account for the indicator and the padding between
+ // indicator and label.
+ val labelPlaceable =
+ label?.let {
+ measurables
+ .fastFirst { it.layoutId == LabelLayoutIdTag }
+ .measure(
+ looseConstraints.offset(
+ vertical = -(indicatorVerticalPadding +
+ VerticalItemIndicatorToLabelPadding).roundToPx()
+ )
+ )
+ }
+
+ if (label == null) {
+ placeIcon(iconPlaceable, indicatorRipplePlaceable, indicatorPlaceable, constraints)
+ } else {
+ placeLabelAndTopIcon(
+ labelPlaceable!!,
+ iconPlaceable,
+ indicatorRipplePlaceable,
+ indicatorPlaceable,
+ constraints,
+ indicatorVerticalPadding,
+ )
+ }
+ }
+}
+
+/**
+ * Places the provided [Placeable]s in the correct position.
+ *
+ * @param iconPlaceable icon placeable inside this item
+ * @param indicatorRipplePlaceable indicator ripple placeable inside this item
+ * @param indicatorPlaceable indicator placeable inside this item
+ * @param constraints constraints of the item
+ */
+private fun MeasureScope.placeIcon(
+ iconPlaceable: Placeable,
+ indicatorRipplePlaceable: Placeable,
+ indicatorPlaceable: Placeable,
+ constraints: Constraints
+): MeasureResult {
+ val width = constraints.constrainWidth(indicatorRipplePlaceable.width)
+ val height = constraints.constrainHeight(indicatorRipplePlaceable.height)
+
+ val indicatorX = (width - indicatorPlaceable.width) / 2
+ val indicatorY = (height - indicatorPlaceable.height) / 2
+ val iconX = (width - iconPlaceable.width) / 2
+ val iconY = (height - iconPlaceable.height) / 2
+ val rippleX = (width - indicatorRipplePlaceable.width) / 2
+ val rippleY = (height - indicatorRipplePlaceable.height) / 2
+
+ return layout(width, height) {
+ indicatorPlaceable.placeRelative(indicatorX, indicatorY)
+ iconPlaceable.placeRelative(iconX, iconY)
+ indicatorRipplePlaceable.placeRelative(rippleX, rippleY)
+ }
+}
+
+/**
+ * Places the provided [Placeable]s in the correct position.
+ *
+ * @param labelPlaceable text label placeable inside this item
+ * @param iconPlaceable icon placeable inside this item
+ * @param indicatorRipplePlaceable indicator ripple placeable inside this item
+ * @param indicatorPlaceable indicator placeable inside this item, if it exists
+ * @param constraints constraints of the item
+ * @param indicatorVerticalPadding vertical padding of the indicator
+ */
+private fun MeasureScope.placeLabelAndTopIcon(
+ labelPlaceable: Placeable,
+ iconPlaceable: Placeable,
+ indicatorRipplePlaceable: Placeable,
+ indicatorPlaceable: Placeable,
+ constraints: Constraints,
+ indicatorVerticalPadding: Dp,
+): MeasureResult {
+ val width =
+ constraints.constrainWidth(maxOf(labelPlaceable.width, indicatorRipplePlaceable.width))
+ val contentHeight = indicatorRipplePlaceable.height +
+ VerticalItemIndicatorToLabelPadding.toPx() +
+ labelPlaceable.height
+ val height = constraints.constrainHeight(contentHeight.roundToInt())
+
+ val contentVerticalPadding = (height -
+ // Vertical padding is apportioned based on icon + label, not indicator + label, so subtract
+ // padding from content height (which is based on indicator + label).
+ (contentHeight - indicatorVerticalPadding.toPx())) / 2
+ // Icon should be `contentVerticalPadding` from top.
+ val iconY = contentVerticalPadding.roundToInt()
+ val iconX = (width - iconPlaceable.width) / 2
+ val indicatorX = (width - indicatorPlaceable.width) / 2
+ val indicatorY = iconY - indicatorVerticalPadding.roundToPx()
+ val labelX = (width - labelPlaceable.width) / 2
+ // Label should be fixed padding below icon.
+ val labelY = iconY + iconPlaceable.height + indicatorVerticalPadding.toPx() +
+ VerticalItemIndicatorToLabelPadding.toPx()
+ val rippleX = (width - indicatorRipplePlaceable.width) / 2
+ val rippleY = indicatorY
+
+ return layout(width, height) {
+ indicatorPlaceable.placeRelative(indicatorX, indicatorY)
+ labelPlaceable.placeRelative(labelX, labelY.roundToInt())
+ iconPlaceable.placeRelative(iconX, iconY)
+ indicatorRipplePlaceable.placeRelative(rippleX, rippleY)
+ }
+}
+
+private const val IndicatorRippleLayoutIdTag: String = "indicatorRipple"
+private const val IndicatorLayoutIdTag: String = "indicator"
+private const val IconLayoutIdTag: String = "icon"
+private const val LabelLayoutIdTag: String = "label"
+private const val ItemAnimationDurationMillis: Int = 100
+
+private val IndicatorVerticalOffset: Dp = 12.dp
+private val VerticalItemIndicatorToLabelPadding: Dp = 4.dp
+private val NavigationItemMinWidth = NavigationRailItemWidth
+private val NavigationItemMinHeight = NavigationRailItemHeight
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt
index 940a168..d16ae0a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ProgressIndicator.kt
@@ -179,7 +179,14 @@
}
}
-private fun DrawScope.drawStopIndicator(
+/**
+ * Draws the stop indicator at the end of the track.
+ *
+ * @param stopSize size of this stop indicator, it cannot be bigger than the track's height
+ * @param color color of this stop indicator
+ * @param strokeCap stroke cap to use for the ends of this stop indicator
+ */
+fun DrawScope.drawStopIndicator(
stopSize: Dp,
color: Color,
strokeCap: StrokeCap,
@@ -334,7 +341,7 @@
}
val gapSizeFraction = adjustedGapSize / size.width.toDp()
- if (firstLineHead.value - firstLineTail.value > 0) {
+ if (firstLineHead.value - firstLineTail.value >= 0) {
if (firstLineTail.value > gapSizeFraction) {
val start = if (secondLineHead.value > gapSizeFraction) {
secondLineHead.value + gapSizeFraction
@@ -358,7 +365,7 @@
)
}
}
- if (secondLineHead.value - secondLineTail.value > 0) {
+ if (secondLineHead.value - secondLineTail.value >= 0) {
if (secondLineTail.value > gapSizeFraction) {
drawLinearIndicator(
0f, secondLineTail.value - gapSizeFraction, trackColor, strokeWidth, strokeCap
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
index 3adb86b..d9f4ad5 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
@@ -16,6 +16,9 @@
package androidx.compose.material3
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
@@ -57,6 +60,7 @@
* to the [Hidden] state if available when hiding the sheet, either programmatically or by user
* interaction.
* @param initialValue The initial value of the state.
+ * @param density The density that this state can use to convert values to and from dp.
* @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
* @param skipHiddenState Whether the hidden state should be skipped. If true, the sheet will always
* expand to the [Expanded] state and move to the [PartiallyExpanded] if available, either
@@ -64,48 +68,13 @@
*/
@Stable
@ExperimentalMaterial3Api
-class SheetState @Deprecated(
- message = "This constructor is deprecated. " +
- "Please use the constructor that provides a [Density]",
- replaceWith = ReplaceWith(
- "SheetState(" +
- "skipPartiallyExpanded, LocalDensity.current, initialValue, " +
- "confirmValueChange, skipHiddenState)"
- )
-) constructor(
+class SheetState(
internal val skipPartiallyExpanded: Boolean,
+ density: Density,
initialValue: SheetValue = Hidden,
confirmValueChange: (SheetValue) -> Boolean = { true },
internal val skipHiddenState: Boolean = false,
) {
-
- /**
- * State of a sheet composable, such as [ModalBottomSheet]
- *
- * Contains states relating to its swipe position as well as animations between state values.
- *
- * @param skipPartiallyExpanded Whether the partially expanded state, if the sheet is large
- * enough, should be skipped. If true, the sheet will always expand to the [Expanded] state and move
- * to the [Hidden] state if available when hiding the sheet, either programmatically or by user
- * interaction.
- * @param initialValue The initial value of the state.
- * @param density The density that this state can use to convert values to and from dp.
- * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
- * @param skipHiddenState Whether the hidden state should be skipped. If true, the sheet will always
- * expand to the [Expanded] state and move to the [PartiallyExpanded] if available, either
- * programmatically or by user interaction.
- */
- @ExperimentalMaterial3Api
- @Suppress("Deprecation")
- constructor(
- skipPartiallyExpanded: Boolean,
- density: Density,
- initialValue: SheetValue = Hidden,
- confirmValueChange: (SheetValue) -> Boolean = { true },
- skipHiddenState: Boolean = false,
- ) : this(skipPartiallyExpanded, initialValue, confirmValueChange, skipHiddenState) {
- this.density = density
- }
init {
if (skipPartiallyExpanded) {
require(initialValue != PartiallyExpanded) {
@@ -264,20 +233,14 @@
internal var anchoredDraggableState = AnchoredDraggableState(
initialValue = initialValue,
- animationSpec = AnchoredDraggableDefaults.AnimationSpec,
+ animationSpec = BottomSheetAnimationSpec,
confirmValueChange = confirmValueChange,
- positionalThreshold = { with(requireDensity()) { 56.dp.toPx() } },
- velocityThreshold = { with(requireDensity()) { 125.dp.toPx() } }
+ positionalThreshold = { with(density) { 56.dp.toPx() } },
+ velocityThreshold = { with(density) { 125.dp.toPx() } },
)
internal val offset: Float? get() = anchoredDraggableState.offset
- internal var density: Density? = null
- private fun requireDensity() = requireNotNull(density) {
- "SheetState did not have a density attached. Are you using SheetState with " +
- "BottomSheetScaffold or ModalBottomSheet component?"
- }
-
companion object {
/**
* The default [Saver] implementation for [SheetState].
@@ -285,32 +248,18 @@
fun Saver(
skipPartiallyExpanded: Boolean,
confirmValueChange: (SheetValue) -> Boolean,
- density: Density
+ density: Density,
+ skipHiddenState: Boolean,
) = Saver<SheetState, SheetValue>(
save = { it.currentValue },
restore = { savedValue ->
- SheetState(skipPartiallyExpanded, density, savedValue, confirmValueChange)
- }
- )
-
- /**
- * The default [Saver] implementation for [SheetState].
- */
- @Deprecated(
- message = "This function is deprecated. Please use the overload where Density is" +
- " provided.",
- replaceWith = ReplaceWith(
- "Saver(skipPartiallyExpanded, confirmValueChange, LocalDensity.current)"
- )
- )
- @Suppress("Deprecation")
- fun Saver(
- skipPartiallyExpanded: Boolean,
- confirmValueChange: (SheetValue) -> Boolean
- ) = Saver<SheetState, SheetValue>(
- save = { it.currentValue },
- restore = { savedValue ->
- SheetState(skipPartiallyExpanded, savedValue, confirmValueChange)
+ SheetState(
+ skipPartiallyExpanded,
+ density,
+ savedValue,
+ confirmValueChange,
+ skipHiddenState,
+ )
}
)
}
@@ -392,8 +341,7 @@
width: Dp = SheetBottomTokens.DockedDragHandleWidth,
height: Dp = SheetBottomTokens.DockedDragHandleHeight,
shape: Shape = MaterialTheme.shapes.extraLarge,
- color: Color = SheetBottomTokens.DockedDragHandleColor.value
- .copy(SheetBottomTokens.DockedDragHandleOpacity),
+ color: Color = SheetBottomTokens.DockedDragHandleColor.value,
) {
val dragHandleDescription = getString(Strings.BottomSheetDragHandleDescription)
Surface(
@@ -479,14 +427,14 @@
initialValue: SheetValue = Hidden,
skipHiddenState: Boolean = false,
): SheetState {
-
val density = LocalDensity.current
return rememberSaveable(
- skipPartiallyExpanded, confirmValueChange,
+ skipPartiallyExpanded, confirmValueChange, skipHiddenState,
saver = SheetState.Saver(
skipPartiallyExpanded = skipPartiallyExpanded,
confirmValueChange = confirmValueChange,
- density = density
+ density = density,
+ skipHiddenState = skipHiddenState,
)
) {
SheetState(
@@ -494,9 +442,16 @@
density,
initialValue,
confirmValueChange,
- skipHiddenState
+ skipHiddenState,
)
}
}
private val DragHandleVerticalPadding = 22.dp
+/**
+ * The default animation spec used by [SheetState].
+ */
+private val BottomSheetAnimationSpec: AnimationSpec<Float> = tween(
+ durationMillis = 300,
+ easing = FastOutSlowInEasing
+)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index 20afbb5..a671387 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -55,6 +55,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.rotate
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
@@ -269,18 +270,17 @@
) {
val state = remember(
steps,
- valueRange,
- onValueChangeFinished
+ valueRange
) {
SliderState(
value,
steps,
onValueChangeFinished,
valueRange
-
)
}
+ state.onValueChangeFinished = onValueChangeFinished
state.onValueChange = onValueChange
state.value = value
@@ -547,8 +547,7 @@
) {
val state = remember(
steps,
- valueRange,
- onValueChangeFinished
+ valueRange
) {
RangeSliderState(
value.start,
@@ -559,6 +558,7 @@
)
}
+ state.onValueChangeFinished = onValueChangeFinished
state.onValueChange = { onValueChange(it.start..it.endInclusive) }
state.activeRangeStart = value.start
state.activeRangeEnd = value.endInclusive
@@ -1110,12 +1110,12 @@
* accessibility services.
*/
@Deprecated(
- message = "Use the overload that takes `thumbTrackGapSize`, `trackInsideCornerSize` and " +
- "`drawStopIndicator`, see `LegacySliderSample` on how to restore the previous " +
+ message = "Use the overload that takes `drawStopIndicator`, `thumbTrackGapSize` and " +
+ "`trackInsideCornerSize`, see `LegacySliderSample` on how to restore the previous " +
"behavior",
replaceWith = ReplaceWith(
- "Track(sliderState, modifier, colors, enabled, thumbTrackGapSize, " +
- "trackInsideCornerSize, drawStopIndicator)"
+ "Track(sliderState, modifier, enabled, colors, drawStopIndicator, " +
+ "thumbTrackGapSize, trackInsideCornerSize)"
),
level = DeprecationLevel.HIDDEN
)
@@ -1130,8 +1130,8 @@
Track(
sliderState,
modifier,
- colors,
enabled,
+ colors,
thumbTrackGapSize = ThumbTrackGapSize,
trackInsideCornerSize = TrackInsideCornerSize
)
@@ -1142,32 +1142,32 @@
*
* @param sliderState [SliderState] which is used to obtain the current active track.
* @param modifier the [Modifier] to be applied to the track.
- * @param colors [SliderColors] that will be used to resolve the colors used for this track in
- * different states. See [SliderDefaults.colors].
* @param enabled controls the enabled state of this slider. When `false`, this component will
* not respond to user input, and it will appear visually disabled and disabled to
* accessibility services.
- * @param thumbTrackGapSize size of the gap between the thumb and the track.
- * @param trackInsideCornerSize size of the corners towards the thumb when a gap is set.
+ * @param colors [SliderColors] that will be used to resolve the colors used for this track in
+ * different states. See [SliderDefaults.colors].
* @param drawStopIndicator lambda that will be called to draw the stop indicator at the end of
* the track.
+ * @param thumbTrackGapSize size of the gap between the thumb and the track.
+ * @param trackInsideCornerSize size of the corners towards the thumb when a gap is set.
*/
@ExperimentalMaterial3Api
@Composable
fun Track(
sliderState: SliderState,
modifier: Modifier = Modifier,
- colors: SliderColors = colors(),
enabled: Boolean = true,
- thumbTrackGapSize: Dp = ThumbTrackGapSize,
- trackInsideCornerSize: Dp = TrackInsideCornerSize,
+ colors: SliderColors = colors(),
drawStopIndicator: (DrawScope.(Offset) -> Unit)? = {
drawStopIndicator(
offset = it,
color = colors.activeTrackColor,
size = TrackStopIndicatorSize
)
- }
+ },
+ thumbTrackGapSize: Dp = ThumbTrackGapSize,
+ trackInsideCornerSize: Dp = TrackInsideCornerSize
) {
val inactiveTrackColor = colors.trackColor(enabled, active = false)
val activeTrackColor = colors.trackColor(enabled, active = true)
@@ -1177,6 +1177,7 @@
modifier
.fillMaxWidth()
.height(TrackHeight)
+ .rotate(if (LocalLayoutDirection.current == LayoutDirection.Rtl) 180f else 0f)
) {
drawTrack(
sliderState.tickFractions,
@@ -1208,12 +1209,12 @@
* accessibility services.
*/
@Deprecated(
- message = "Use the overload that takes `thumbTrackGapSize`, `trackInsideCornerSize` and " +
- "`drawStopIndicator`, see `LegacyRangeSliderSample` on how to restore the " +
+ message = "Use the overload that takes `drawStopIndicator`, `thumbTrackGapSize` and " +
+ "`trackInsideCornerSize`, see `LegacyRangeSliderSample` on how to restore the " +
"previous behavior",
replaceWith = ReplaceWith(
- "Track(rangeSliderState, modifier, colors, enabled, thumbTrackGapSize, " +
- "trackInsideCornerSize, drawStopIndicator)"
+ "Track(rangeSliderState, modifier, colors, enabled, drawStopIndicator, " +
+ "thumbTrackGapSize, trackInsideCornerSize)"
),
level = DeprecationLevel.HIDDEN
)
@@ -1228,8 +1229,8 @@
Track(
rangeSliderState,
modifier,
- colors,
enabled,
+ colors,
thumbTrackGapSize = ThumbTrackGapSize,
trackInsideCornerSize = TrackInsideCornerSize
)
@@ -1240,32 +1241,32 @@
*
* @param rangeSliderState [RangeSliderState] which is used to obtain the current active track.
* @param modifier the [Modifier] to be applied to the track.
- * @param colors [SliderColors] that will be used to resolve the colors used for this track in
- * different states. See [SliderDefaults.colors].
* @param enabled controls the enabled state of this slider. When `false`, this component will
* not respond to user input, and it will appear visually disabled and disabled to
* accessibility services.
- * @param thumbTrackGapSize size of the gap between the thumbs and the track.
- * @param trackInsideCornerSize size of the corners towards the thumbs when a gap is set.
+ * @param colors [SliderColors] that will be used to resolve the colors used for this track in
+ * different states. See [SliderDefaults.colors].
* @param drawStopIndicator lambda that will be called to draw the stop indicator at the
* start/end of the track.
+ * @param thumbTrackGapSize size of the gap between the thumbs and the track.
+ * @param trackInsideCornerSize size of the corners towards the thumbs when a gap is set.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Track(
rangeSliderState: RangeSliderState,
modifier: Modifier = Modifier,
- colors: SliderColors = colors(),
enabled: Boolean = true,
- thumbTrackGapSize: Dp = ThumbTrackGapSize,
- trackInsideCornerSize: Dp = TrackInsideCornerSize,
+ colors: SliderColors = colors(),
drawStopIndicator: (DrawScope.(Offset) -> Unit)? = {
drawStopIndicator(
offset = it,
color = colors.activeTrackColor,
size = TrackStopIndicatorSize
)
- }
+ },
+ thumbTrackGapSize: Dp = ThumbTrackGapSize,
+ trackInsideCornerSize: Dp = TrackInsideCornerSize
) {
val inactiveTrackColor = colors.trackColor(enabled, active = false)
val activeTrackColor = colors.trackColor(enabled, active = true)
@@ -1275,6 +1276,7 @@
modifier
.fillMaxWidth()
.height(TrackHeight)
+ .rotate(if (LocalLayoutDirection.current == LayoutDirection.Rtl) 180f else 0f)
) {
drawTrack(
rangeSliderState.tickFractions,
@@ -1309,11 +1311,8 @@
drawStopIndicator: (DrawScope.(Offset) -> Unit)?,
isRangeSlider: Boolean
) {
- val isRtl = layoutDirection == LayoutDirection.Rtl
- val sliderLeft = Offset(0f, center.y)
- val sliderRight = Offset(size.width, center.y)
- val sliderStart = if (isRtl) sliderRight else sliderLeft
- val sliderEnd = if (isRtl) sliderLeft else sliderRight
+ val sliderStart = Offset(0f, center.y)
+ val sliderEnd = Offset(size.width, center.y)
val tickSize = TickSize.toPx()
val trackStrokeWidth = height.toPx()
@@ -1911,6 +1910,7 @@
private val ThumbTrackGapSize: Dp = 6.dp
private val TrackInsideCornerSize: Dp = 2.dp
private val TrackStopIndicatorSize: Dp = 4.dp
+private const val SliderRangeTolerance = 0.0001
private enum class SliderComponents {
THUMB,
@@ -1981,13 +1981,12 @@
* @param valueRange range of values that Slider values can take. [value] will be
* coerced to this range.
*/
-@Stable
@ExperimentalMaterial3Api
class SliderState(
value: Float = 0f,
@IntRange(from = 0)
val steps: Int = 0,
- val onValueChangeFinished: (() -> Unit)? = null,
+ var onValueChangeFinished: (() -> Unit)? = null,
val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
) : DraggableState {
@@ -2111,14 +2110,13 @@
* @param valueRange range of values that Range Slider values can take. [activeRangeStart]
* and [activeRangeEnd] will be coerced to this range.
*/
-@Stable
@ExperimentalMaterial3Api
class RangeSliderState(
activeRangeStart: Float = 0f,
activeRangeEnd: Float = 1f,
@IntRange(from = 0)
val steps: Int = 0,
- val onValueChangeFinished: (() -> Unit)? = null,
+ var onValueChangeFinished: (() -> Unit)? = null,
val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
) {
private var activeRangeStartState by mutableFloatStateOf(activeRangeStart)
@@ -2320,7 +2318,8 @@
@Stable
internal fun SliderRange(start: Float, endInclusive: Float): SliderRange {
val isUnspecified = start.isNaN() && endInclusive.isNaN()
- require(isUnspecified || start <= endInclusive) {
+
+ require(isUnspecified || start <= endInclusive + SliderRangeTolerance) {
"start($start) must be <= endInclusive($endInclusive)"
}
return SliderRange(packFloats(start, endInclusive))
@@ -2337,7 +2336,7 @@
val start = range.start
val endInclusive = range.endInclusive
val isUnspecified = start.isNaN() && endInclusive.isNaN()
- require(isUnspecified || start <= endInclusive) {
+ require(isUnspecified || start <= endInclusive + SliderRangeTolerance) {
"ClosedFloatingPointRange<Float>.start($start) must be <= " +
"ClosedFloatingPoint.endInclusive($endInclusive)"
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnapFlingBehavior.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnapFlingBehavior.kt
deleted file mode 100644
index 1cd9750..0000000
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnapFlingBehavior.kt
+++ /dev/null
@@ -1,355 +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
-
-import androidx.compose.animation.core.AnimationScope
-import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.AnimationVector
-import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.DecayAnimationSpec
-import androidx.compose.animation.core.animateDecay
-import androidx.compose.animation.core.animateTo
-import androidx.compose.animation.core.calculateTargetValue
-import androidx.compose.animation.core.copy
-import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.ScrollScope
-import androidx.compose.foundation.lazy.LazyListItemInfo
-import androidx.compose.foundation.lazy.LazyListLayoutInfo
-import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.ui.MotionDurationScale
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastSumBy
-import kotlin.math.abs
-import kotlin.math.absoluteValue
-import kotlin.math.sign
-import kotlinx.coroutines.withContext
-
-/**
- * A [FlingBehavior] that snaps to the mostly visible item in the list of items. This behavior is
- * designed to be used when there is a single visible page.
- *
- * Note: This is a temporary fling behavior that will be removed once the framework's
- * `rememberSnapFlingBehavior` function is stable.
- *
- * @param lazyListState a [LazyListState]
- * @param decayAnimationSpec a [DecayAnimationSpec] that is used for the fling's decay animation
- * @param snapAnimationSpec an [AnimationSpec] that is used for the snap animation
- * @param density the current display [Density]
- */
-// TODO(b/264687693): Replace with the framework's rememberSnapFlingBehavior ones it's stable.
-@ExperimentalMaterial3Api
-internal class SnapFlingBehavior(
- private val lazyListState: LazyListState,
- private val decayAnimationSpec: DecayAnimationSpec<Float>,
- private val snapAnimationSpec: AnimationSpec<Float>,
- private val density: Density
-) : FlingBehavior {
-
- private val visibleItemsInfo: List<LazyListItemInfo>
- get() = lazyListState.layoutInfo.visibleItemsInfo
-
- private val itemSize: Float
- get() = if (visibleItemsInfo.isNotEmpty()) {
- visibleItemsInfo.fastSumBy { it.size } / visibleItemsInfo.size.toFloat()
- } else {
- 0f
- }
-
- private val velocityThreshold = with(density) { MinFlingVelocityDp.toPx() }
- private var motionScaleDuration = object : MotionDurationScale {
- override val scaleFactor: Float
- get() = DefaultScrollMotionDurationScaleFactor
- }
-
- override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
- val (remainingOffset, remainingState) = fling(initialVelocity)
-
- // No remaining offset means we've used everything, no need to propagate velocity. Otherwise
- // we couldn't use everything (probably because we have hit the min/max bounds of the
- // containing layout) we should propagate the offset.
- return if (remainingOffset == 0f) 0f else remainingState.velocity
- }
-
- private suspend fun ScrollScope.fling(
- initialVelocity: Float
- ): AnimationResult<Float, AnimationVector1D> {
- // If snapping from scroll (short snap) or fling (long snap)
- val result = withContext(motionScaleDuration) {
- if (abs(initialVelocity) <= abs(velocityThreshold)) {
- shortSnap(initialVelocity)
- } else {
- longSnap(initialVelocity)
- }
- }
-
- return result
- }
-
- private suspend fun ScrollScope.shortSnap(
- velocity: Float
- ): AnimationResult<Float, AnimationVector1D> {
-
- val closestOffset = findClosestOffset(0f, lazyListState)
-
- val animationState = AnimationState(0f, velocity)
- return animateSnap(
- closestOffset,
- closestOffset,
- animationState,
- snapAnimationSpec
- )
- }
-
- private suspend fun ScrollScope.longSnap(
- initialVelocity: Float
- ): AnimationResult<Float, AnimationVector1D> {
-
- val offset =
- decayAnimationSpec.calculateTargetValue(0f, initialVelocity).absoluteValue
-
- val finalDecayOffset = (offset - itemSize).coerceAtLeast(0f)
- val initialOffset = if (finalDecayOffset == 0f) {
- finalDecayOffset
- } else {
- finalDecayOffset * initialVelocity.sign
- }
-
- val (remainingOffset, animationState) = runApproach(
- initialOffset,
- initialVelocity
- )
-
- return animateSnap(
- remainingOffset,
- remainingOffset,
- animationState.copy(value = 0f),
- snapAnimationSpec
- )
- }
-
- private suspend fun ScrollScope.runApproach(
- initialTargetOffset: Float,
- initialVelocity: Float
- ): AnimationResult<Float, AnimationVector1D> {
- val animationState = AnimationState(initialValue = 0f, initialVelocity = initialVelocity)
- val (_, currentAnimationState) = with(this) {
- animateDecay(initialTargetOffset, animationState, decayAnimationSpec)
- }
- val remainingOffset =
- findClosestOffset(currentAnimationState.velocity, lazyListState)
- // will snap the remainder
- return AnimationResult(remainingOffset, currentAnimationState)
- }
-
- override fun equals(other: Any?): Boolean {
- return if (other is SnapFlingBehavior) {
- other.snapAnimationSpec == this.snapAnimationSpec &&
- other.decayAnimationSpec == this.decayAnimationSpec &&
- other.lazyListState == this.lazyListState &&
- other.density == this.density
- } else {
- false
- }
- }
-
- override fun hashCode(): Int = 0
- .let { 31 * it + snapAnimationSpec.hashCode() }
- .let { 31 * it + decayAnimationSpec.hashCode() }
- .let { 31 * it + lazyListState.hashCode() }
- .let { 31 * it + density.hashCode() }
-
- private operator fun <T : Comparable<T>> ClosedFloatingPointRange<T>.component1(): T =
- this.start
-
- private operator fun <T : Comparable<T>> ClosedFloatingPointRange<T>.component2(): T =
- this.endInclusive
-
- private fun findClosestOffset(
- velocity: Float,
- lazyListState: LazyListState
- ): Float {
-
- fun Float.isValidDistance(): Boolean {
- return this != Float.POSITIVE_INFINITY && this != Float.NEGATIVE_INFINITY
- }
-
- fun calculateSnappingOffsetBounds(): ClosedFloatingPointRange<Float> {
- var lowerBoundOffset = Float.NEGATIVE_INFINITY
- var upperBoundOffset = Float.POSITIVE_INFINITY
-
- with(lazyListState.layoutInfo) {
- visibleItemsInfo.fastForEach { item ->
- val offset =
- calculateDistanceToDesiredSnapPosition(this, item)
-
- // Find item that is closest to the center
- if (offset <= 0 && offset > lowerBoundOffset) {
- lowerBoundOffset = offset
- }
-
- // Find item that is closest to center, but after it
- if (offset >= 0 && offset < upperBoundOffset) {
- upperBoundOffset = offset
- }
- }
- }
-
- return lowerBoundOffset..upperBoundOffset
- }
-
- val (lowerBound, upperBound) = calculateSnappingOffsetBounds()
-
- val finalDistance = when (sign(velocity)) {
- 0f -> {
- if (abs(upperBound) <= abs(lowerBound)) {
- upperBound
- } else {
- lowerBound
- }
- }
-
- 1f -> upperBound
- -1f -> lowerBound
- else -> 0f
- }
-
- return if (finalDistance.isValidDistance()) {
- finalDistance
- } else {
- 0f
- }
- }
-
- /**
- * Run a [DecayAnimationSpec] animation up to before [targetOffset] using [animationState]
- *
- * @param targetOffset The destination of this animation. Since this is a decay animation, we can
- * use this value to prevent the animation to run until the end.
- * @param animationState The previous [AnimationState] for continuation purposes.
- * @param decayAnimationSpec The [DecayAnimationSpec] that will drive this animation
- */
- private suspend fun ScrollScope.animateDecay(
- targetOffset: Float,
- animationState: AnimationState<Float, AnimationVector1D>,
- decayAnimationSpec: DecayAnimationSpec<Float>
- ): AnimationResult<Float, AnimationVector1D> {
- var previousValue = 0f
-
- fun AnimationScope<Float, AnimationVector1D>.consumeDelta(delta: Float) {
- val consumed = scrollBy(delta)
- if (abs(delta - consumed) > 0.5f) cancelAnimation()
- }
-
- animationState.animateDecay(
- decayAnimationSpec,
- sequentialAnimation = animationState.velocity != 0f
- ) {
- if (abs(value) >= abs(targetOffset)) {
- val finalValue = value.coerceToTarget(targetOffset)
- val finalDelta = finalValue - previousValue
- consumeDelta(finalDelta)
- cancelAnimation()
- } else {
- val delta = value - previousValue
- consumeDelta(delta)
- previousValue = value
- }
- }
- return AnimationResult(
- targetOffset - previousValue,
- animationState
- )
- }
-
- /**
- * Runs a [AnimationSpec] to snap the list into [targetOffset]. Uses [cancelOffset] to stop this
- * animation before it reaches the target.
- *
- * @param targetOffset The final target of this animation
- * @param cancelOffset If we'd like to finish the animation earlier we use this value
- * @param animationState The current animation state for continuation purposes
- * @param snapAnimationSpec The [AnimationSpec] that will drive this animation
- */
- private suspend fun ScrollScope.animateSnap(
- targetOffset: Float,
- cancelOffset: Float,
- animationState: AnimationState<Float, AnimationVector1D>,
- snapAnimationSpec: AnimationSpec<Float>
- ): AnimationResult<Float, AnimationVector1D> {
- var consumedUpToNow = 0f
- val initialVelocity = animationState.velocity
- animationState.animateTo(
- targetOffset,
- animationSpec = snapAnimationSpec,
- sequentialAnimation = (animationState.velocity != 0f)
- ) {
- val realValue = value.coerceToTarget(cancelOffset)
- val delta = realValue - consumedUpToNow
- val consumed = scrollBy(delta)
- // stop when unconsumed or when we reach the desired value
- if (abs(delta - consumed) > 0.5f || realValue != value) {
- cancelAnimation()
- }
- consumedUpToNow += consumed
- }
-
- // Always course correct velocity so they don't become too large.
- val finalVelocity = animationState.velocity.coerceToTarget(initialVelocity)
- return AnimationResult(
- targetOffset - consumedUpToNow,
- animationState.copy(velocity = finalVelocity)
- )
- }
-
- private fun Float.coerceToTarget(target: Float): Float {
- if (target == 0f) return 0f
- return if (target > 0) coerceAtMost(target) else coerceAtLeast(target)
- }
-
- private fun calculateDistanceToDesiredSnapPosition(
- layoutInfo: LazyListLayoutInfo,
- item: LazyListItemInfo
- ): Float {
- val containerSize =
- with(layoutInfo) { singleAxisViewportSize - beforeContentPadding - afterContentPadding }
-
- val desiredDistance =
- containerSize.toFloat() / 2 - item.size.toFloat() / 2 // snap to center
-
- val itemCurrentPosition = item.offset
- return itemCurrentPosition - desiredDistance
- }
-
- private val LazyListLayoutInfo.singleAxisViewportSize: Int
- get() = if (orientation == Orientation.Vertical) viewportSize.height else viewportSize.width
-
- private val DefaultScrollMotionDurationScaleFactor = 1f
-
- private val MinFlingVelocityDp = 400.dp
-}
-
-private class AnimationResult<T, V : AnimationVector>(
- val remainingOffset: T,
- val currentAnimationState: AnimationState<T, V>
-) {
- operator fun component1(): T = remainingOffset
- operator fun component2(): AnimationState<T, V> = currentAnimationState
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt
index 044ae6b..7a6d278 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt
@@ -78,7 +78,7 @@
animationSpec = AnchoredDraggableDefaults.AnimationSpec,
confirmValueChange = confirmValueChange,
positionalThreshold = positionalThreshold,
- velocityThreshold = { with(density) { DismissThreshold.toPx() } }
+ velocityThreshold = { with(density) { DismissVelocityThreshold.toPx() } }
)
internal val offset: Float get() = anchoredDraggableState.offset
@@ -111,14 +111,15 @@
/**
* The direction (if any) in which the composable has been or is being dismissed.
*
- * Use this to change the background of the [SwipeToDismissBox] if you want different actions on each
- * side.
+ * Use this to change the background of the [SwipeToDismissBox] if you want different actions
+ * on each side.
*/
val dismissDirection: SwipeToDismissBoxValue
- get() = if (offset == 0f || offset.isNaN())
- SwipeToDismissBoxValue.Settled
- else if (offset > 0f)
- SwipeToDismissBoxValue.StartToEnd else SwipeToDismissBoxValue.EndToStart
+ get() = when {
+ offset == 0f || offset.isNaN() -> SwipeToDismissBoxValue.Settled
+ offset > 0f -> SwipeToDismissBoxValue.StartToEnd
+ else -> SwipeToDismissBoxValue.EndToStart
+ }
/**
* Whether the component has been dismissed in the given [direction].
@@ -266,8 +267,8 @@
* @sample androidx.compose.material3.samples.SwipeToDismissListItems
*
* @param state The state of this component.
- * @param backgroundContent A composable that is stacked behind the [content] and is exposed when the
- * content is swiped. You can/should use the [state] to have different backgrounds on each side.
+ * @param backgroundContent A composable that is stacked behind the [content] and is exposed when
+ * the content is swiped. You can/should use the [state] to have different backgrounds on each side.
* @param modifier Optional [Modifier] for this component.
* @param enableDismissFromStartToEnd Whether SwipeToDismissBox can be dismissed from start to end.
* @param enableDismissFromEndToStart Whether SwipeToDismissBox can be dismissed from end to start.
@@ -291,7 +292,6 @@
state = state.anchoredDraggableState,
orientation = Orientation.Horizontal,
enabled = state.currentValue == SwipeToDismissBoxValue.Settled,
- reverseDirection = isRtl,
),
propagateMinConstraints = true
) {
@@ -309,10 +309,10 @@
return@draggableAnchors DraggableAnchors {
SwipeToDismissBoxValue.Settled at 0f
if (enableDismissFromStartToEnd) {
- SwipeToDismissBoxValue.StartToEnd at width
+ SwipeToDismissBoxValue.StartToEnd at (if (isRtl) -width else width)
}
if (enableDismissFromEndToStart) {
- SwipeToDismissBoxValue.EndToStart at -width
+ SwipeToDismissBoxValue.EndToStart at (if (isRtl) width else -width)
}
} to state.targetValue
}
@@ -377,4 +377,4 @@
DismissedToStart
}
-private val DismissThreshold = 125.dp
+private val DismissVelocityThreshold = 125.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index 458be3a..05d6a3a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -29,6 +29,7 @@
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.material3.tokens.ElevationTokens
import androidx.compose.material3.tokens.PlainTooltipTokens
import androidx.compose.material3.tokens.RichTooltipTokens
import androidx.compose.runtime.Composable
@@ -229,7 +230,7 @@
caretProperties: CaretProperties? = null,
shape: Shape = TooltipDefaults.richTooltipContainerShape,
colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
- tonalElevation: Dp = RichTooltipTokens.ContainerElevation,
+ tonalElevation: Dp = ElevationTokens.Level0,
shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
text: @Composable () -> Unit
)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
index 298d7a0..d8461aa 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
@@ -150,6 +150,7 @@
},
modifier = modifier,
itemSpacing = itemSpacing,
+ flingBehavior = rememberDecaySnapFlingBehavior(),
content = content
)
}
@@ -195,6 +196,12 @@
}
val snapPosition = remember(snapPositionMap) { KeylineSnapPosition(snapPositionMap) }
+ val currentItemScrollOffset =
+ (state.pagerState.currentPage * pageSize.strategy.itemMainAxisSize) +
+ (state.pagerState.currentPageOffsetFraction * pageSize.strategy.itemMainAxisSize)
+ val scrollOffset = currentItemScrollOffset -
+ (if (snapPositionMap.size > 0) snapPositionMap[state.pagerState.currentPage] else 0)
+
if (orientation == Orientation.Horizontal) {
HorizontalPager(
state = state.pagerState,
@@ -205,7 +212,7 @@
flingBehavior = flingBehavior,
modifier = modifier
) { page ->
- Box(modifier = Modifier.carouselItem(page, state, pageSize.strategy)) {
+ Box(modifier = Modifier.carouselItem(page, state, pageSize.strategy, scrollOffset)) {
carouselScope.content(page)
}
}
@@ -219,7 +226,7 @@
flingBehavior = flingBehavior,
modifier = modifier
) { page ->
- Box(modifier = Modifier.carouselItem(page, state, pageSize.strategy)) {
+ Box(modifier = Modifier.carouselItem(page, state, pageSize.strategy, scrollOffset)) {
carouselScope.content(page)
}
}
@@ -272,12 +279,12 @@
* @param state the carousel state
* @param strategy the strategy used to mask and translate items in the carousel
*/
-@Suppress("IllegalExperimentalApiUsage")
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
internal fun Modifier.carouselItem(
index: Int,
state: CarouselState,
- strategy: Strategy
+ strategy: Strategy,
+ scrollOffset: Float,
): Modifier {
val viewportSize = state.pagerState.layoutInfo.viewportSize
val orientation = state.pagerState.layoutInfo.orientation
@@ -287,13 +294,7 @@
if (mainAxisCarouselSize == 0 || !strategy.isValid()) {
return this
}
- // Scroll offset calculation using currentPage and currentPageOffsetFraction
- val firstVisibleItemScrollOffset =
- state.pagerState.currentPageOffsetFraction * strategy.itemMainAxisSize
- val scrollOffset = (state.pagerState.currentPage * strategy.itemMainAxisSize) +
- firstVisibleItemScrollOffset
val itemsCount = state.pagerState.pageCount
-
val maxScrollOffset =
itemsCount * strategy.itemMainAxisSize - mainAxisCarouselSize
val keylines = strategy.getKeylineListForScrollOffset(scrollOffset, maxScrollOffset)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt
index 65b7ac4..b6d989a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt
@@ -35,9 +35,6 @@
* @param currentItemOffsetFraction the current item offset as a fraction of the item size.
* @param itemCount the number of items this Carousel will have.
*/
-// TODO: b/321997456 - Remove lint suppression once version checks are added in lint or library
-// moves to beta
-@Suppress("IllegalExperimentalApiUsage")
@OptIn(ExperimentalFoundationApi::class)
@ExperimentalMaterial3Api
internal class CarouselState(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineSnapPosition.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineSnapPosition.kt
index a0cdcf5..7dfc4b9 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineSnapPosition.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineSnapPosition.kt
@@ -19,7 +19,15 @@
import androidx.collection.IntIntMap
import androidx.collection.emptyIntIntMap
import androidx.collection.mutableIntIntMapOf
+import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.animation.rememberSplineBasedDecay
+import androidx.compose.foundation.gestures.TargetedFlingBehavior
+import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
import androidx.compose.foundation.gestures.snapping.SnapPosition
+import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
@@ -44,22 +52,21 @@
val endStepsSize = endKeylineSteps.size + numOfFocalKeylines
for (itemIndex in 0 until itemCount) {
- map[itemIndex] = 0
+ map[itemIndex] = (defaultKeylines.firstFocal.offset -
+ defaultKeylines.firstFocal.size / 2F).roundToInt()
if (itemIndex < startStepsSize) {
var startIndex = max(0, startStepsSize - 1 - itemIndex)
startIndex = min(startKeylineSteps.size - 1, startIndex)
val startKeylines = startKeylineSteps[startIndex]
map[itemIndex] = (startKeylines.firstFocal.offset -
startKeylines.firstFocal.size / 2f).roundToInt()
- } else if (itemIndex >= itemCount - endStepsSize) {
+ }
+ if (itemCount > numOfFocalKeylines + 1 && itemIndex >= itemCount - endStepsSize) {
var endIndex = max(0, itemIndex - itemCount + endStepsSize)
endIndex = min(endKeylineSteps.size - 1, endIndex)
val endKeylines = endKeylineSteps[endIndex]
map[itemIndex] = (endKeylines.firstFocal.offset -
endKeylines.firstFocal.size / 2f).roundToInt()
- } else {
- map[itemIndex] = (defaultKeylines.firstFocal.offset -
- defaultKeylines.firstFocal.size / 2F).roundToInt()
}
}
return map
@@ -78,3 +85,20 @@
return if (snapPositions.size > 0) snapPositions[itemIndex] else 0
}
}
+
+@ExperimentalMaterial3Api
+@Composable
+internal fun rememberDecaySnapFlingBehavior(): TargetedFlingBehavior {
+ val splineDecay = rememberSplineBasedDecay<Float>()
+ val decayLayoutInfoProvider = remember {
+ object : SnapLayoutInfoProvider {
+ override fun calculateApproachOffset(initialVelocity: Float): Float {
+ return splineDecay.calculateTargetValue(0f, initialVelocity)
+ }
+
+ override fun calculateSnappingOffset(currentVelocity: Float): Float = 0f
+ }
+ }
+
+ return rememberSnapFlingBehavior(snapLayoutInfoProvider = decayLayoutInfoProvider)
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
index 7d281be..506af37 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
@@ -100,27 +100,29 @@
return if (arrangement == null) {
null
} else {
- createStartAlignedKeylineList(
+ createLeftAlignedKeylineList(
carouselMainAxisSize = carouselMainAxisSize,
- anchorSize = anchorSize,
+ leftAnchorSize = anchorSize,
+ rightAnchorSize = anchorSize,
arrangement = arrangement
)
}
}
-internal fun createStartAlignedKeylineList(
+internal fun createLeftAlignedKeylineList(
carouselMainAxisSize: Float,
- anchorSize: Float,
+ leftAnchorSize: Float,
+ rightAnchorSize: Float,
arrangement: Arrangement
): KeylineList {
return keylineListOf(carouselMainAxisSize, CarouselAlignment.Start) {
- add(anchorSize, isAnchor = true)
+ add(leftAnchorSize, isAnchor = true)
repeat(arrangement.largeCount) { add(arrangement.largeSize) }
repeat(arrangement.mediumCount) { add(arrangement.mediumSize) }
repeat(arrangement.smallCount) { add(arrangement.smallSize) }
- add(anchorSize, isAnchor = true)
+ add(rightAnchorSize, isAnchor = true)
}
}
@@ -171,12 +173,13 @@
)
val xSmallSize = min(defaultAnchorSize, itemSize)
- // Make the anchor size half the cut off item size to make the motion at the left closer
+ // Make the left anchor size half the cut off item size to make the motion at the left closer
// to the right where the cut off is.
- val anchorSize: Float = max(xSmallSize, mediumItemSize * 0.5f)
- return createStartAlignedKeylineList(
+ val leftAnchorSize: Float = max(xSmallSize, mediumItemSize * 0.5f)
+ return createLeftAlignedKeylineList(
carouselMainAxisSize = carouselMainAxisSize,
- anchorSize = anchorSize,
+ leftAnchorSize = leftAnchorSize,
+ rightAnchorSize = defaultAnchorSize,
arrangement = arrangement)
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt
index 760ee89..b5c1162 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.lerp
+import kotlin.math.max
import kotlin.math.roundToInt
/**
@@ -163,7 +164,7 @@
roundToNearestStep: Boolean = false
): KeylineList {
val startShiftOffset = startShiftDistance
- val endShiftOffset = maxScrollOffset - endShiftDistance
+ val endShiftOffset = max(0f, maxScrollOffset - endShiftDistance)
// If we're not within either shift range, return the default keylines
if (scrollOffset in startShiftOffset..endShiftOffset) {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/AssistChipTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/AssistChipTokens.kt
index 12c2566..2647474 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/AssistChipTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/AssistChipTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_126
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -23,32 +23,32 @@
internal object AssistChipTokens {
val ContainerHeight = 32.0.dp
val ContainerShape = ShapeKeyTokens.CornerSmall
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val DisabledLabelTextColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledLabelTextOpacity = 0.38f
+ val DisabledLabelTextOpacity = 0.38f
val DraggedContainerElevation = ElevationTokens.Level4
val DraggedLabelTextColor = ColorSchemeKeyTokens.OnSurface
- val ElevatedContainerColor = ColorSchemeKeyTokens.Surface
+ val ElevatedContainerColor = ColorSchemeKeyTokens.SurfaceContainerLow
val ElevatedContainerElevation = ElevationTokens.Level1
val ElevatedDisabledContainerColor = ColorSchemeKeyTokens.OnSurface
val ElevatedDisabledContainerElevation = ElevationTokens.Level0
- const val ElevatedDisabledContainerOpacity = 0.12f
+ val ElevatedDisabledContainerOpacity = 0.12f
val ElevatedFocusContainerElevation = ElevationTokens.Level1
val ElevatedHoverContainerElevation = ElevationTokens.Level2
val ElevatedPressedContainerElevation = ElevationTokens.Level1
val FlatContainerElevation = ElevationTokens.Level0
val FlatDisabledOutlineColor = ColorSchemeKeyTokens.OnSurface
- const val FlatDisabledOutlineOpacity = 0.12f
+ val FlatDisabledOutlineOpacity = 0.12f
val FlatFocusOutlineColor = ColorSchemeKeyTokens.OnSurface
val FlatOutlineColor = ColorSchemeKeyTokens.Outline
val FlatOutlineWidth = 1.0.dp
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val FocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
val HoverLabelTextColor = ColorSchemeKeyTokens.OnSurface
val LabelTextColor = ColorSchemeKeyTokens.OnSurface
val LabelTextFont = TypographyKeyTokens.LabelLarge
val PressedLabelTextColor = ColorSchemeKeyTokens.OnSurface
val DisabledIconColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledIconOpacity = 0.38f
+ val DisabledIconOpacity = 0.38f
val DraggedIconColor = ColorSchemeKeyTokens.Primary
val FocusIconColor = ColorSchemeKeyTokens.Primary
val HoverIconColor = ColorSchemeKeyTokens.Primary
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/BottomAppBarTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/BottomAppBarTokens.kt
index 16521e6..5d650cd 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/BottomAppBarTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/BottomAppBarTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -21,9 +21,8 @@
import androidx.compose.ui.unit.dp
internal object BottomAppBarTokens {
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val ContainerElevation = ElevationTokens.Level2
val ContainerHeight = 80.0.dp
val ContainerShape = ShapeKeyTokens.CornerNone
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DatePickerModalTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DatePickerModalTokens.kt
index ce3ec11..0b33cf9 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DatePickerModalTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DatePickerModalTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_161
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -21,11 +21,10 @@
import androidx.compose.ui.unit.dp
internal object DatePickerModalTokens {
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
val ContainerElevation = ElevationTokens.Level3
val ContainerHeight = 568.0.dp
val ContainerShape = ShapeKeyTokens.CornerExtraLarge
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val ContainerWidth = 360.0.dp
val DateContainerHeight = 40.0.dp
val DateContainerShape = ShapeKeyTokens.CornerFull
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DialogTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DialogTokens.kt
index fffa870..ab467ec 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DialogTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DialogTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_117
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -26,10 +26,9 @@
val ActionLabelTextColor = ColorSchemeKeyTokens.Primary
val ActionLabelTextFont = TypographyKeyTokens.LabelLarge
val ActionPressedLabelTextColor = ColorSchemeKeyTokens.Primary
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
val ContainerElevation = ElevationTokens.Level3
val ContainerShape = ShapeKeyTokens.CornerExtraLarge
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val HeadlineColor = ColorSchemeKeyTokens.OnSurface
val HeadlineFont = TypographyKeyTokens.HeadlineSmall
val SupportingTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ElevatedButtonTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ElevatedButtonTokens.kt
index bf908da..c6b02d7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ElevatedButtonTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ElevatedButtonTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -21,17 +21,17 @@
import androidx.compose.ui.unit.dp
internal object ElevatedButtonTokens {
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainerLow
val ContainerElevation = ElevationTokens.Level1
val ContainerHeight = 40.0.dp
val ContainerShape = ShapeKeyTokens.CornerFull
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
val DisabledContainerElevation = ElevationTokens.Level0
- const val DisabledContainerOpacity = 0.12f
+ val DisabledContainerOpacity = 0.12f
val DisabledLabelTextColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledLabelTextOpacity = 0.38f
+ val DisabledLabelTextOpacity = 0.38f
val FocusContainerElevation = ElevationTokens.Level1
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val FocusLabelTextColor = ColorSchemeKeyTokens.Primary
val HoverContainerElevation = ElevationTokens.Level2
val HoverLabelTextColor = ColorSchemeKeyTokens.Primary
@@ -40,7 +40,7 @@
val PressedContainerElevation = ElevationTokens.Level1
val PressedLabelTextColor = ColorSchemeKeyTokens.Primary
val DisabledIconColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledIconOpacity = 0.38f
+ val DisabledIconOpacity = 0.38f
val FocusIconColor = ColorSchemeKeyTokens.Primary
val HoverIconColor = ColorSchemeKeyTokens.Primary
val IconColor = ColorSchemeKeyTokens.Primary
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ElevatedCardTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ElevatedCardTokens.kt
index 8db812f..935d50a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ElevatedCardTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ElevatedCardTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -21,15 +21,15 @@
import androidx.compose.ui.unit.dp
internal object ElevatedCardTokens {
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainerLow
val ContainerElevation = ElevationTokens.Level1
val ContainerShape = ShapeKeyTokens.CornerMedium
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val DisabledContainerColor = ColorSchemeKeyTokens.Surface
val DisabledContainerElevation = ElevationTokens.Level1
- const val DisabledContainerOpacity = 0.38f
+ val DisabledContainerOpacity = 0.38f
val DraggedContainerElevation = ElevationTokens.Level4
val FocusContainerElevation = ElevationTokens.Level1
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val HoverContainerElevation = ElevationTokens.Level2
val IconColor = ColorSchemeKeyTokens.Primary
val IconSize = 24.0.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledAutocompleteTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledAutocompleteTokens.kt
index 7ea8782..7ef0566 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledAutocompleteTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledAutocompleteTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -21,37 +21,29 @@
import androidx.compose.ui.unit.dp
internal object FilledAutocompleteTokens {
- val MenuContainerColor = ColorSchemeKeyTokens.Surface
+ val MenuContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val MenuContainerElevation = ElevationTokens.Level2
val MenuContainerShape = ShapeKeyTokens.CornerExtraSmall
- val MenuContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
- val MenuDividerColor = ColorSchemeKeyTokens.SurfaceVariant
- val MenuDividerHeight = 1.0.dp
- val MenuListItemContainerHeight = 48.0.dp
- val MenuListItemLabelTextColor = ColorSchemeKeyTokens.OnSurface
- val MenuListItemLabelTextFont = TypographyKeyTokens.LabelLarge
- val MenuListItemSelectedContainerColor = ColorSchemeKeyTokens.SurfaceVariant
val TextFieldActiveIndicatorColor = ColorSchemeKeyTokens.OnSurfaceVariant
val TextFieldActiveIndicatorHeight = 1.0.dp
val TextFieldCaretColor = ColorSchemeKeyTokens.Primary
- val TextFieldContainerColor = ColorSchemeKeyTokens.SurfaceVariant
- val TextFieldContainerHeight = 56.0.dp
+ val TextFieldContainerColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val TextFieldContainerShape = ShapeKeyTokens.CornerExtraSmallTop
val TextFieldDisabledActiveIndicatorColor = ColorSchemeKeyTokens.OnSurface
val TextFieldDisabledActiveIndicatorHeight = 1.0.dp
- const val TextFieldDisabledActiveIndicatorOpacity = 0.38f
+ val TextFieldDisabledActiveIndicatorOpacity = 0.38f
val TextFieldDisabledContainerColor = ColorSchemeKeyTokens.OnSurface
- const val TextFieldDisabledContainerOpacity = 0.04f
+ val TextFieldDisabledContainerOpacity = 0.04f
val FieldDisabledInputTextColor = ColorSchemeKeyTokens.OnSurface
- const val FieldDisabledInputTextOpacity = 0.38f
+ val FieldDisabledInputTextOpacity = 0.38f
val FieldDisabledLabelTextColor = ColorSchemeKeyTokens.OnSurface
- const val FieldDisabledLabelTextOpacity = 0.38f
+ val FieldDisabledLabelTextOpacity = 0.38f
val TextFieldDisabledLeadingIconColor = ColorSchemeKeyTokens.OnSurface
- const val TextFieldDisabledLeadingIconOpacity = 0.38f
+ val TextFieldDisabledLeadingIconOpacity = 0.38f
val FieldDisabledSupportingTextColor = ColorSchemeKeyTokens.OnSurface
- const val FieldDisabledSupportingTextOpacity = 0.38f
+ val FieldDisabledSupportingTextOpacity = 0.38f
val TextFieldDisabledTrailingIconColor = ColorSchemeKeyTokens.OnSurface
- const val TextFieldDisabledTrailingIconOpacity = 0.38f
+ val TextFieldDisabledTrailingIconOpacity = 0.38f
val TextFieldErrorActiveIndicatorColor = ColorSchemeKeyTokens.Error
val TextFieldErrorFocusActiveIndicatorColor = ColorSchemeKeyTokens.Error
val TextFieldErrorFocusCaretColor = ColorSchemeKeyTokens.Error
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledCardTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledCardTokens.kt
index fcada51..acca512 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledCardTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledCardTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -21,14 +21,15 @@
import androidx.compose.ui.unit.dp
internal object FilledCardTokens {
- val ContainerColor = ColorSchemeKeyTokens.SurfaceVariant
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val ContainerElevation = ElevationTokens.Level0
val ContainerShape = ShapeKeyTokens.CornerMedium
val DisabledContainerColor = ColorSchemeKeyTokens.SurfaceVariant
val DisabledContainerElevation = ElevationTokens.Level0
- const val DisabledContainerOpacity = 0.38f
+ val DisabledContainerOpacity = 0.38f
val DraggedContainerElevation = ElevationTokens.Level3
val FocusContainerElevation = ElevationTokens.Level0
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val HoverContainerElevation = ElevationTokens.Level1
val IconColor = ColorSchemeKeyTokens.Primary
val IconSize = 24.0.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledIconButtonTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledIconButtonTokens.kt
index 6b5effe..286f5b2 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledIconButtonTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledIconButtonTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -22,13 +22,15 @@
internal object FilledIconButtonTokens {
val ContainerColor = ColorSchemeKeyTokens.Primary
+ val ContainerHeight = 40.0.dp
val ContainerShape = ShapeKeyTokens.CornerFull
- val ContainerSize = 40.0.dp
+ val ContainerWidth = 40.0.dp
val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledContainerOpacity = 0.12f
+ val DisabledContainerOpacity = 0.12f
val DisabledColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledOpacity = 0.38f
+ val DisabledOpacity = 0.38f
val FocusColor = ColorSchemeKeyTokens.OnPrimary
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val HoverColor = ColorSchemeKeyTokens.OnPrimary
val Color = ColorSchemeKeyTokens.OnPrimary
val Size = 24.0.dp
@@ -42,5 +44,5 @@
val ToggleUnselectedHoverColor = ColorSchemeKeyTokens.Primary
val ToggleUnselectedColor = ColorSchemeKeyTokens.Primary
val ToggleUnselectedPressedColor = ColorSchemeKeyTokens.Primary
- val UnselectedContainerColor = ColorSchemeKeyTokens.SurfaceVariant
+ val UnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainerHighest
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledTextFieldTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledTextFieldTokens.kt
index 4d8c142..431029b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledTextFieldTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledTextFieldTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -24,24 +24,23 @@
val ActiveIndicatorColor = ColorSchemeKeyTokens.OnSurfaceVariant
val ActiveIndicatorHeight = 1.0.dp
val CaretColor = ColorSchemeKeyTokens.Primary
- val ContainerColor = ColorSchemeKeyTokens.SurfaceVariant
- val ContainerHeight = 56.0.dp
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val ContainerShape = ShapeKeyTokens.CornerExtraSmallTop
val DisabledActiveIndicatorColor = ColorSchemeKeyTokens.OnSurface
val DisabledActiveIndicatorHeight = 1.0.dp
- const val DisabledActiveIndicatorOpacity = 0.38f
+ val DisabledActiveIndicatorOpacity = 0.38f
val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledContainerOpacity = 0.04f
+ val DisabledContainerOpacity = 0.04f
val DisabledInputColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledInputOpacity = 0.38f
+ val DisabledInputOpacity = 0.38f
val DisabledLabelColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledLabelOpacity = 0.38f
+ val DisabledLabelOpacity = 0.38f
val DisabledLeadingIconColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledLeadingIconOpacity = 0.38f
+ val DisabledLeadingIconOpacity = 0.38f
val DisabledSupportingColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledSupportingOpacity = 0.38f
+ val DisabledSupportingOpacity = 0.38f
val DisabledTrailingIconColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledTrailingIconOpacity = 0.38f
+ val DisabledTrailingIconOpacity = 0.38f
val ErrorActiveIndicatorColor = ColorSchemeKeyTokens.Error
val ErrorFocusActiveIndicatorColor = ColorSchemeKeyTokens.Error
val ErrorFocusCaretColor = ColorSchemeKeyTokens.Error
@@ -83,7 +82,7 @@
val LabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
val LabelFont = TypographyKeyTokens.BodyLarge
val LeadingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val LeadingIconSize = 20.0.dp
+ val LeadingIconSize = 24.0.dp
val SupportingColor = ColorSchemeKeyTokens.OnSurfaceVariant
val SupportingFont = TypographyKeyTokens.BodySmall
val TrailingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledTonalIconButtonTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledTonalIconButtonTokens.kt
index 7a1da2d..ab83fec 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledTonalIconButtonTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilledTonalIconButtonTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -22,13 +22,15 @@
internal object FilledTonalIconButtonTokens {
val ContainerColor = ColorSchemeKeyTokens.SecondaryContainer
+ val ContainerHeight = 40.0.dp
val ContainerShape = ShapeKeyTokens.CornerFull
- val ContainerSize = 40.0.dp
+ val ContainerWidth = 40.0.dp
val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledContainerOpacity = 0.12f
+ val DisabledContainerOpacity = 0.12f
val DisabledColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledOpacity = 0.38f
+ val DisabledOpacity = 0.38f
val FocusColor = ColorSchemeKeyTokens.OnSecondaryContainer
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val HoverColor = ColorSchemeKeyTokens.OnSecondaryContainer
val Color = ColorSchemeKeyTokens.OnSecondaryContainer
val Size = 24.0.dp
@@ -42,5 +44,5 @@
val ToggleUnselectedHoverColor = ColorSchemeKeyTokens.OnSurfaceVariant
val ToggleUnselectedColor = ColorSchemeKeyTokens.OnSurfaceVariant
val ToggleUnselectedPressedColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val UnselectedContainerColor = ColorSchemeKeyTokens.SurfaceVariant
+ val UnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainerHighest
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilterChipTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilterChipTokens.kt
index 1798b56..39b50cf 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilterChipTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/FilterChipTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_126
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -23,24 +23,23 @@
internal object FilterChipTokens {
val ContainerHeight = 32.0.dp
val ContainerShape = ShapeKeyTokens.CornerSmall
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val DisabledLabelTextColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledLabelTextOpacity = 0.38f
+ val DisabledLabelTextOpacity = 0.38f
val DraggedContainerElevation = ElevationTokens.Level4
val ElevatedContainerElevation = ElevationTokens.Level1
val ElevatedDisabledContainerColor = ColorSchemeKeyTokens.OnSurface
val ElevatedDisabledContainerElevation = ElevationTokens.Level0
- const val ElevatedDisabledContainerOpacity = 0.12f
+ val ElevatedDisabledContainerOpacity = 0.12f
val ElevatedFocusContainerElevation = ElevationTokens.Level1
val ElevatedHoverContainerElevation = ElevationTokens.Level2
val ElevatedPressedContainerElevation = ElevationTokens.Level1
val ElevatedSelectedContainerColor = ColorSchemeKeyTokens.SecondaryContainer
- val ElevatedUnselectedContainerColor = ColorSchemeKeyTokens.Surface
+ val ElevatedUnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainerLow
val FlatContainerElevation = ElevationTokens.Level0
val FlatDisabledSelectedContainerColor = ColorSchemeKeyTokens.OnSurface
- const val FlatDisabledSelectedContainerOpacity = 0.12f
+ val FlatDisabledSelectedContainerOpacity = 0.12f
val FlatDisabledUnselectedOutlineColor = ColorSchemeKeyTokens.OnSurface
- const val FlatDisabledUnselectedOutlineOpacity = 0.12f
+ val FlatDisabledUnselectedOutlineOpacity = 0.12f
val FlatSelectedContainerColor = ColorSchemeKeyTokens.SecondaryContainer
val FlatSelectedFocusContainerElevation = ElevationTokens.Level0
val FlatSelectedHoverContainerElevation = ElevationTokens.Level1
@@ -52,6 +51,7 @@
val FlatUnselectedOutlineColor = ColorSchemeKeyTokens.Outline
val FlatUnselectedOutlineWidth = 1.0.dp
val FlatUnselectedPressedContainerElevation = ElevationTokens.Level0
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val LabelTextFont = TypographyKeyTokens.LabelLarge
val SelectedDraggedLabelTextColor = ColorSchemeKeyTokens.OnSecondaryContainer
val SelectedFocusLabelTextColor = ColorSchemeKeyTokens.OnSecondaryContainer
@@ -65,8 +65,7 @@
val UnselectedPressedLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val IconSize = 18.0.dp
val DisabledLeadingIconColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledLeadingIconOpacity = 0.38f
- val LeadingIconUnselectedColor = ColorSchemeKeyTokens.Primary
+ val DisabledLeadingIconOpacity = 0.38f
val SelectedDraggedLeadingIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
val SelectedFocusLeadingIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
val SelectedHoverLeadingIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
@@ -75,17 +74,18 @@
val UnselectedDraggedLeadingIconColor = ColorSchemeKeyTokens.Primary
val UnselectedFocusLeadingIconColor = ColorSchemeKeyTokens.Primary
val UnselectedHoverLeadingIconColor = ColorSchemeKeyTokens.Primary
+ val UnselectedLeadingIconColor = ColorSchemeKeyTokens.Primary
val UnselectedPressedLeadingIconColor = ColorSchemeKeyTokens.Primary
val DisabledTrailingIconColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledTrailingIconOpacity = 0.38f
+ val DisabledTrailingIconOpacity = 0.38f
val SelectedDraggedTrailingIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
val SelectedFocusTrailingIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
val SelectedHoverTrailingIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
val SelectedPressedTrailingIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
val SelectedTrailingIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
- val TrailingIconUnselectedColor = ColorSchemeKeyTokens.OnSurfaceVariant
val UnselectedDraggedTrailingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
val UnselectedFocusTrailingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
val UnselectedHoverTrailingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
val UnselectedPressedTrailingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
+ val UnselectedTrailingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ListTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ListTokens.kt
index 95d349d..759f35a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ListTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ListTokens.kt
@@ -13,18 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_159
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
import androidx.compose.ui.unit.dp
+
internal object ListTokens {
+ val DividerLeadingSpace = 16.0.dp
+ val DividerTrailingSpace = 16.0.dp
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val ListItemContainerColor = ColorSchemeKeyTokens.Surface
val ListItemContainerElevation = ElevationTokens.Level0
val ListItemContainerShape = ShapeKeyTokens.CornerNone
val ListItemDisabledLabelTextColor = ColorSchemeKeyTokens.OnSurface
- val ListItemDisabledLabelTextOpacity = 0.3f
+ val ListItemDisabledLabelTextOpacity = 0.38f
val ListItemDisabledLeadingIconColor = ColorSchemeKeyTokens.OnSurface
val ListItemDisabledLeadingIconOpacity = 0.38f
val ListItemDisabledTrailingIconColor = ColorSchemeKeyTokens.OnSurface
@@ -48,10 +52,11 @@
val ListItemLeadingAvatarShape = ShapeKeyTokens.CornerFull
val ListItemLeadingAvatarSize = 40.0.dp
val ListItemLeadingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val ListItemLeadingIconSize = 18.0.dp
+ val ListItemLeadingIconSize = 24.0.dp
val ListItemLeadingImageHeight = 56.0.dp
val ListItemLeadingImageShape = ShapeKeyTokens.CornerNone
val ListItemLeadingImageWidth = 56.0.dp
+ val ListItemLeadingSpace = 16.0.dp
val ListItemLeadingVideoShape = ShapeKeyTokens.CornerNone
val ListItemLeadingVideoWidth = 100.0.dp
val ListItemOneLineContainerHeight = 56.0.dp
@@ -67,6 +72,7 @@
val ListItemThreeLineContainerHeight = 88.0.dp
val ListItemTrailingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
val ListItemTrailingIconSize = 24.0.dp
+ val ListItemTrailingSpace = 16.0.dp
val ListItemTrailingSupportingTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val ListItemTrailingSupportingTextFont = TypographyKeyTokens.LabelSmall
val ListItemTwoLineContainerHeight = 72.0.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MenuTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MenuTokens.kt
index fb1718e..512d254 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MenuTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MenuTokens.kt
@@ -13,39 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_117
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
-import androidx.compose.ui.unit.dp
-
internal object MenuTokens {
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val ContainerElevation = ElevationTokens.Level2
val ContainerShape = ShapeKeyTokens.CornerExtraSmall
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
- val ListItemContainerHeight = 48.0.dp
- val ListItemDisabledLabelTextColor = ColorSchemeKeyTokens.OnSurface
- const val ListItemDisabledLabelTextOpacity = 0.38f
- val ListItemFocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
- val ListItemHoverLabelTextColor = ColorSchemeKeyTokens.OnSurface
- val ListItemLabelTextColor = ColorSchemeKeyTokens.OnSurface
- val ListItemLabelTextFont = TypographyKeyTokens.LabelLarge
- val ListItemPressedLabelTextColor = ColorSchemeKeyTokens.OnSurface
- val ListItemSelectedContainerColor = ColorSchemeKeyTokens.SurfaceVariant
- val ListItemDisabledLeadingIconColor = ColorSchemeKeyTokens.OnSurface
- const val ListItemDisabledLeadingIconOpacity = 0.38f
- val ListItemLeadingFocusIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val ListItemLeadingHoverIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val ListItemLeadingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val ListItemLeadingIconSize = 24.0.dp
- val ListItemLeadingPressedIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val ListItemDisabledTrailingIconColor = ColorSchemeKeyTokens.OnSurface
- const val ListItemDisabledTrailingIconOpacity = 0.38f
- val ListItemTrailingFocusIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val ListItemTrailingHoverIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val ListItemTrailingPressedIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val ListItemTrailingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val ListItemTrailingIconSize = 24.0.dp
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
+ val ListItemSelectedContainerColor = ColorSchemeKeyTokens.SecondaryContainer
+ val ListItemSelectedLabelTextColor = ColorSchemeKeyTokens.OnSecondaryContainer
+ val ListItemSelectedLeadingTrailingIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
+ val MenuListItemLeadingIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt
index 8a95164..be8d590 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationBarTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -33,11 +33,11 @@
val ActiveLabelTextColor = ColorSchemeKeyTokens.OnSurface
val ActivePressedIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
val ActivePressedLabelTextColor = ColorSchemeKeyTokens.OnSurface
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val ContainerElevation = ElevationTokens.Level2
val ContainerHeight = 80.0.dp
val ContainerShape = ShapeKeyTokens.CornerNone
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val IconSize = 24.0.dp
val InactiveFocusIconColor = ColorSchemeKeyTokens.OnSurface
val InactiveFocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationDrawerTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationDrawerTokens.kt
index 8aa688b..ce3cd36 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationDrawerTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/NavigationDrawerTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_117
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -34,11 +34,10 @@
val ActivePressedIconColor = ColorSchemeKeyTokens.OnSecondaryContainer
val ActivePressedLabelTextColor = ColorSchemeKeyTokens.OnSecondaryContainer
val BottomContainerShape = ShapeKeyTokens.CornerLargeTop
- val ContainerColor = ColorSchemeKeyTokens.Surface
- const val ContainerHeightPercent = 100.0f
+ val ContainerHeightPercent = 100.0f
val ContainerShape = ShapeKeyTokens.CornerLargeEnd
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val ContainerWidth = 360.0.dp
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val HeadlineColor = ColorSchemeKeyTokens.OnSurfaceVariant
val HeadlineFont = TypographyKeyTokens.TitleSmall
val IconSize = 24.0.dp
@@ -53,6 +52,8 @@
val LabelTextFont = TypographyKeyTokens.LabelLarge
val LargeBadgeLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
val LargeBadgeLabelFont = TypographyKeyTokens.LabelLarge
+ val ModalContainerColor = ColorSchemeKeyTokens.SurfaceContainerLow
val ModalContainerElevation = ElevationTokens.Level1
+ val StandardContainerColor = ColorSchemeKeyTokens.Surface
val StandardContainerElevation = ElevationTokens.Level0
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/OutlinedAutocompleteTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/OutlinedAutocompleteTokens.kt
index 3f3c6d3..e6b0572 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/OutlinedAutocompleteTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/OutlinedAutocompleteTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -21,33 +21,25 @@
import androidx.compose.ui.unit.dp
internal object OutlinedAutocompleteTokens {
- val MenuContainerColor = ColorSchemeKeyTokens.Surface
+ val MenuContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val MenuContainerElevation = ElevationTokens.Level2
val MenuContainerShape = ShapeKeyTokens.CornerExtraSmall
- val MenuContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
- val MenuDividerColor = ColorSchemeKeyTokens.SurfaceVariant
- val MenuDividerHeight = 1.0.dp
- val MenuListItemContainerHeight = 48.0.dp
- val MenuListItemLabelTextColor = ColorSchemeKeyTokens.OnSurface
- val MenuListItemLabelTextFont = TypographyKeyTokens.LabelLarge
- val MenuListItemSelectedContainerColor = ColorSchemeKeyTokens.SurfaceVariant
val TextFieldCaretColor = ColorSchemeKeyTokens.Primary
- val TextFieldContainerColor = ColorSchemeKeyTokens.SurfaceVariant
- val TextFieldContainerHeight = 56.0.dp
+ val TextFieldContainerColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val TextFieldContainerShape = ShapeKeyTokens.CornerExtraSmall
val FieldDisabledInputTextColor = ColorSchemeKeyTokens.OnSurface
- const val FieldDisabledInputTextOpacity = 0.38f
+ val FieldDisabledInputTextOpacity = 0.38f
val FieldDisabledLabelTextColor = ColorSchemeKeyTokens.OnSurface
- const val FieldDisabledLabelTextOpacity = 0.38f
+ val FieldDisabledLabelTextOpacity = 0.38f
val TextFieldDisabledLeadingIconColor = ColorSchemeKeyTokens.OnSurface
- const val TextFieldDisabledLeadingIconOpacity = 0.38f
+ val TextFieldDisabledLeadingIconOpacity = 0.38f
val TextFieldDisabledOutlineColor = ColorSchemeKeyTokens.OnSurface
- const val TextFieldDisabledOutlineOpacity = 0.12f
+ val TextFieldDisabledOutlineOpacity = 0.12f
val TextFieldDisabledOutlineWidth = 1.0.dp
val FieldDisabledSupportingTextColor = ColorSchemeKeyTokens.OnSurface
- const val FieldDisabledSupportingTextOpacity = 0.38f
+ val FieldDisabledSupportingTextOpacity = 0.38f
val TextFieldDisabledTrailingIconColor = ColorSchemeKeyTokens.OnSurface
- const val TextFieldDisabledTrailingIconOpacity = 0.38f
+ val TextFieldDisabledTrailingIconOpacity = 0.38f
val TextFieldErrorFocusCaretColor = ColorSchemeKeyTokens.Error
val FieldErrorFocusInputTextColor = ColorSchemeKeyTokens.OnSurface
val FieldErrorFocusLabelTextColor = ColorSchemeKeyTokens.Error
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/RichTooltipTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/RichTooltipTokens.kt
index 0363645..b327485 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/RichTooltipTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/RichTooltipTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_162
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -24,10 +24,9 @@
val ActionLabelTextColor = ColorSchemeKeyTokens.Primary
val ActionLabelTextFont = TypographyKeyTokens.LabelLarge
val ActionPressedLabelTextColor = ColorSchemeKeyTokens.Primary
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val ContainerElevation = ElevationTokens.Level2
val ContainerShape = ShapeKeyTokens.CornerMedium
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val SubheadColor = ColorSchemeKeyTokens.OnSurfaceVariant
val SubheadFont = TypographyKeyTokens.TitleSmall
val SupportingTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SearchBarTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SearchBarTokens.kt
index fc83e22..04a13a3 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SearchBarTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SearchBarTokens.kt
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-// VERSION: v0_126
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -24,11 +23,11 @@
internal object SearchBarTokens {
val AvatarShape = ShapeKeyTokens.CornerFull
val AvatarSize = 30.0.dp
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
val ContainerElevation = ElevationTokens.Level3
val ContainerHeight = 56.0.dp
val ContainerShape = ShapeKeyTokens.CornerFull
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val HoverSupportingTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val InputTextColor = ColorSchemeKeyTokens.OnSurface
val InputTextFont = TypographyKeyTokens.BodyLarge
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SearchViewTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SearchViewTokens.kt
index 8553159..4ad7468 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SearchViewTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SearchViewTokens.kt
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-// VERSION: v0_126
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -22,9 +21,8 @@
import androidx.compose.ui.unit.dp
internal object SearchViewTokens {
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
val ContainerElevation = ElevationTokens.Level3
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val DividerColor = ColorSchemeKeyTokens.Outline
val DockedContainerShape = ShapeKeyTokens.CornerExtraLarge
val DockedHeaderContainerHeight = 56.0.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SheetBottomTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SheetBottomTokens.kt
index 4625132..e39cfbf 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SheetBottomTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SheetBottomTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_126
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -21,14 +21,13 @@
import androidx.compose.ui.unit.dp
internal object SheetBottomTokens {
- val DockedContainerColor = ColorSchemeKeyTokens.Surface
+ val DockedContainerColor = ColorSchemeKeyTokens.SurfaceContainerLow
val DockedContainerShape = ShapeKeyTokens.CornerExtraLargeTop
- val DockedContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val DockedDragHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
val DockedDragHandleHeight = 4.0.dp
- const val DockedDragHandleOpacity = 0.4f
val DockedDragHandleWidth = 32.0.dp
val DockedMinimizedContainerShape = ShapeKeyTokens.CornerNone
val DockedModalContainerElevation = ElevationTokens.Level1
val DockedStandardContainerElevation = ElevationTokens.Level1
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SuggestionChipTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SuggestionChipTokens.kt
index dac1658..b6723fa 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SuggestionChipTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SuggestionChipTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_117
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -23,36 +23,36 @@
internal object SuggestionChipTokens {
val ContainerHeight = 32.0.dp
val ContainerShape = ShapeKeyTokens.CornerSmall
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val DisabledLabelTextColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledLabelTextOpacity = 0.38f
+ val DisabledLabelTextOpacity = 0.38f
val DraggedContainerElevation = ElevationTokens.Level4
val DraggedLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val ElevatedContainerColor = ColorSchemeKeyTokens.Surface
+ val ElevatedContainerColor = ColorSchemeKeyTokens.SurfaceContainerLow
val ElevatedContainerElevation = ElevationTokens.Level1
val ElevatedDisabledContainerColor = ColorSchemeKeyTokens.OnSurface
val ElevatedDisabledContainerElevation = ElevationTokens.Level0
- const val ElevatedDisabledContainerOpacity = 0.12f
+ val ElevatedDisabledContainerOpacity = 0.12f
val ElevatedFocusContainerElevation = ElevationTokens.Level1
val ElevatedHoverContainerElevation = ElevationTokens.Level2
val ElevatedPressedContainerElevation = ElevationTokens.Level1
val FlatContainerElevation = ElevationTokens.Level0
val FlatDisabledOutlineColor = ColorSchemeKeyTokens.OnSurface
- const val FlatDisabledOutlineOpacity = 0.12f
+ val FlatDisabledOutlineOpacity = 0.12f
val FlatFocusOutlineColor = ColorSchemeKeyTokens.OnSurfaceVariant
val FlatOutlineColor = ColorSchemeKeyTokens.Outline
val FlatOutlineWidth = 1.0.dp
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val FocusLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val HoverLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val LabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val LabelTextFont = TypographyKeyTokens.LabelLarge
val PressedLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val DisabledLeadingIconColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledLeadingIconOpacity = 0.38f
- val DraggedLeadingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val FocusLeadingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val HoverLeadingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val LeadingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
+ val DisabledLeadingIconOpacity = 0.38f
+ val DraggedLeadingIconColor = ColorSchemeKeyTokens.Primary
+ val FocusLeadingIconColor = ColorSchemeKeyTokens.Primary
+ val HoverLeadingIconColor = ColorSchemeKeyTokens.Primary
+ val LeadingIconColor = ColorSchemeKeyTokens.Primary
val LeadingIconSize = 18.0.dp
- val PressedLeadingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
+ val PressedLeadingIconColor = ColorSchemeKeyTokens.Primary
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SwitchTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SwitchTokens.kt
index a6c2e72..e73c333 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SwitchTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SwitchTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -22,17 +22,18 @@
internal object SwitchTokens {
val DisabledSelectedHandleColor = ColorSchemeKeyTokens.Surface
- const val DisabledSelectedHandleOpacity = 1.0f
+ val DisabledSelectedHandleOpacity = 1.0f
val DisabledSelectedIconColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledSelectedIconOpacity = 0.38f
+ val DisabledSelectedIconOpacity = 0.38f
val DisabledSelectedTrackColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledTrackOpacity = 0.12f
+ val DisabledTrackOpacity = 0.12f
val DisabledUnselectedHandleColor = ColorSchemeKeyTokens.OnSurface
- const val DisabledUnselectedHandleOpacity = 0.38f
- val DisabledUnselectedIconColor = ColorSchemeKeyTokens.SurfaceVariant
- const val DisabledUnselectedIconOpacity = 0.38f
- val DisabledUnselectedTrackColor = ColorSchemeKeyTokens.SurfaceVariant
+ val DisabledUnselectedHandleOpacity = 0.38f
+ val DisabledUnselectedIconColor = ColorSchemeKeyTokens.SurfaceContainerHighest
+ val DisabledUnselectedIconOpacity = 0.38f
+ val DisabledUnselectedTrackColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val DisabledUnselectedTrackOutlineColor = ColorSchemeKeyTokens.OnSurface
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val HandleShape = ShapeKeyTokens.CornerFull
val PressedHandleHeight = 28.0.dp
val PressedHandleWidth = 28.0.dp
@@ -58,23 +59,23 @@
val TrackShape = ShapeKeyTokens.CornerFull
val TrackWidth = 52.0.dp
val UnselectedFocusHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val UnselectedFocusIconColor = ColorSchemeKeyTokens.SurfaceVariant
- val UnselectedFocusTrackColor = ColorSchemeKeyTokens.SurfaceVariant
+ val UnselectedFocusIconColor = ColorSchemeKeyTokens.SurfaceContainerHighest
+ val UnselectedFocusTrackColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val UnselectedFocusTrackOutlineColor = ColorSchemeKeyTokens.Outline
val UnselectedHandleColor = ColorSchemeKeyTokens.Outline
val UnselectedHandleHeight = 16.0.dp
val UnselectedHandleWidth = 16.0.dp
val UnselectedHoverHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val UnselectedHoverIconColor = ColorSchemeKeyTokens.SurfaceVariant
- val UnselectedHoverTrackColor = ColorSchemeKeyTokens.SurfaceVariant
+ val UnselectedHoverIconColor = ColorSchemeKeyTokens.SurfaceContainerHighest
+ val UnselectedHoverTrackColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val UnselectedHoverTrackOutlineColor = ColorSchemeKeyTokens.Outline
- val UnselectedIconColor = ColorSchemeKeyTokens.SurfaceVariant
+ val UnselectedIconColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val UnselectedIconSize = 16.0.dp
val UnselectedPressedHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val UnselectedPressedIconColor = ColorSchemeKeyTokens.SurfaceVariant
- val UnselectedPressedTrackColor = ColorSchemeKeyTokens.SurfaceVariant
+ val UnselectedPressedIconColor = ColorSchemeKeyTokens.SurfaceContainerHighest
+ val UnselectedPressedTrackColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val UnselectedPressedTrackOutlineColor = ColorSchemeKeyTokens.Outline
- val UnselectedTrackColor = ColorSchemeKeyTokens.SurfaceVariant
+ val UnselectedTrackColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val UnselectedTrackOutlineColor = ColorSchemeKeyTokens.Outline
val IconHandleHeight = 24.0.dp
val IconHandleWidth = 24.0.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimeInputTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimeInputTokens.kt
index 384f336..2bd2372 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimeInputTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimeInputTokens.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-// VERSION: v0_159
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
-
package androidx.compose.material3.tokens
-
import androidx.compose.ui.unit.dp
-
internal object TimeInputTokens {
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
val ContainerElevation = ElevationTokens.Level3
val ContainerShape = ShapeKeyTokens.CornerExtraLarge
+ val FocusIndicatorColor = ColorSchemeKeyTokens.Secondary
val HeadlineColor = ColorSchemeKeyTokens.OnSurfaceVariant
val HeadlineFont = TypographyKeyTokens.LabelMedium
val PeriodSelectorContainerHeight = 72.0.dp
@@ -42,8 +40,7 @@
val PeriodSelectorUnselectedHoverLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val PeriodSelectorUnselectedLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val PeriodSelectorUnselectedPressedLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
- val SurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
- val TimeFieldContainerColor = ColorSchemeKeyTokens.SurfaceVariant
+ val TimeFieldContainerColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val TimeFieldContainerHeight = 72.0.dp
val TimeFieldContainerShape = ShapeKeyTokens.CornerSmall
val TimeFieldContainerWidth = 96.0.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimePickerTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimePickerTokens.kt
index 0c4fe2c..76170c1 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimePickerTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimePickerTokens.kt
@@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_157
+
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
-
package androidx.compose.material3.tokens
-
import androidx.compose.ui.unit.dp
-
internal object TimePickerTokens {
- val ClockDialColor = ColorSchemeKeyTokens.SurfaceVariant
+ val ClockDialColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val ClockDialContainerSize = 256.0.dp
val ClockDialLabelTextFont = TypographyKeyTokens.BodyLarge
val ClockDialSelectedLabelTextColor = ColorSchemeKeyTokens.OnPrimary
@@ -35,7 +33,7 @@
val ClockDialSelectorTrackContainerWidth = 2.0.dp
val ClockDialShape = ShapeKeyTokens.CornerFull
val ClockDialUnselectedLabelTextColor = ColorSchemeKeyTokens.OnSurface
- val ContainerColor = ColorSchemeKeyTokens.Surface
+ val ContainerColor = ColorSchemeKeyTokens.SurfaceContainerHigh
val ContainerElevation = ElevationTokens.Level3
val ContainerShape = ShapeKeyTokens.CornerExtraLarge
val HeadlineColor = ColorSchemeKeyTokens.OnSurfaceVariant
@@ -57,7 +55,6 @@
val PeriodSelectorUnselectedPressedLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val PeriodSelectorVerticalContainerHeight = 80.0.dp
val PeriodSelectorVerticalContainerWidth = 52.0.dp
- val SurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val TimeSelector24HVerticalContainerWidth = 114.0.dp
val TimeSelectorContainerHeight = 80.0.dp
val TimeSelectorContainerShape = ShapeKeyTokens.CornerSmall
@@ -70,7 +67,7 @@
val TimeSelectorSelectedPressedLabelTextColor = ColorSchemeKeyTokens.OnPrimaryContainer
val TimeSelectorSeparatorColor = ColorSchemeKeyTokens.OnSurface
val TimeSelectorSeparatorFont = TypographyKeyTokens.DisplayLarge
- val TimeSelectorUnselectedContainerColor = ColorSchemeKeyTokens.SurfaceVariant
+ val TimeSelectorUnselectedContainerColor = ColorSchemeKeyTokens.SurfaceContainerHighest
val TimeSelectorUnselectedFocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
val TimeSelectorUnselectedHoverLabelTextColor = ColorSchemeKeyTokens.OnSurface
val TimeSelectorUnselectedLabelTextColor = ColorSchemeKeyTokens.OnSurface
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TopAppBarSmallCenteredTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TopAppBarSmallCenteredTokens.kt
index b684952..45e29bb 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TopAppBarSmallCenteredTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TopAppBarSmallCenteredTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -32,6 +32,7 @@
val HeadlineFont = TypographyKeyTokens.TitleLarge
val LeadingIconColor = ColorSchemeKeyTokens.OnSurface
val LeadingIconSize = 24.0.dp
+ val OnScrollContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val OnScrollContainerElevation = ElevationTokens.Level2
val TrailingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
val TrailingIconSize = 24.0.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TopAppBarSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TopAppBarSmallTokens.kt
index 3fc3b56..4e099d3 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TopAppBarSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TopAppBarSmallTokens.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// VERSION: v0_103
+// VERSION: v0_210
// GENERATED CODE - DO NOT MODIFY BY HAND
package androidx.compose.material3.tokens
@@ -25,11 +25,11 @@
val ContainerElevation = ElevationTokens.Level0
val ContainerHeight = 64.0.dp
val ContainerShape = ShapeKeyTokens.CornerNone
- val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
val HeadlineColor = ColorSchemeKeyTokens.OnSurface
val HeadlineFont = TypographyKeyTokens.TitleLarge
val LeadingIconColor = ColorSchemeKeyTokens.OnSurface
val LeadingIconSize = 24.0.dp
+ val OnScrollContainerColor = ColorSchemeKeyTokens.SurfaceContainer
val OnScrollContainerElevation = ElevationTokens.Level2
val TrailingIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
val TrailingIconSize = 24.0.dp
diff --git a/compose/runtime/runtime-livedata/build.gradle b/compose/runtime/runtime-livedata/build.gradle
index 50cb9c3..8bab57d 100644
--- a/compose/runtime/runtime-livedata/build.gradle
+++ b/compose/runtime/runtime-livedata/build.gradle
@@ -49,7 +49,7 @@
androidx {
name = "Compose LiveData integration"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose integration with LiveData"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime-rxjava2/build.gradle b/compose/runtime/runtime-rxjava2/build.gradle
index 9187fa9..2f58ae2 100644
--- a/compose/runtime/runtime-rxjava2/build.gradle
+++ b/compose/runtime/runtime-rxjava2/build.gradle
@@ -47,7 +47,7 @@
androidx {
name = "Compose RxJava 2 integration"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose integration with RxJava 2"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime-rxjava3/build.gradle b/compose/runtime/runtime-rxjava3/build.gradle
index 63a9aa5..f90ceba 100644
--- a/compose/runtime/runtime-rxjava3/build.gradle
+++ b/compose/runtime/runtime-rxjava3/build.gradle
@@ -47,7 +47,7 @@
androidx {
name = "Compose RxJava 3 integration"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose integration with RxJava 3"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime-saveable/build.gradle b/compose/runtime/runtime-saveable/build.gradle
index 972d80b..e73d562 100644
--- a/compose/runtime/runtime-saveable/build.gradle
+++ b/compose/runtime/runtime-saveable/build.gradle
@@ -116,7 +116,7 @@
androidx {
name = "Compose Saveable"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose components that allow saving and restoring the local ui state"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index ea7cfa6..9b26e4b4 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -518,8 +518,6 @@
}
public enum Recomposer.State {
- method public static androidx.compose.runtime.Recomposer.State valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.runtime.Recomposer.State[] values();
enum_constant public static final androidx.compose.runtime.Recomposer.State Idle;
enum_constant public static final androidx.compose.runtime.Recomposer.State Inactive;
enum_constant public static final androidx.compose.runtime.Recomposer.State InactivePendingWork;
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 5a5621f..e8028b1 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -550,8 +550,6 @@
}
public enum Recomposer.State {
- method public static androidx.compose.runtime.Recomposer.State valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.runtime.Recomposer.State[] values();
enum_constant public static final androidx.compose.runtime.Recomposer.State Idle;
enum_constant public static final androidx.compose.runtime.Recomposer.State Inactive;
enum_constant public static final androidx.compose.runtime.Recomposer.State InactivePendingWork;
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index d625262..063fec6 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -132,7 +132,7 @@
androidx {
name = "Compose Runtime"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2019"
description = "Tree composition support for code generated by the Compose compiler plugin and corresponding public API"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
index c7756b2..1271dd4 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
@@ -28,5 +28,5 @@
* IMPORTANT: Whenever updating this value, please make sure to also update `versionTable` and
* `minimumRuntimeVersionInt` in `VersionChecker.kt` of the compiler.
*/
- const val version: Int = 12200
+ const val version: Int = 12300
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 2204884..d57031f 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -75,7 +75,12 @@
/**
* The [RememberObserver] is being forgotten by a slot in the slot table.
*/
- fun forgetting(instance: RememberObserver, order: Int, priority: Int, after: Int)
+ fun forgetting(
+ instance: RememberObserver,
+ endRelativeOrder: Int,
+ priority: Int,
+ endRelativeAfter: Int
+ )
/**
* The [effect] should be called when changes are being applied but after the remember/forget
@@ -86,12 +91,22 @@
/**
* The [ComposeNodeLifecycleCallback] is being deactivated.
*/
- fun deactivating(instance: ComposeNodeLifecycleCallback)
+ fun deactivating(
+ instance: ComposeNodeLifecycleCallback,
+ endRelativeOrder: Int,
+ priority: Int,
+ endRelativeAfter: Int
+ )
/**
* The [ComposeNodeLifecycleCallback] is being released.
*/
- fun releasing(instance: ComposeNodeLifecycleCallback)
+ fun releasing(
+ instance: ComposeNodeLifecycleCallback,
+ endRelativeOrder: Int,
+ priority: Int,
+ endRelativeAfter: Int
+ )
}
/**
@@ -2059,7 +2074,7 @@
if (changeListWriter.pastParent) {
// The reader is after the first child of the group so we cannot reposition the
// writer to the parent to update it as this will cause the writer to navigate
- // backward which violates the single pass, forward walking nature of update.
+ // backward which violates the single pass, forward walking nature of update.
// Using an anchored updated allows to to violate this principle just for
// updating slots as this is required if the update occurs after the writer has
// been moved past the parent.
@@ -4081,11 +4096,18 @@
// even that in the documentation we claim ComposeNodeLifecycleCallback should be only
// implemented on the nodes we do not really enforce it here as doing so will be expensive.
if (slot is ComposeNodeLifecycleCallback) {
- rememberManager.releasing(slot)
+ val endRelativeOrder = slotsSize - slotIndex
+ rememberManager.releasing(slot, endRelativeOrder, -1, -1)
}
if (slot is RememberObserverHolder) {
- withAfterAnchorInfo(slot.after) { priority, after ->
- rememberManager.forgetting(slot.wrapped, slotIndex, priority, after)
+ val endRelativeSlotIndex = slotsSize - slotIndex
+ withAfterAnchorInfo(slot.after) { priority, endRelativeAfter ->
+ rememberManager.forgetting(
+ slot.wrapped,
+ endRelativeSlotIndex,
+ priority,
+ endRelativeAfter
+ )
}
}
if (slot is RecomposeScopeImpl) {
@@ -4098,12 +4120,12 @@
internal inline fun <R> SlotWriter.withAfterAnchorInfo(anchor: Anchor?, cb: (Int, Int) -> R) {
var priority = -1
- var after = -1
+ var endRelativeAfter = -1
if (anchor != null && anchor.valid) {
priority = anchorIndex(anchor)
- after = slotsEndAllIndex(priority)
+ endRelativeAfter = slotsSize - slotsEndAllIndex(priority)
}
- cb(priority, after)
+ cb(priority, endRelativeAfter)
}
internal val SlotWriter.isAfterFirstChild get() = currentGroup > parent + 1
@@ -4121,24 +4143,31 @@
for (group in start until end) {
val node = node(group)
if (node is ComposeNodeLifecycleCallback) {
- rememberManager.deactivating(node)
+ val endRelativeOrder = slotsSize - slotsStartIndex(group)
+ rememberManager.deactivating(node, endRelativeOrder, -1, -1)
}
- forEachData(group) { index, data ->
+ forEachData(group) { slotIndex, data ->
when (data) {
is RememberObserverHolder -> {
val wrapped = data.wrapped
if (wrapped is ReusableRememberObserver) {
// do nothing, the value should be preserved on reuse
} else {
- removeData(group, index, data)
- withAfterAnchorInfo(data.after) { priority, after ->
- rememberManager.forgetting(wrapped, index, priority, after)
+ removeData(group, slotIndex, data)
+ val endRelativeOrder = slotsSize - slotIndex
+ withAfterAnchorInfo(data.after) { priority, endRelativeAfter ->
+ rememberManager.forgetting(
+ wrapped,
+ endRelativeOrder,
+ priority,
+ endRelativeAfter
+ )
}
}
}
is RecomposeScopeImpl -> {
- removeData(group, index, data)
+ removeData(group, slotIndex, data)
data.release()
}
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
index 371faae..f99e46e 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
@@ -900,13 +900,15 @@
// Record derived state dependency mapping
if (value is DerivedState<*>) {
+ val record = value.currentRecord
derivedStates.removeScope(value)
- value.currentRecord.dependencies.forEachKey { dependency ->
+ record.dependencies.forEachKey { dependency ->
if (dependency is StateObjectImpl) {
dependency.recordReadIn(ReaderKind.Composition)
}
derivedStates.add(dependency, value)
}
+ it.recordDerivedStateValue(value, record.currentValue)
}
}
}
@@ -1280,7 +1282,7 @@
private val abandoning: MutableSet<RememberObserver>
) : RememberManager {
private val remembering = mutableListOf<RememberObserver>()
- private val forgetting = mutableListOf<Any>()
+ private val leaving = mutableListOf<Any>()
private val sideEffects = mutableListOf<() -> Unit>()
private var releasing: MutableScatterSet<ComposeNodeLifecycleCallback>? = null
private val pending = mutableListOf<Any>()
@@ -1290,43 +1292,51 @@
remembering.add(instance)
}
- override fun forgetting(instance: RememberObserver, order: Int, priority: Int, after: Int) {
- processPending(order)
- if (order < after) {
- pending.add(instance)
- priorities.add(priority)
- afters.add(after)
- } else {
- forgetting.add(instance)
- }
+ override fun forgetting(
+ instance: RememberObserver,
+ endRelativeOrder: Int,
+ priority: Int,
+ endRelativeAfter: Int
+ ) {
+ recordLeaving(instance, endRelativeOrder, priority, endRelativeAfter)
}
override fun sideEffect(effect: () -> Unit) {
sideEffects += effect
}
- override fun deactivating(instance: ComposeNodeLifecycleCallback) {
- forgetting += instance
+ override fun deactivating(
+ instance: ComposeNodeLifecycleCallback,
+ endRelativeOrder: Int,
+ priority: Int,
+ endRelativeAfter: Int
+ ) {
+ recordLeaving(instance, endRelativeOrder, priority, endRelativeAfter)
}
- override fun releasing(instance: ComposeNodeLifecycleCallback) {
+ override fun releasing(
+ instance: ComposeNodeLifecycleCallback,
+ endRelativeOrder: Int,
+ priority: Int,
+ endRelativeAfter: Int
+ ) {
val releasing = releasing
?: mutableScatterSetOf<ComposeNodeLifecycleCallback>().also { releasing = it }
releasing += instance
- forgetting += instance
+ recordLeaving(instance, endRelativeOrder, priority, endRelativeAfter)
}
fun dispatchRememberObservers() {
// Add any pending out-of-order forgotten objects
- processPending(Int.MAX_VALUE)
+ processPendingLeaving(Int.MIN_VALUE)
// Send forgets and node callbacks
- if (forgetting.isNotEmpty()) {
+ if (leaving.isNotEmpty()) {
trace("Compose:onForgotten") {
val releasing = releasing
- for (i in forgetting.size - 1 downTo 0) {
- val instance = forgetting[i]
+ for (i in leaving.size - 1 downTo 0) {
+ val instance = leaving[i]
if (instance is RememberObserver) {
abandoning.remove(instance)
instance.onForgotten()
@@ -1380,39 +1390,107 @@
}
}
- private fun processPending(order: Int) {
+ private fun recordLeaving(
+ instance: Any,
+ endRelativeOrder: Int,
+ priority: Int,
+ endRelativeAfter: Int
+ ) {
+ processPendingLeaving(endRelativeOrder)
+ if (endRelativeAfter in 0 until endRelativeOrder) {
+ pending.add(instance)
+ priorities.add(priority)
+ afters.add(endRelativeAfter)
+ } else {
+ leaving.add(instance)
+ }
+ }
+
+ private fun processPendingLeaving(endRelativeOrder: Int) {
if (pending.isNotEmpty()) {
- var i = 0
+ var index = 0
var toAdd: MutableList<Any>? = null
+ var toAddAfter: MutableIntList? = null
var toAddPriority: MutableIntList? = null
- while (i < afters.size) {
- if (order >= afters[i]) {
- val priority = priorities[i]
- val forgetting = pending[i]
- afters.removeAt(i)
- priorities.removeAt(i)
- pending.removeAt(i)
+ while (index < afters.size) {
+ if (endRelativeOrder <= afters[index]) {
+ val instance = pending.removeAt(index)
+ val endRelativeAfter = afters.removeAt(index)
+ val priority = priorities.removeAt(index)
+
if (toAdd == null) {
- toAdd = mutableListOf(forgetting)
+ toAdd = mutableListOf(instance)
+ toAddAfter = MutableIntList().also { it.add(endRelativeAfter) }
toAddPriority = MutableIntList().also { it.add(priority) }
} else {
toAddPriority as MutableIntList
- val insertLocation = toAddPriority.indexOfFirst { priority > it }.let {
- if (it < 0) toAddPriority.size else it
- }
- toAddPriority.add(insertLocation, priority)
- toAdd.add(insertLocation, forgetting)
+ toAddAfter as MutableIntList
+ toAdd.add(instance)
+ toAddAfter.add(endRelativeAfter)
+ toAddPriority.add(priority)
}
} else {
- i++
+ index++
}
}
- if (toAdd != null) forgetting.addAll(toAdd)
+ if (toAdd != null) {
+ toAddPriority as MutableIntList
+ toAddAfter as MutableIntList
+
+ // Sort the list into [after, -priority] order where it is ordered by after
+ // in ascending order as the primary key and priority in descending order as
+ // secondary key.
+
+ // For example if remember occurs after a child group it must be added after
+ // all the remembers of the child. This is reported with an after which is the
+ // slot index of the child's last slot. As this slot might be at the same
+ // location as where its parents ends this would be ambiguous which should
+ // first if both the two groups request a slot to be after the same slot.
+ // Priority is used to break the tie here which is the group index of the group
+ // which is leaving. Groups that are lower must be added before the parent's
+ // remember when they have the same after.
+
+ // The sort must be stable as as consecutive remembers in the same group after
+ // the same child will have the same after and priority.
+
+ // A selection sort is used here because it is stable and the groups are
+ // typically very short so this quickly exit list of one and not loop for
+ // for sizes of 2. As the information is split between three lists, to
+ // reduce allocations, [MutableList.sort] cannot be used as it doesn't have
+ // an option to supply a custom swap.
+ for (i in 0 until toAdd.size - 1) {
+ for (j in i + 1 until toAdd.size) {
+ val iAfter = toAddAfter[i]
+ val jAfter = toAddAfter[j]
+ if (
+ iAfter < jAfter ||
+ (jAfter == iAfter && toAddPriority[i] < toAddPriority[j])
+ ) {
+ toAdd.swap(i, j)
+ toAddPriority.swap(i, j)
+ toAddAfter.swap(i, j)
+ }
+ }
+ }
+ leaving.addAll(toAdd)
+ }
}
}
}
}
+private fun <T> MutableList<T>.swap(a: Int, b: Int) {
+ val item = this[a]
+ this[a] = this[b]
+ this[b] = item
+}
+
+private fun MutableIntList.swap(a: Int, b: Int) {
+ val item = this[a]
+ this[a] = this[b]
+ this[b] = item
+}
+
internal object ScopeInvalidated
@ExperimentalComposeRuntimeApi
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
index 5f0d312..86142c7 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
@@ -265,17 +265,19 @@
// value is used instead which doesn't notify. This allow the read observer to read the
// value and only update the cache once.
Snapshot.current.readObserver?.invoke(this)
- return first.withCurrent {
- @Suppress("UNCHECKED_CAST")
- currentRecord(it, Snapshot.current, true, calculation).result as T
- }
+ // Read observer could advance the snapshot, so get current snapshot again
+ val snapshot = Snapshot.current
+ val record = current(first, snapshot)
+ @Suppress("UNCHECKED_CAST")
+ return currentRecord(record, snapshot, true, calculation).result as T
}
- override val currentRecord: DerivedState.Record<T> get() {
- return first.withCurrent {
- currentRecord(it, Snapshot.current, false, calculation)
+ override val currentRecord: DerivedState.Record<T>
+ get() {
+ val snapshot = Snapshot.current
+ val record = current(first, snapshot)
+ return currentRecord(record, snapshot, false, calculation)
}
- }
override fun toString(): String = first.withCurrent {
"DerivedState(value=${displayValue()})@${hashCode()}"
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt
index 2af8671..c07a344 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt
@@ -322,16 +322,16 @@
return true
}
- if (instance is DerivedState<*>) {
- val trackedDependencies = trackedDependencies
- ?: MutableScatterMap<DerivedState<*>, Any?>().also { trackedDependencies = it }
-
- trackedDependencies[instance] = instance.currentRecord.currentValue
- }
-
return false
}
+ fun recordDerivedStateValue(instance: DerivedState<*>, value: Any?) {
+ val trackedDependencies = trackedDependencies
+ ?: MutableScatterMap<DerivedState<*>, Any?>().also { trackedDependencies = it }
+
+ trackedDependencies[instance] = value
+ }
+
/**
* Returns true if the scope is observing derived state which might make this scope
* conditionally invalidated.
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index ab0082f..a51e665 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -1480,6 +1480,8 @@
*/
val isGroupEnd get() = currentGroup == currentGroupEnd
+ val slotsSize get() = slots.size - slotsGapLen
+
/**
* Return true if the current slot starts a node. A node is a kind of group so this will
* return true for isGroup as well.
@@ -1804,9 +1806,9 @@
set(currentGroup, index, value)
/**
- * Set the [group] slot at [index] to [value]. Returns the previous value.
+ * Convert a slot group index into a global slot index.
*/
- fun set(group: Int, index: Int, value: Any?): Any? {
+ fun slotIndexOfGroupSlotIndex(group: Int, index: Int): Int {
val address = groupIndexToAddress(group)
val slotsStart = groups.slotIndex(address)
val slotsEnd = groups.dataIndex(groupIndexToAddress(group + 1))
@@ -1815,6 +1817,14 @@
runtimeCheck(slotsIndex >= slotsStart && slotsIndex < slotsEnd) {
"Write to an invalid slot index $index for group $group"
}
+ return slotsIndex
+ }
+
+ /**
+ * Set the [group] slot at [index] to [value]. Returns the previous value.
+ */
+ fun set(group: Int, index: Int, value: Any?): Any? {
+ val slotsIndex = slotIndexOfGroupSlotIndex(group, index)
val slotAddress = dataIndexToDataAddress(slotsIndex)
val result = slots[slotAddress]
slots[slotAddress] = value
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFlow.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFlow.kt
index 03c5ce9..245f130 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFlow.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFlow.kt
@@ -19,6 +19,7 @@
package androidx.compose.runtime
import androidx.collection.MutableScatterSet
+import androidx.compose.runtime.collection.fastAny
import androidx.compose.runtime.snapshots.ReaderKind
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.runtime.snapshots.StateObjectImpl
@@ -124,7 +125,7 @@
// Register the apply observer before running for the first time
// so that we don't miss updates.
val unregisterApplyObserver = Snapshot.registerApplyObserver { changed, _ ->
- val maybeObserved = changed.any {
+ val maybeObserved = changed.fastAny {
it !is StateObjectImpl || it.isReadIn(ReaderKind.SnapshotFlow)
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt
index 7cb5056..0dce8de 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operation.kt
@@ -203,12 +203,15 @@
rememberManager: RememberManager
) {
val count = getInt(Count)
- slots.forEachTailSlot(slots.parent, count) { index, value ->
+ val slotsSize = slots.slotsSize
+ slots.forEachTailSlot(slots.parent, count) { slotIndex, value ->
when (value) {
- is RememberObserverHolder ->
- slots.withAfterAnchorInfo(value.after) { priority, after ->
- rememberManager.forgetting(value.wrapped, index, priority, after)
- }
+ is RememberObserverHolder -> {
+ // Values are always updated in the composition order (not slot table order)
+ // so there is no need to reorder these.
+ val endRelativeOrder = slotsSize - slotIndex
+ rememberManager.forgetting(value.wrapped, endRelativeOrder, -1, -1)
+ }
is RecomposeScopeImpl -> value.release()
}
}
@@ -241,15 +244,15 @@
rememberManager.remembering(value.wrapped)
}
when (val previous = slots.set(groupSlotIndex, value)) {
- is RememberObserverHolder ->
- slots.withAfterAnchorInfo(previous.after) { priority, after ->
- rememberManager.forgetting(
- previous.wrapped,
- groupSlotIndex,
- priority,
- after
- )
- }
+ is RememberObserverHolder -> {
+ val endRelativeOrder = slots.slotsSize - slots.slotIndexOfGroupSlotIndex(
+ slots.currentGroup,
+ groupSlotIndex
+ )
+ // Values are always updated in the composition order (not slot table order)
+ // so there is no need to reorder these.
+ rememberManager.forgetting(previous.wrapped, endRelativeOrder, -1, -1)
+ }
is RecomposeScopeImpl -> previous.release()
}
}
@@ -284,15 +287,18 @@
}
val groupIndex = slots.anchorIndex(anchor)
when (val previous = slots.set(groupIndex, groupSlotIndex, value)) {
- is RememberObserverHolder ->
- slots.withAfterAnchorInfo(previous.after) { priority, after ->
+ is RememberObserverHolder -> {
+ val endRelativeSlotOrder = slots.slotsSize -
+ slots.slotIndexOfGroupSlotIndex(groupIndex, groupSlotIndex)
+ slots.withAfterAnchorInfo(previous.after) { priority, endRelativeAfter ->
rememberManager.forgetting(
previous.wrapped,
- groupSlotIndex,
+ endRelativeSlotOrder,
priority,
- after
+ endRelativeAfter
)
}
+ }
is RecomposeScopeImpl -> previous.release()
}
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScatterSetWrapper.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScatterSetWrapper.kt
index 5854635..ddeccc1 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScatterSetWrapper.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/ScatterSetWrapper.kt
@@ -53,3 +53,10 @@
forEach(block)
}
}
+
+internal inline fun Set<Any>.fastAny(block: (Any) -> Boolean) =
+ if (this is ScatterSetWrapper<Any>) {
+ set.any(block)
+ } else {
+ any(block)
+ }
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
index 4b0e903..6bbe83b 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -2157,6 +2157,184 @@
}
@Test
+ fun testRememberObserver_RememberForgetOrder_ReplaceOnRecompose() = compositionTest {
+ var order = 0
+ val objects = mutableListOf<Any>()
+ val newRememberObject = { name: String, data: Int ->
+ object :
+ RememberObserver,
+ Counted,
+ Ordered,
+ Named,
+ WithData {
+ override var name = name
+ override var data = data
+ override var count = 0
+ override var rememberOrder = -1
+ override var forgetOrder = -1
+ override fun onRemembered() {
+ assertEquals(-1, rememberOrder, "Only one call to onRemembered expected")
+ rememberOrder = order++
+ count++
+ }
+
+ override fun onForgotten() {
+ assertEquals(-1, forgetOrder, "Only one call to onForgotten expected")
+ forgetOrder = order++
+ count--
+ }
+
+ override fun onAbandoned() {
+ assertEquals(0, count, "onAbandoned called after onRemembered")
+ }
+
+ override fun toString(): String =
+ "$name: count($count), remember($rememberOrder), forgotten($forgetOrder)"
+ }.also { objects.add(it) }
+ }
+
+ var changing by mutableStateOf(0)
+ val fixed = 10
+
+ @Composable
+ @NonRestartableComposable
+ fun RememberUser(name: String, data: Int) {
+ remember(name, data) { newRememberObject(name, data) }
+ }
+
+ @Composable
+ fun Tree() {
+ Linear {
+ RememberUser("A", changing)
+ RememberUser("B", fixed)
+ }
+ }
+
+ @Composable
+ fun Composition(includeTree: Boolean) {
+ Linear {
+ if (includeTree) Tree()
+ }
+ }
+
+ var includeTree by mutableStateOf(true)
+
+ compose {
+ Composition(includeTree)
+ }
+
+ changing++
+ advance()
+
+ includeTree = false
+ advance()
+
+ val nameAndDataInForgetOrder = objects.mapNotNull {
+ it as? Ordered
+ }.sortedBy {
+ it.forgetOrder
+ }.map {
+ val named = it as Named
+ val withData = it as WithData
+ "${named.name}:${withData.data}"
+ }.joinToString()
+
+ assertEquals("A:0, B:10, A:1", nameAndDataInForgetOrder)
+ }
+
+ @Test
+ fun testRememberObserver_RememberForgetOrder_RelativeOrder() = compositionTest {
+ var order = 0
+ val objects = mutableListOf<Any>()
+ val newRememberObject = { name: String, data: Int ->
+ object :
+ RememberObserver,
+ Counted,
+ Ordered,
+ Named,
+ WithData {
+ override var name = name
+ override var data = data
+ override var count = 0
+ override var rememberOrder = -1
+ override var forgetOrder = -1
+ override fun onRemembered() {
+ assertEquals(-1, rememberOrder, "Only one call to onRemembered expected")
+ rememberOrder = order++
+ count++
+ }
+
+ override fun onForgotten() {
+ assertEquals(-1, forgetOrder, "Only one call to onForgotten expected")
+ forgetOrder = order++
+ count--
+ }
+
+ override fun onAbandoned() {
+ assertEquals(0, count, "onAbandoned called after onRemembered")
+ }
+
+ override fun toString(): String =
+ "$name: count($count), remember($rememberOrder), forgotten($forgetOrder)"
+ }.also { objects.add(it) }
+ }
+
+ var changing by mutableStateOf(0)
+ var includeChildren by mutableStateOf(true)
+ val fixed = 10
+
+ @Composable
+ @NonRestartableComposable
+ fun RememberUser(name: String, data: Int) {
+ remember(name, data) { newRememberObject(name, data) }
+ }
+
+ @Composable
+ @NonRestartableComposable
+ fun Children() {
+ RememberUser("A2", fixed)
+ RememberUser("B2", fixed)
+ }
+
+ @Composable
+ @NonRestartableComposable
+ fun NoChildren() {}
+
+ @Composable
+ fun Composition() {
+ InlineLinear {
+ RememberUser("A1", changing)
+ if (includeChildren) {
+ Children()
+ } else {
+ NoChildren()
+ }
+ RememberUser("B1", changing)
+ }
+ }
+
+ compose {
+ Composition()
+ }
+
+ changing++
+ includeChildren = false
+ advance()
+
+ val nameAndDataInForgetOrder = objects.mapNotNull { item ->
+ (item as? Ordered)?.takeIf { it.forgetOrder >= 0 }
+ }.sortedBy {
+ it.forgetOrder
+ }.joinToString {
+ val named = it as Named
+ val withData = it as WithData
+ "${named.name}:${withData.data}"
+ }
+
+ assertEquals("B1:0, B2:10, A2:10, A1:0", nameAndDataInForgetOrder)
+ }
+
+ @Test
fun testRememberObserver_Abandon_Simple() = compositionTest {
val abandonedObjects = mutableListOf<RememberObserver>()
val observed = object : RememberObserver {
@@ -4606,6 +4784,10 @@
val name: String
}
+private interface WithData {
+ val data: Int
+}
+
private fun Int.isOdd() = this % 2 == 1
private fun Int.isEven() = this % 2 == 0
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/GroupSizeValidationTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/GroupSizeValidationTests.kt
index cf106d3..5761c5f 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/GroupSizeValidationTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/GroupSizeValidationTests.kt
@@ -31,7 +31,7 @@
fun spacerLike() = compositionTest {
slotExpect(
name = "SpacerLike",
- noMoreGroupsThan = 5,
+ noMoreGroupsThan = 3,
noMoreSlotsThan = 10,
) {
SpacerLike(Modifier)
@@ -42,7 +42,7 @@
fun columnLikeSize() = compositionTest {
slotExpect(
name = "ColumnLike",
- noMoreGroupsThan = 6,
+ noMoreGroupsThan = 3,
noMoreSlotsThan = 9,
) {
ColumnLike { }
@@ -64,7 +64,7 @@
fun basicTextLikeSize() = compositionTest {
slotExpect(
name = "TextLike",
- noMoreGroupsThan = 9,
+ noMoreGroupsThan = 5,
noMoreSlotsThan = 13
) {
BasicTextLike("")
@@ -75,7 +75,7 @@
fun checkboxLike() = compositionTest {
slotExpect(
name = "CheckboxLike",
- noMoreGroupsThan = 12,
+ noMoreGroupsThan = 8,
noMoreSlotsThan = 17
) {
CheckboxLike(checked = false, onCheckedChange = { })
@@ -639,7 +639,12 @@
// Utility functions for the tests
@Composable
-private inline fun Marker(content: @Composable () -> Unit) = content()
+@ExplicitGroupsComposable
+private inline fun Marker(content: @Composable () -> Unit) {
+ currentComposer.startReplaceGroup(MarkerGroup)
+ content()
+ currentComposer.endReplaceGroup()
+}
// left unused for debugging. This is useful for debugging differences in the slot table
@Suppress("unused")
diff --git a/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/ParameterizedComposeTestRuleTest.kt b/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/ParameterizedComposeTestRuleTest.kt
new file mode 100644
index 0000000..0f65302
--- /dev/null
+++ b/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/ParameterizedComposeTestRuleTest.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.compose.testutils
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+
+class ParameterizedComposeTestRuleTest {
+ @get:Rule
+ val composeTestRule = createParameterizedComposeTestRule<Param>()
+
+ @Test
+ fun assertionErrorInParameterIsPropagated() {
+ val paramList = listOf(Param("first"), Param("second"))
+ composeTestRule.setContent {
+ Box(modifier = Modifier.size(10.dp))
+ }
+
+ val error = kotlin.runCatching {
+ composeTestRule.forEachParameter(paramList) {
+ if (it.singleParam == "first") {
+ throw AssertionError()
+ }
+ }
+ }
+
+ assertTrue(error.exceptionOrNull()?.localizedMessage?.contains("Error on Config") == true)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setContentCannotBeCalledTwice() {
+ composeTestRule.setContent {
+ Box(modifier = Modifier.size(10.dp))
+ }
+ composeTestRule.setContent {
+ Box(modifier = Modifier.size(10.dp))
+ }
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun forEachParameterNeedsAtLeastOneParameter() {
+ composeTestRule.forEachParameter(emptyList()) {}
+ }
+
+ @Test
+ fun forEachParameterRunsCompositionForEachParameter() {
+ val paramList = listOf(Param("first"), Param("second"))
+ var recompositionCount = 0
+ composeTestRule.setContent {
+ recompositionCount++
+ }
+ composeTestRule.forEachParameter(paramList) {}
+ assertTrue(recompositionCount == paramList.size)
+ }
+
+ @Test
+ fun forEachParameterPropagatesParameterToContentAndRunBlock() {
+ val paramList = listOf(Param("first"), Param("second"))
+ var index = 0
+ composeTestRule.setContent {
+ assertTrue(it.singleParam == paramList[index].singleParam)
+ }
+
+ composeTestRule.forEachParameter(paramList) {
+ assertTrue(it.singleParam == paramList[index].singleParam)
+ index++
+ }
+ }
+
+ data class Param(var singleParam: String)
+}
diff --git a/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/ParameterizedComposeTestRule.kt b/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/ParameterizedComposeTestRule.kt
index b3228349..9b38842 100644
--- a/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/ParameterizedComposeTestRule.kt
+++ b/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/ParameterizedComposeTestRule.kt
@@ -17,7 +17,10 @@
package androidx.compose.testutils
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.junit4.createComposeRule
@@ -71,20 +74,22 @@
block: T.() -> Unit
) {
check(parameters.isNotEmpty()) { "Config List Cannot Be Empty" }
- val configState = mutableStateOf(parameters.first())
+ var configState by mutableStateOf(parameters.first())
// setting content on the first config
rule.setContent {
- content(configState.value)
+ key(configState) {
+ content(configState)
+ }
}
- runBlockCheck(block, configState.value)
+ runBlockCheck(block, configState)
rule.mainClock.advanceTimeByFrame() // push time forward
// changing contents on remaining configs
for (index in 1..parameters.lastIndex) {
- configState.value = parameters[index] // change params
+ configState = parameters[index] // change params
rule.mainClock.advanceTimeByFrame() // push time forward
- runBlockCheck(block, configState.value)
+ runBlockCheck(block, configState)
}
}
diff --git a/compose/ui/ui-android-stubs/build.gradle b/compose/ui/ui-android-stubs/build.gradle
index b40a943..161d6f7 100644
--- a/compose/ui/ui-android-stubs/build.gradle
+++ b/compose/ui/ui-android-stubs/build.gradle
@@ -34,7 +34,7 @@
androidx {
name = "Compose Android Stubs"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Stubs for classes in older Android APIs"
metalavaK2UastEnabled = true
diff --git a/compose/ui/ui-geometry/build.gradle b/compose/ui/ui-geometry/build.gradle
index 1d45f14..e731a2f 100644
--- a/compose/ui/ui-geometry/build.gradle
+++ b/compose/ui/ui-geometry/build.gradle
@@ -41,7 +41,7 @@
commonMain {
dependencies {
implementation(libs.kotlinStdlibCommon)
- implementation("androidx.compose.runtime:runtime:1.6.0")
+ implementation(project(":compose:runtime:runtime"))
implementation(project(":compose:ui:ui-util"))
}
}
@@ -95,7 +95,7 @@
androidx {
name = "Compose Geometry"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose classes related to dimensions without units"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index 8b2bfa9..53c4ae6 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -689,8 +689,6 @@
}
public enum Path.Direction {
- method public static androidx.compose.ui.graphics.Path.Direction valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.graphics.Path.Direction[] values();
enum_constant public static final androidx.compose.ui.graphics.Path.Direction Clockwise;
enum_constant public static final androidx.compose.ui.graphics.Path.Direction CounterClockwise;
}
@@ -720,6 +718,7 @@
public final class PathGeometryKt {
method public static androidx.compose.ui.graphics.Path.Direction computeDirection(androidx.compose.ui.graphics.Path);
method public static java.util.List<androidx.compose.ui.graphics.Path> divide(androidx.compose.ui.graphics.Path, optional java.util.List<androidx.compose.ui.graphics.Path> contours);
+ method public static androidx.compose.ui.graphics.Path reverse(androidx.compose.ui.graphics.Path, optional androidx.compose.ui.graphics.Path destination);
}
public final class PathHitTester {
@@ -745,8 +744,6 @@
}
public enum PathIterator.ConicEvaluation {
- method public static androidx.compose.ui.graphics.PathIterator.ConicEvaluation valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.graphics.PathIterator.ConicEvaluation[] values();
enum_constant public static final androidx.compose.ui.graphics.PathIterator.ConicEvaluation AsConic;
enum_constant public static final androidx.compose.ui.graphics.PathIterator.ConicEvaluation AsQuadratics;
}
@@ -799,8 +796,6 @@
}
public enum PathSegment.Type {
- method public static androidx.compose.ui.graphics.PathSegment.Type valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.graphics.PathSegment.Type[] values();
enum_constant public static final androidx.compose.ui.graphics.PathSegment.Type Close;
enum_constant public static final androidx.compose.ui.graphics.PathSegment.Type Conic;
enum_constant public static final androidx.compose.ui.graphics.PathSegment.Type Cubic;
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index f96de04..e57466d 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -760,8 +760,6 @@
}
public enum Path.Direction {
- method public static androidx.compose.ui.graphics.Path.Direction valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.graphics.Path.Direction[] values();
enum_constant public static final androidx.compose.ui.graphics.Path.Direction Clockwise;
enum_constant public static final androidx.compose.ui.graphics.Path.Direction CounterClockwise;
}
@@ -791,6 +789,7 @@
public final class PathGeometryKt {
method public static androidx.compose.ui.graphics.Path.Direction computeDirection(androidx.compose.ui.graphics.Path);
method public static java.util.List<androidx.compose.ui.graphics.Path> divide(androidx.compose.ui.graphics.Path, optional java.util.List<androidx.compose.ui.graphics.Path> contours);
+ method public static androidx.compose.ui.graphics.Path reverse(androidx.compose.ui.graphics.Path, optional androidx.compose.ui.graphics.Path destination);
}
public final class PathHitTester {
@@ -816,8 +815,6 @@
}
public enum PathIterator.ConicEvaluation {
- method public static androidx.compose.ui.graphics.PathIterator.ConicEvaluation valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.graphics.PathIterator.ConicEvaluation[] values();
enum_constant public static final androidx.compose.ui.graphics.PathIterator.ConicEvaluation AsConic;
enum_constant public static final androidx.compose.ui.graphics.PathIterator.ConicEvaluation AsQuadratics;
}
@@ -870,8 +867,6 @@
}
public enum PathSegment.Type {
- method public static androidx.compose.ui.graphics.PathSegment.Type valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.graphics.PathSegment.Type[] values();
enum_constant public static final androidx.compose.ui.graphics.PathSegment.Type Close;
enum_constant public static final androidx.compose.ui.graphics.PathSegment.Type Conic;
enum_constant public static final androidx.compose.ui.graphics.PathSegment.Type Cubic;
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index a28e275..56a2d27 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KmpPlatformsKt
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
@@ -43,7 +45,7 @@
api(libs.androidx.annotation)
api(project(":compose:ui:ui-unit"))
- implementation("androidx.compose.runtime:runtime:1.6.0")
+ implementation(project(":compose:runtime:runtime"))
implementation(project(":compose:ui:ui-util"))
implementation("androidx.collection:collection:1.4.0")
}
@@ -133,7 +135,7 @@
androidx {
name = "Compose Graphics"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose graphics"
legacyDisableKotlinStrictApiMode = true
@@ -147,6 +149,8 @@
namespace "androidx.compose.ui.graphics"
}
-tasks.findByName("desktopTest").configure {
- systemProperties["GOLDEN_PATH"] = project.rootDir.absolutePath + "/../../golden"
-}
+if (KmpPlatformsKt.enableDesktop(project)) {
+ tasks.findByName("desktopTest").configure {
+ systemProperties["GOLDEN_PATH"] = project.rootDir.absolutePath + "/../../golden"
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathDivisionTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathDivisionTest.kt
index ae6685a..d9e509f 100644
--- a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathDivisionTest.kt
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathDivisionTest.kt
@@ -20,7 +20,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import kotlin.test.assertEquals
-import org.junit.Assert.assertArrayEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -80,37 +79,3 @@
assertEquals(2, paths.size)
}
}
-
-private fun valueCountForType(type: PathSegment.Type) = when (type) {
- PathSegment.Type.Move -> 2
- PathSegment.Type.Line -> 4
- PathSegment.Type.Quadratic -> 6
- PathSegment.Type.Conic -> 8
- PathSegment.Type.Cubic -> 8
- PathSegment.Type.Close -> 0
- PathSegment.Type.Done -> 0
-}
-
-private fun assertPathEquals(
- expected: Path,
- actual: Path,
- points1: FloatArray = FloatArray(8),
- points2: FloatArray = FloatArray(8)
-) {
- val iterator1 = expected.iterator()
- val iterator2 = actual.iterator()
-
- assertEquals(iterator1.calculateSize(), iterator2.calculateSize())
-
- while (iterator1.hasNext() && iterator2.hasNext()) {
- val type1 = iterator1.next(points1)
- val type2 = iterator2.next(points2)
-
- points1.fill(0.0f, valueCountForType(type1))
- points2.fill(0.0f, valueCountForType(type2))
-
- assertEquals(type1, type2)
-
- assertArrayEquals(points1, points2, 1e-7f)
- }
-}
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathHitTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathHitTest.kt
index 0a9347d..81aa0a6 100644
--- a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathHitTest.kt
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathHitTest.kt
@@ -66,31 +66,3 @@
assertFalse(Offset(350f, 350f) in evenOddHitTester)
}
}
-
-/**
- * Creates a path from the specified shape, using the EvenOdd fill type to match SVG.
- * The returned path has its origin set to 0,0 for convenience.
- */
-private fun createSvgPath(svgShape: SvgShape) = Path().apply {
- addSvg(svgShape.pathData)
- val bounds = getBounds()
- translate(Offset(-bounds.left, -bounds.top))
- fillType = PathFillType.EvenOdd
-}
-
-/* ktlint-disable max-line-length */
-internal enum class SvgShape(val pathData: String) {
- Cubics(
- "M958.729,822.904L958.729,1086.67C958.729,1159.45 899.635,1218.55 826.848,1218.55L563.086,1218.55L355.844,1444.63L450.045,1218.55L337.004,1218.55C264.217,1218.55 205.123,1159.45 205.123,1086.67L205.123,822.904C205.123,750.117 264.217,691.023 337.004,691.023L826.848,691.023C899.635,691.023 958.729,750.117 958.729,822.904ZM581.925,850.888C544.745,781.585 470.384,781.585 433.204,816.236C396.023,850.888 396.023,920.191 433.204,989.493C459.23,1041.47 526.155,1093.45 581.925,1128.1C637.696,1093.45 704.621,1041.47 730.647,989.493C767.829,920.191 767.829,850.888 730.647,816.236C693.467,781.585 619.106,781.585 581.925,850.888Z"
- ),
- Quads(
- "M 664.72,242.306 L 664.72,423.585 Q 649.1189999999999,514.2250000000001 574.08,514.225 L 392.801,514.225 L 250.367,669.607 L 315.11,514.225 L 237.419,514.225 Q 146.779,498.62249999999995 146.779,423.585 L 146.779,242.306 Q 162.38,151.666 237.419,151.666 L 574.08,151.666 Q 664.72,167.267 664.72,242.306 M 375.55,220.052 Q 355.947,196.49699999999999 334.296,208.998 Q 310.741,228.5995 323.242,250.252 Q 294.48900000000003,239.65350000000004 281.988,261.306 Q 271.3895000000001,290.05899999999997 293.042,302.56 Q 262.842,307.758 262.842,332.76 Q 268.0400000000001,362.9599999999999 293.042,362.96 Q 269.48699999999997,382.5615 281.988,404.214 Q 301.58950000000004,427.769 323.242,415.268 Q 312.6435,444.02099999999996 334.296,456.522 Q 363.0490000000001,467.1189999999999 375.55,445.468 Q 380.74850000000004,475.66700000000003 405.749,475.667 Q 435.94899999999996,470.46849999999995 435.949,445.468 Q 455.552,469.02150000000006 477.203,456.522 Q 500.75800000000004,436.919 488.257,415.268 Q 517.01,425.865 529.511,404.214 Q 540.1095,375.461 518.457,362.96 Q 548.6569999999999,357.76200000000006 548.657,332.76 Q 543.4590000000001,302.56000000000006 518.457,302.56 Q 542.0120000000001,282.95849999999996 529.511,261.306 Q 509.9095,237.751 488.257,250.252 Q 498.8555,221.499 477.203,208.998 Q 448.45000000000005,198.3995 435.949,220.052 Q 430.751,189.85200000000003 405.749,189.852 Q 375.55000000000007,195.04999999999995 375.55,220.052Z"
- ),
- Lines(
- "M741.323,698.969L825.045,956.64L1095.98,956.64L876.789,1115.89L960.511,1373.56L741.323,1214.31L522.134,1373.56L605.857,1115.89L386.668,956.64L657.6,956.64L741.323,698.969Z"
- ),
- FillTypes(
- "M570.019,673.111L580.895,726.063C601.945,729.284 622.576,734.812 642.417,742.548L678.312,702.128C698.399,711.261 717.552,722.32 735.505,735.149L718.449,786.445C735.068,799.759 750.171,814.862 763.485,831.481L814.781,814.425C827.61,832.378 838.669,851.531 847.801,871.618L807.382,907.513C815.118,927.354 820.646,947.985 823.867,969.035L876.819,979.911C878.953,1001.87 878.953,1023.99 876.819,1045.95L823.867,1056.83C820.646,1077.88 815.118,1098.51 807.382,1118.35L847.801,1154.25C838.669,1174.33 827.61,1193.49 814.781,1211.44L763.485,1194.38C750.171,1211 735.068,1226.1 718.449,1239.42L735.505,1290.71C717.552,1303.54 698.399,1314.6 678.312,1323.74L642.417,1283.32C622.576,1291.05 601.945,1296.58 580.895,1299.8L570.019,1352.75C548.057,1354.89 525.94,1354.89 503.978,1352.75L493.101,1299.8C472.051,1296.58 451.42,1291.05 431.58,1283.32L395.684,1323.74C375.598,1314.6 356.444,1303.54 338.492,1290.71L355.548,1239.42C338.929,1226.1 323.826,1211 310.511,1194.38L259.215,1211.44C246.386,1193.49 235.328,1174.33 226.195,1154.25L266.614,1118.35C258.879,1098.51 253.351,1077.88 250.13,1056.83L197.178,1045.95C195.044,1023.99 195.044,1001.87 197.178,979.911L250.13,969.035C253.351,947.985 258.879,927.354 266.614,907.513L226.195,871.618C235.328,851.531 246.386,832.378 259.215,814.425L310.511,831.481C323.826,814.862 338.929,799.759 355.548,786.445L338.492,735.149C356.444,722.32 375.598,711.261 395.684,702.128L431.58,742.548C451.42,734.812 472.051,729.284 493.101,726.063L503.978,673.111C525.94,670.977 548.057,670.977 570.019,673.111ZM536.998,944.648C574.685,944.648 605.282,975.245 605.282,1012.93C605.282,1050.62 574.685,1081.22 536.998,1081.22C499.311,1081.22 468.714,1050.62 468.714,1012.93C468.714,975.245 499.311,944.648 536.998,944.648Z"
- )
-}
-/* ktlint-enable max-line-length */
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathIteratorTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathIteratorTest.kt
index b1de40a..80ec203 100644
--- a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathIteratorTest.kt
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathIteratorTest.kt
@@ -28,16 +28,6 @@
import org.junit.Test
import org.junit.runner.RunWith
-fun assertPointsEquals(p1: Offset, p2: Offset) {
- assertEquals(p1.x, p2.x, 1e-6f)
- assertEquals(p1.y, p2.y, 1e-6f)
-}
-
-fun assertPointsEquals(p1: FloatArray, offset: Int, p2: Offset) {
- assertEquals(p1[0 + offset * 2], p2.x, 1e-6f)
- assertEquals(p1[1 + offset * 2], p2.y, 1e-6f)
-}
-
@SmallTest
@RunWith(AndroidJUnit4::class)
class PathIteratorTest {
@@ -130,44 +120,44 @@
val p = segment.points
when (type) {
PathSegment.Type.Move -> {
- assertPointsEquals(points, 0, Offset(p[0], p[1]))
- assertPointsEquals(points2, 4, Offset(p[0], p[1]))
+ assertPointEquals(Offset(p[0], p[1]), points, 0)
+ assertPointEquals(Offset(p[0], p[1]), points2, 4)
}
PathSegment.Type.Line -> {
- assertPointsEquals(points, 0, Offset(p[0], p[1]))
- assertPointsEquals(points, 1, Offset(p[2], p[3]))
- assertPointsEquals(points2, 4, Offset(p[0], p[1]))
- assertPointsEquals(points2, 5, Offset(p[2], p[3]))
+ assertPointEquals(Offset(p[0], p[1]), points, 0)
+ assertPointEquals(Offset(p[2], p[3]), points, 1)
+ assertPointEquals(Offset(p[0], p[1]), points2, 4)
+ assertPointEquals(Offset(p[2], p[3]), points2, 5)
}
PathSegment.Type.Quadratic -> {
- assertPointsEquals(points, 0, Offset(p[0], p[1]))
- assertPointsEquals(points, 1, Offset(p[2], p[3]))
- assertPointsEquals(points, 2, Offset(p[4], p[5]))
- assertPointsEquals(points2, 4, Offset(p[0], p[1]))
- assertPointsEquals(points2, 5, Offset(p[2], p[3]))
- assertPointsEquals(points2, 6, Offset(p[4], p[5]))
+ assertPointEquals(Offset(p[0], p[1]), points, 0)
+ assertPointEquals(Offset(p[2], p[3]), points, 1)
+ assertPointEquals(Offset(p[4], p[5]), points, 2)
+ assertPointEquals(Offset(p[0], p[1]), points2, 4)
+ assertPointEquals(Offset(p[2], p[3]), points2, 5)
+ assertPointEquals(Offset(p[4], p[5]), points2, 6)
}
PathSegment.Type.Conic -> {
- assertPointsEquals(points, 0, Offset(p[0], p[1]))
- assertPointsEquals(points, 1, Offset(p[2], p[3]))
- assertPointsEquals(points, 2, Offset(p[4], p[5]))
+ assertPointEquals(Offset(p[0], p[1]), points, 0)
+ assertPointEquals(Offset(p[2], p[3]), points, 1)
+ assertPointEquals(Offset(p[4], p[5]), points, 2)
assertEquals(points[6], segment.weight)
- assertPointsEquals(points2, 4, Offset(p[0], p[1]))
- assertPointsEquals(points2, 5, Offset(p[2], p[3]))
- assertPointsEquals(points2, 6, Offset(p[4], p[5]))
+ assertPointEquals(Offset(p[0], p[1]), points2, 4)
+ assertPointEquals(Offset(p[2], p[3]), points2, 5)
+ assertPointEquals(Offset(p[4], p[5]), points2, 6)
assertEquals(points2[14], segment.weight)
}
PathSegment.Type.Cubic -> {
- assertPointsEquals(points, 0, Offset(p[0], p[1]))
- assertPointsEquals(points, 1, Offset(p[2], p[3]))
- assertPointsEquals(points, 2, Offset(p[4], p[5]))
- assertPointsEquals(points, 3, Offset(p[6], p[7]))
+ assertPointEquals(Offset(p[0], p[1]), points, 0)
+ assertPointEquals(Offset(p[2], p[3]), points, 1)
+ assertPointEquals(Offset(p[4], p[5]), points, 2)
+ assertPointEquals(Offset(p[6], p[7]), points, 3)
- assertPointsEquals(points2, 4, Offset(p[0], p[1]))
- assertPointsEquals(points2, 5, Offset(p[2], p[3]))
- assertPointsEquals(points2, 6, Offset(p[4], p[5]))
- assertPointsEquals(points2, 7, Offset(p[6], p[7]))
+ assertPointEquals(Offset(p[0], p[1]), points2, 4)
+ assertPointEquals(Offset(p[2], p[3]), points2, 5)
+ assertPointEquals(Offset(p[4], p[5]), points2, 6)
+ assertPointEquals(Offset(p[6], p[7]), points2, 7)
}
PathSegment.Type.Close -> { }
PathSegment.Type.Done -> { }
@@ -219,7 +209,7 @@
assertEquals(PathSegment.Type.Move, segment.type)
val points = segment.points
assertEquals(2, points.size)
- assertPointsEquals(Offset(10.0f, 12.0f), Offset(points[0], points[1]))
+ assertPointEquals(Offset(points[0], points[1]), Offset(10.0f, 12.0f))
assertEquals(0.0f, segment.weight)
}
@@ -239,8 +229,8 @@
assertEquals(PathSegment.Type.Line, segment.type)
val points = segment.points
assertEquals(4, points.size)
- assertPointsEquals(Offset(4.0f, 6.0f), Offset(points[0], points[1]))
- assertPointsEquals(Offset(10.0f, 12.0f), Offset(points[2], points[3]))
+ assertPointEquals(Offset(points[0], points[1]), Offset(4.0f, 6.0f))
+ assertPointEquals(Offset(points[2], points[3]), Offset(10.0f, 12.0f))
assertEquals(0.0f, segment.weight)
}
@@ -260,9 +250,9 @@
assertEquals(PathSegment.Type.Quadratic, segment.type)
val points = segment.points
assertEquals(6, points.size)
- assertPointsEquals(Offset(4.0f, 6.0f), Offset(points[0], points[1]))
- assertPointsEquals(Offset(10.0f, 12.0f), Offset(points[2], points[3]))
- assertPointsEquals(Offset(20.0f, 24.0f), Offset(points[4], points[5]))
+ assertPointEquals(Offset(points[0], points[1]), Offset(4.0f, 6.0f))
+ assertPointEquals(Offset(points[2], points[3]), Offset(10.0f, 12.0f))
+ assertPointEquals(Offset(points[4], points[5]), Offset(20.0f, 24.0f))
assertEquals(0.0f, segment.weight)
}
@@ -282,10 +272,10 @@
assertEquals(PathSegment.Type.Cubic, segment.type)
val points = segment.points
assertEquals(8, points.size)
- assertPointsEquals(Offset(4.0f, 6.0f), Offset(points[0], points[1]))
- assertPointsEquals(Offset(10.0f, 12.0f), Offset(points[2], points[3]))
- assertPointsEquals(Offset(20.0f, 24.0f), Offset(points[4], points[5]))
- assertPointsEquals(Offset(30.0f, 36.0f), Offset(points[6], points[7]))
+ assertPointEquals(Offset(points[0], points[1]), Offset(4.0f, 6.0f))
+ assertPointEquals(Offset(points[2], points[3]), Offset(10.0f, 12.0f))
+ assertPointEquals(Offset(points[4], points[5]), Offset(20.0f, 24.0f))
+ assertPointEquals(Offset(points[6], points[7]), Offset(30.0f, 36.0f))
assertEquals(0.0f, segment.weight)
}
@@ -306,9 +296,9 @@
val points = segment.points
assertEquals(6, points.size)
- assertPointsEquals(Offset(12.0f, 18.0f), Offset(points[0], points[1]))
- assertPointsEquals(Offset(12.0f, 24.0f), Offset(points[2], points[3]))
- assertPointsEquals(Offset(18.0f, 24.0f), Offset(points[4], points[5]))
+ assertPointEquals(Offset(points[0], points[1]), Offset(12.0f, 18.0f))
+ assertPointEquals(Offset(points[2], points[3]), Offset(12.0f, 24.0f))
+ assertPointEquals(Offset(points[4], points[5]), Offset(18.0f, 24.0f))
assertEquals(0.70710677f, segment.weight)
}
}
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathReversionTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathReversionTest.kt
new file mode 100644
index 0000000..dda072e
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathReversionTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import android.graphics.Color
+import android.graphics.Paint
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.roundToIntRect
+import androidx.core.graphics.applyCanvas
+import androidx.core.graphics.createBitmap
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class PathReversionTest {
+ @Test
+ fun emptyPath() {
+ val path = Path().reverse()
+ assertTrue(path.isEmpty)
+ }
+
+ @Test
+ fun singleMove() {
+ val path = Path().apply {
+ moveTo(10.0f, 10.0f)
+ }.reverse()
+
+ val iterator = path.iterator()
+ assertTrue(iterator.hasNext())
+
+ val segment = iterator.next()
+ assertEquals(PathSegment.Type.Move, segment.type)
+ assertPointEquals(Offset(10.0f, 10.0f), segment.points, 0)
+ }
+
+ @Test
+ fun lineTo() {
+ val path = Path().apply {
+ moveTo(10.0f, 10.0f)
+ lineTo(20.0f, 20.0f)
+ }.reverse()
+
+ val iterator = path.iterator()
+ assertTrue(iterator.hasNext())
+
+ assertEquals(2, iterator.calculateSize(false))
+
+ var segment = iterator.next()
+ assertEquals(PathSegment.Type.Move, segment.type)
+ assertPointEquals(Offset(20.0f, 20.0f), segment.points, 0)
+
+ segment = iterator.next()
+ assertEquals(PathSegment.Type.Line, segment.type)
+ assertPointEquals(Offset(10.0f, 10.0f), segment.points, 1)
+ }
+
+ @Test
+ fun close() {
+ val path = Path().apply {
+ moveTo(10.0f, 10.0f)
+ lineTo(20.0f, 20.0f)
+ lineTo(10.0f, 30.0f)
+ close()
+ }.reverse()
+
+ val iterator = path.iterator()
+ assertTrue(iterator.hasNext())
+
+ assertEquals(4, iterator.calculateSize(false))
+
+ var segment = iterator.next()
+ assertEquals(PathSegment.Type.Move, segment.type)
+ assertPointEquals(Offset(10.0f, 30.0f), segment.points, 0)
+
+ segment = iterator.next()
+ assertEquals(PathSegment.Type.Line, segment.type)
+ assertPointEquals(Offset(20.0f, 20.0f), segment.points, 1)
+
+ segment = iterator.next()
+ assertEquals(PathSegment.Type.Line, segment.type)
+ assertPointEquals(Offset(10.0f, 10.0f), segment.points, 1)
+
+ segment = iterator.next()
+ assertEquals(PathSegment.Type.Close, segment.type)
+ }
+
+ @Test
+ fun multipleContours() {
+ val path = Path().apply {
+ moveTo(10.0f, 10.0f)
+ lineTo(20.0f, 20.0f)
+ lineTo(10.0f, 30.0f)
+ close()
+
+ moveTo(50.0f, 50.0f)
+ lineTo(70.0f, 70.0f)
+ lineTo(50.0f, 90.0f)
+ }.reverse()
+
+ val iterator = path.iterator()
+ assertTrue(iterator.hasNext())
+
+ assertEquals(7, iterator.calculateSize(false))
+
+ var segment = iterator.next()
+ assertEquals(PathSegment.Type.Move, segment.type)
+ assertPointEquals(Offset(50.0f, 90.0f), segment.points, 0)
+
+ segment = iterator.next()
+ assertEquals(PathSegment.Type.Line, segment.type)
+ assertPointEquals(Offset(70.0f, 70.0f), segment.points, 1)
+
+ segment = iterator.next()
+ assertEquals(PathSegment.Type.Line, segment.type)
+ assertPointEquals(Offset(50.0f, 50.0f), segment.points, 1)
+
+ segment = iterator.next()
+ assertEquals(PathSegment.Type.Move, segment.type)
+ assertPointEquals(Offset(10.0f, 30.0f), segment.points, 0)
+
+ segment = iterator.next()
+ assertEquals(PathSegment.Type.Line, segment.type)
+ assertPointEquals(Offset(20.0f, 20.0f), segment.points, 1)
+
+ segment = iterator.next()
+ assertEquals(PathSegment.Type.Line, segment.type)
+ assertPointEquals(Offset(10.0f, 10.0f), segment.points, 1)
+
+ segment = iterator.next()
+ assertEquals(PathSegment.Type.Close, segment.type)
+ }
+
+ @Test
+ fun directionIsReversed() {
+ val path = createSvgPath(SvgShape.Heart)
+ assertEquals(Path.Direction.Clockwise, path.computeDirection())
+
+ val reversed = path.reverse()
+ assertEquals(Path.Direction.CounterClockwise, reversed.computeDirection())
+ }
+
+ @Test
+ fun contoursDirectionIsReversed() {
+ val path = createSvgPath(SvgShape.Cubics) // this shape has a cutout
+ val reversed = path.reverse()
+
+ assertTrue(
+ path.divide().zip(reversed.divide().reversed()) { a, b ->
+ a.computeDirection() != b.computeDirection()
+ }.all { it }
+ )
+ }
+
+ @Test
+ fun pixelComparison() {
+ val paint = Paint().apply {
+ style = Paint.Style.FILL
+ color = Color.RED
+ isAntiAlias = false
+ }
+
+ for (svg in listOf(
+ SvgShape.Cubics,
+ SvgShape.Quads,
+ SvgShape.Heart,
+ SvgShape.Lines
+ )) {
+ val path = createSvgPath(svg)
+ val reversed = path.reverse()
+
+ val bounds1 = path.getBounds().roundToIntRect()
+ val bounds2 = reversed.getBounds().roundToIntRect()
+ assertEquals(bounds1, bounds2)
+
+ val reference = createBitmap(bounds1.width, bounds1.height).applyCanvas {
+ drawPath(path.asAndroidPath(), paint)
+ }
+ val result = createBitmap(bounds2.width, bounds2.height).applyCanvas {
+ drawPath(reversed.asAndroidPath(), paint)
+ }
+
+ compareBitmaps(reference, result, 0)
+ }
+ }
+}
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathTestUtilities.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathTestUtilities.kt
new file mode 100644
index 0000000..cb2152f
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/PathTestUtilities.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import android.graphics.Bitmap
+import androidx.compose.ui.geometry.Offset
+import androidx.core.graphics.component1
+import androidx.core.graphics.component2
+import androidx.core.graphics.component3
+import androidx.core.graphics.component4
+import kotlin.math.abs
+import kotlin.test.assertEquals
+import kotlin.test.fail
+import org.junit.Assert.assertArrayEquals
+
+/**
+ * Creates a path from the specified shape, using the EvenOdd fill type to match SVG.
+ * The returned path has its origin set to 0,0 for convenience.
+ */
+internal fun createSvgPath(svgShape: SvgShape) = Path().apply {
+ addSvg(svgShape.pathData)
+ val bounds = getBounds()
+ translate(Offset(-bounds.left, -bounds.top))
+ fillType = PathFillType.EvenOdd
+}
+
+/* ktlint-disable max-line-length */
+internal enum class SvgShape(val pathData: String) {
+ Cubics(
+ "M958.729,822.904L958.729,1086.67C958.729,1159.45 899.635,1218.55 826.848,1218.55L563.086,1218.55L355.844,1444.63L450.045,1218.55L337.004,1218.55C264.217,1218.55 205.123,1159.45 205.123,1086.67L205.123,822.904C205.123,750.117 264.217,691.023 337.004,691.023L826.848,691.023C899.635,691.023 958.729,750.117 958.729,822.904ZM581.925,850.888C544.745,781.585 470.384,781.585 433.204,816.236C396.023,850.888 396.023,920.191 433.204,989.493C459.23,1041.47 526.155,1093.45 581.925,1128.1C637.696,1093.45 704.621,1041.47 730.647,989.493C767.829,920.191 767.829,850.888 730.647,816.236C693.467,781.585 619.106,781.585 581.925,850.888Z"
+ ),
+ Quads(
+ "M 664.72,242.306 L 664.72,423.585 Q 649.1189999999999,514.2250000000001 574.08,514.225 L 392.801,514.225 L 250.367,669.607 L 315.11,514.225 L 237.419,514.225 Q 146.779,498.62249999999995 146.779,423.585 L 146.779,242.306 Q 162.38,151.666 237.419,151.666 L 574.08,151.666 Q 664.72,167.267 664.72,242.306 M 375.55,220.052 Q 355.947,196.49699999999999 334.296,208.998 Q 310.741,228.5995 323.242,250.252 Q 294.48900000000003,239.65350000000004 281.988,261.306 Q 271.3895000000001,290.05899999999997 293.042,302.56 Q 262.842,307.758 262.842,332.76 Q 268.0400000000001,362.9599999999999 293.042,362.96 Q 269.48699999999997,382.5615 281.988,404.214 Q 301.58950000000004,427.769 323.242,415.268 Q 312.6435,444.02099999999996 334.296,456.522 Q 363.0490000000001,467.1189999999999 375.55,445.468 Q 380.74850000000004,475.66700000000003 405.749,475.667 Q 435.94899999999996,470.46849999999995 435.949,445.468 Q 455.552,469.02150000000006 477.203,456.522 Q 500.75800000000004,436.919 488.257,415.268 Q 517.01,425.865 529.511,404.214 Q 540.1095,375.461 518.457,362.96 Q 548.6569999999999,357.76200000000006 548.657,332.76 Q 543.4590000000001,302.56000000000006 518.457,302.56 Q 542.0120000000001,282.95849999999996 529.511,261.306 Q 509.9095,237.751 488.257,250.252 Q 498.8555,221.499 477.203,208.998 Q 448.45000000000005,198.3995 435.949,220.052 Q 430.751,189.85200000000003 405.749,189.852 Q 375.55000000000007,195.04999999999995 375.55,220.052Z"
+ ),
+ Lines(
+ "M741.323,698.969L825.045,956.64L1095.98,956.64L876.789,1115.89L960.511,1373.56L741.323,1214.31L522.134,1373.56L605.857,1115.89L386.668,956.64L657.6,956.64L741.323,698.969Z"
+ ),
+ FillTypes(
+ "M570.019,673.111L580.895,726.063C601.945,729.284 622.576,734.812 642.417,742.548L678.312,702.128C698.399,711.261 717.552,722.32 735.505,735.149L718.449,786.445C735.068,799.759 750.171,814.862 763.485,831.481L814.781,814.425C827.61,832.378 838.669,851.531 847.801,871.618L807.382,907.513C815.118,927.354 820.646,947.985 823.867,969.035L876.819,979.911C878.953,1001.87 878.953,1023.99 876.819,1045.95L823.867,1056.83C820.646,1077.88 815.118,1098.51 807.382,1118.35L847.801,1154.25C838.669,1174.33 827.61,1193.49 814.781,1211.44L763.485,1194.38C750.171,1211 735.068,1226.1 718.449,1239.42L735.505,1290.71C717.552,1303.54 698.399,1314.6 678.312,1323.74L642.417,1283.32C622.576,1291.05 601.945,1296.58 580.895,1299.8L570.019,1352.75C548.057,1354.89 525.94,1354.89 503.978,1352.75L493.101,1299.8C472.051,1296.58 451.42,1291.05 431.58,1283.32L395.684,1323.74C375.598,1314.6 356.444,1303.54 338.492,1290.71L355.548,1239.42C338.929,1226.1 323.826,1211 310.511,1194.38L259.215,1211.44C246.386,1193.49 235.328,1174.33 226.195,1154.25L266.614,1118.35C258.879,1098.51 253.351,1077.88 250.13,1056.83L197.178,1045.95C195.044,1023.99 195.044,1001.87 197.178,979.911L250.13,969.035C253.351,947.985 258.879,927.354 266.614,907.513L226.195,871.618C235.328,851.531 246.386,832.378 259.215,814.425L310.511,831.481C323.826,814.862 338.929,799.759 355.548,786.445L338.492,735.149C356.444,722.32 375.598,711.261 395.684,702.128L431.58,742.548C451.42,734.812 472.051,729.284 493.101,726.063L503.978,673.111C525.94,670.977 548.057,670.977 570.019,673.111ZM536.998,944.648C574.685,944.648 605.282,975.245 605.282,1012.93C605.282,1050.62 574.685,1081.22 536.998,1081.22C499.311,1081.22 468.714,1050.62 468.714,1012.93C468.714,975.245 499.311,944.648 536.998,944.648Z"
+ ),
+ Heart(
+ "M648.094,783.362C721.785,623.533 869.168,623.533 942.86,703.447C1016.55,783.362 1016.55,943.19 942.86,1103.02C891.275,1222.89 758.631,1342.76 648.094,1422.68C537.557,1342.76 404.913,1222.89 353.329,1103.02C279.638,943.19 279.638,783.362 353.329,703.447C427.021,623.533 574.403,623.533 648.094,783.362Z"
+ ),
+}
+/* ktlint-enable max-line-length */
+
+private fun valueCountForType(type: PathSegment.Type) = when (type) {
+ PathSegment.Type.Move -> 2
+ PathSegment.Type.Line -> 4
+ PathSegment.Type.Quadratic -> 6
+ PathSegment.Type.Conic -> 8
+ PathSegment.Type.Cubic -> 8
+ PathSegment.Type.Close -> 0
+ PathSegment.Type.Done -> 0
+}
+
+internal fun assertPathEquals(
+ expected: Path,
+ actual: Path,
+ points1: FloatArray = FloatArray(8),
+ points2: FloatArray = FloatArray(8)
+) {
+ val iterator1 = expected.iterator()
+ val iterator2 = actual.iterator()
+
+ assertEquals(iterator1.calculateSize(), iterator2.calculateSize())
+
+ while (iterator1.hasNext() && iterator2.hasNext()) {
+ val type1 = iterator1.next(points1)
+ val type2 = iterator2.next(points2)
+
+ points1.fill(0.0f, valueCountForType(type1))
+ points2.fill(0.0f, valueCountForType(type2))
+
+ assertEquals(type1, type2)
+
+ assertArrayEquals(points1, points2, 1e-7f)
+ }
+}
+
+internal fun assertPointEquals(expected: Offset, actual: Offset) {
+ assertEquals(expected.x, actual.x, 1e-6f)
+ assertEquals(expected.y, actual.y, 1e-6f)
+}
+
+internal fun assertPointEquals(expected: Offset, actual: FloatArray, offset: Int) {
+ assertEquals(expected.x, actual[0 + offset * 2], 1e-6f)
+ assertEquals(expected.y, actual[1 + offset * 2], 1e-6f)
+}
+
+/**
+ * Compares two bitmaps and fails the test if they are different. The two bitmaps
+ * are considered different if more than [errorCount] pixels differ by more than
+ * [threshold] in any of the RGB channels.
+ */
+internal fun compareBitmaps(bitmap1: Bitmap, bitmap2: Bitmap, errorCount: Int, threshold: Int = 1) {
+ assertEquals(bitmap1.width, bitmap2.width)
+ assertEquals(bitmap1.height, bitmap2.height)
+
+ val p1 = IntArray(bitmap1.width * bitmap1.height)
+ bitmap1.getPixels(p1, 0, bitmap1.width, 0, 0, bitmap1.width, bitmap1.height)
+
+ val p2 = IntArray(bitmap2.width * bitmap2.height)
+ bitmap2.getPixels(p2, 0, bitmap2.width, 0, 0, bitmap2.width, bitmap2.height)
+
+ var count = 0
+ for (y in 0 until bitmap1.height) {
+ for (x in 0 until bitmap1.width) {
+ val index = y * bitmap1.width + x
+
+ val (r1, g1, b1, _) = p1[index]
+ val (r2, g2, b2, _) = p2[index]
+
+ if (abs(r1 - r2) > threshold ||
+ abs(g1 - g2) > threshold ||
+ abs(b1 - b2) > threshold
+ ) {
+ count++
+ }
+ }
+ }
+
+ if (count > errorCount) {
+ fail("More than $errorCount different pixels ($count) with error threshold=$threshold")
+ }
+}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathGeometry.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathGeometry.kt
index a6e510f..a02386a 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathGeometry.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathGeometry.kt
@@ -17,18 +17,25 @@
package androidx.compose.ui.graphics
/**
- * Computes this [Path]'s direction (or winding, or orientation), which can be either
- * [Path.Direction.Clockwise] or [Path.Direction.CounterClockwise].
+ * Computes this [Path]'s direction (or winding, or orientation), which can
+ * be either [Path.Direction.Clockwise] or [Path.Direction.CounterClockwise].
*
- * If the path is made of multiple contours (the path contains multiple "move" commands),
- * the direction returned by this property is the direction of the first contour.
+ * If the path is made of multiple contours (the path contains multiple "move"
+ * commands), the direction returned by this property is the direction of the
+ * first contour.
*
- * If the path is empty (contains no lines/curves), the direction is [Path.Direction.Clockwise].
+ * If the path is empty (contains no lines/curves), the direction is
+ * [Path.Direction.Clockwise].
*
- * If the path has no area (single straight line), the direction is [Path.Direction.Clockwise].
+ * If the path has no area (single straight line), the direction is
+ * [Path.Direction.Clockwise].
*
- * Calling this property does not cache the result, the direction is computed every
- * time the property is accessed.
+ * Calling this property does not cache the result, the direction is computed
+ * Calling this method does not cache the result, the direction is computed
+ * every time the method is called.
+ *
+ * If you need to query the direction of individual contours, you should
+ * [divide][Path.divide] the path first.
*/
fun Path.computeDirection(): Path.Direction {
var first = true
@@ -249,3 +256,106 @@
return contours
}
+
+/**
+ * Reverses the segments of this path into the specified [destination], turning
+ * a clockwise path into a counter-clockwise path and vice-versa. Each contour
+ * in the path is reversed independently, and the contours appear in the
+ * [destination] in reverse order.
+ *
+ * This method preserves the general structure of this path as much as possible:
+ *
+ * - Lines become lines
+ * - Quadratic Bézier curves become quadratic Bézier curves
+ * - Cubic Bézier curves become cubic Bézier curves
+ * - Close and move commands remain close and move commands
+ * - Conic segments become quadratic Bézier curves
+ *
+ * @return A [Path] containing the reverse of this [Path]. The returned path is
+ * either a newly allocated [Path] if the [destination] parameter was left
+ * unspecified, or the [destination] parameter.
+ */
+fun Path.reverse(destination: Path = Path()): Path {
+ val iterator = iterator()
+
+ val count = iterator.calculateSize(false)
+ val segments = ArrayList<PathSegment.Type>(count)
+ val data = ArrayList<FloatArray>(count)
+
+ // Gather all the segments going forward so we can iterate backward
+ // to construct the new reversed path. It would be unnecessary if
+ // PathIterator supported reverse iteration.
+ var points = FloatArray(8)
+ var type = iterator.next(points)
+ while (type != PathSegment.Type.Done) {
+ segments.add(type)
+ if (type != PathSegment.Type.Close) {
+ data.add(points.copyOf(floatCountForType(type)))
+ }
+ type = iterator.next(points)
+ }
+
+ var insertMove = true
+ var insertClose = false
+ var dataIndex = data.size
+
+ for (i in segments.size - 1 downTo 0) {
+ if (insertMove) {
+ dataIndex--
+ points = data[dataIndex]
+ val offset = points.lastIndex
+ destination.moveTo(points[offset - 1], points[offset])
+ insertMove = false
+ } else {
+ points = data[dataIndex]
+ }
+
+ when (segments[i]) {
+ PathSegment.Type.Move -> {
+ if (insertClose) {
+ destination.close()
+ insertClose = false
+ }
+ insertMove = true
+ }
+ PathSegment.Type.Line -> {
+ destination.lineTo(points[0], points[1])
+ dataIndex--
+ }
+ PathSegment.Type.Quadratic -> {
+ destination.quadraticTo(
+ points[2], points[3],
+ points[0], points[1]
+ )
+ dataIndex--
+ }
+ PathSegment.Type.Conic -> { } // won't happen, we convert to quadratics
+ PathSegment.Type.Cubic -> {
+ destination.cubicTo(
+ points[4], points[5],
+ points[2], points[3],
+ points[0], points[1]
+ )
+ dataIndex--
+ }
+ PathSegment.Type.Close -> insertClose = true
+ PathSegment.Type.Done -> { } // won't happen, we filtered it out in the previous loop
+ }
+ }
+
+ if (insertClose) {
+ destination.close()
+ }
+
+ return destination
+}
+
+private fun floatCountForType(type: PathSegment.Type) = when (type) {
+ PathSegment.Type.Move -> 2
+ PathSegment.Type.Line -> 4
+ PathSegment.Type.Quadratic -> 6
+ PathSegment.Type.Conic -> 8 // won't happen
+ PathSegment.Type.Cubic -> 8
+ PathSegment.Type.Close -> 0
+ PathSegment.Type.Done -> 0
+}
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index a7b8e52..8622541 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -141,7 +141,7 @@
androidx {
name = "Compose Testing for JUnit4"
- type = LibraryType.PUBLISHED_TEST_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY
inceptionYear = "2020"
description = "Compose testing integration with JUnit4"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-test-manifest/build.gradle b/compose/ui/ui-test-manifest/build.gradle
index 806bc76..c06cf1f 100644
--- a/compose/ui/ui-test-manifest/build.gradle
+++ b/compose/ui/ui-test-manifest/build.gradle
@@ -37,7 +37,7 @@
androidx {
name = "Compose Testing manifest dependency"
- type = LibraryType.PUBLISHED_TEST_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY
inceptionYear = "2021"
description = "Compose testing library that should be added as a debugImplementation dependency to add properties to the debug manifest necessary for testing an application"
metalavaK2UastEnabled = true
diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle
index 68eb8e6..64289e4 100644
--- a/compose/ui/ui-test/build.gradle
+++ b/compose/ui/ui-test/build.gradle
@@ -42,7 +42,7 @@
api(project(":compose:ui:ui"))
api(project(":compose:ui:ui-text"))
api(project(":compose:ui:ui-unit"))
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
api(libs.kotlinStdlib)
implementation(project(":compose:ui:ui-util"))
@@ -162,7 +162,7 @@
androidx {
name = "Compose Testing"
- type = LibraryType.PUBLISHED_TEST_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_TEST_LIBRARY
inceptionYear = "2019"
description = "Compose testing library"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/SemanticsNodeInteraction.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/SemanticsNodeInteraction.kt
index 2265a7f..f500d6a 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/SemanticsNodeInteraction.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/SemanticsNodeInteraction.kt
@@ -81,7 +81,7 @@
* @throws AssertionError if 0 or multiple nodes found.
*/
fun fetchSemanticsNode(errorMessageOnFail: String? = null): SemanticsNode {
- return fetchOneOrDie(errorMessageOnFail)
+ return fetchOneOrThrow(errorMessageOnFail)
}
/**
@@ -122,7 +122,7 @@
* @throws [AssertionError] if the assert fails.
*/
fun assertExists(errorMessageOnFail: String? = null): SemanticsNodeInteraction {
- fetchOneOrDie(errorMessageOnFail)
+ fetchOneOrThrow(errorMessageOnFail)
return this
}
@@ -135,7 +135,7 @@
* @throws [AssertionError] if the assert fails.
*/
fun assertIsDeactivated(errorMessageOnFail: String? = null) {
- val node = fetchOneOrDie(skipDeactivatedNodes = false)
+ val node = fetchOneOrThrow(skipDeactivatedNodes = false)
if (!node.layoutInfo.isDeactivated) {
throw AssertionError(
buildGeneralErrorMessage(
@@ -147,7 +147,7 @@
}
}
- private fun fetchOneOrDie(
+ private fun fetchOneOrThrow(
errorMessageOnFail: String? = null,
skipDeactivatedNodes: Boolean = true
): SemanticsNode {
diff --git a/compose/ui/ui-text-google-fonts/build.gradle b/compose/ui/ui-text-google-fonts/build.gradle
index 1c199e3..cda3047 100644
--- a/compose/ui/ui-text-google-fonts/build.gradle
+++ b/compose/ui/ui-text-google-fonts/build.gradle
@@ -48,7 +48,7 @@
androidx {
name = "Compose Google Fonts integration"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2022"
description = "Compose Downloadable Fonts integration for Google Fonts"
metalavaK2UastEnabled = true
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index 2c23f05..5266e91 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -1058,17 +1058,21 @@
@androidx.compose.runtime.Immutable public final class ImeOptions {
ctor @Deprecated public ImeOptions(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
- ctor public ImeOptions(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
+ ctor @Deprecated public ImeOptions(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
+ ctor public ImeOptions(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
method @Deprecated public androidx.compose.ui.text.input.ImeOptions copy(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
- method public androidx.compose.ui.text.input.ImeOptions copy(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
+ method @Deprecated public androidx.compose.ui.text.input.ImeOptions copy(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
+ method public androidx.compose.ui.text.input.ImeOptions copy(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
method public boolean getAutoCorrect();
method public int getCapitalization();
+ method public androidx.compose.ui.text.intl.LocaleList? getHintLocales();
method public int getImeAction();
method public int getKeyboardType();
method public androidx.compose.ui.text.input.PlatformImeOptions? getPlatformImeOptions();
method public boolean getSingleLine();
property public final boolean autoCorrect;
property public final int capitalization;
+ property public final androidx.compose.ui.text.intl.LocaleList? hintLocales;
property public final int imeAction;
property public final int keyboardType;
property public final androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions;
@@ -1270,10 +1274,12 @@
@androidx.compose.runtime.Immutable public final class Locale {
ctor public Locale(String languageTag);
method public String getLanguage();
+ method public java.util.Locale getPlatformLocale();
method public String getRegion();
method public String getScript();
method public String toLanguageTag();
property public final String language;
+ property public final java.util.Locale platformLocale;
property public final String region;
property public final String script;
field public static final androidx.compose.ui.text.intl.Locale.Companion Companion;
@@ -1465,8 +1471,6 @@
}
public enum ResolvedTextDirection {
- method public static androidx.compose.ui.text.style.ResolvedTextDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.text.style.ResolvedTextDirection[] values();
enum_constant public static final androidx.compose.ui.text.style.ResolvedTextDirection Ltr;
enum_constant public static final androidx.compose.ui.text.style.ResolvedTextDirection Rtl;
}
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index d12b6e4..07aa3d7 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -1058,17 +1058,21 @@
@androidx.compose.runtime.Immutable public final class ImeOptions {
ctor @Deprecated public ImeOptions(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
- ctor public ImeOptions(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
+ ctor @Deprecated public ImeOptions(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
+ ctor public ImeOptions(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
method @Deprecated public androidx.compose.ui.text.input.ImeOptions copy(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
- method public androidx.compose.ui.text.input.ImeOptions copy(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
+ method @Deprecated public androidx.compose.ui.text.input.ImeOptions copy(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
+ method public androidx.compose.ui.text.input.ImeOptions copy(optional boolean singleLine, optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
method public boolean getAutoCorrect();
method public int getCapitalization();
+ method public androidx.compose.ui.text.intl.LocaleList? getHintLocales();
method public int getImeAction();
method public int getKeyboardType();
method public androidx.compose.ui.text.input.PlatformImeOptions? getPlatformImeOptions();
method public boolean getSingleLine();
property public final boolean autoCorrect;
property public final int capitalization;
+ property public final androidx.compose.ui.text.intl.LocaleList? hintLocales;
property public final int imeAction;
property public final int keyboardType;
property public final androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions;
@@ -1270,10 +1274,12 @@
@androidx.compose.runtime.Immutable public final class Locale {
ctor public Locale(String languageTag);
method public String getLanguage();
+ method public java.util.Locale getPlatformLocale();
method public String getRegion();
method public String getScript();
method public String toLanguageTag();
property public final String language;
+ property public final java.util.Locale platformLocale;
property public final String region;
property public final String script;
field public static final androidx.compose.ui.text.intl.Locale.Companion Companion;
@@ -1476,8 +1482,6 @@
}
public enum ResolvedTextDirection {
- method public static androidx.compose.ui.text.style.ResolvedTextDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.text.style.ResolvedTextDirection[] values();
enum_constant public static final androidx.compose.ui.text.style.ResolvedTextDirection Ltr;
enum_constant public static final androidx.compose.ui.text.style.ResolvedTextDirection Rtl;
}
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index 6641935..8904291 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
+
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
@@ -47,7 +47,7 @@
api(project(":compose:ui:ui-unit"))
// when updating the runtime version please also update the runtime-saveable version
- implementation("androidx.compose.runtime:runtime:1.6.0")
+ implementation(project(":compose:runtime:runtime"))
implementation("androidx.compose.runtime:runtime-saveable:1.6.0")
implementation(project(":compose:ui:ui-util"))
@@ -145,7 +145,7 @@
androidx {
name = "Compose UI Text"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2019"
description = "Compose Text primitives and utilities"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/intl/LocaleListTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/intl/LocaleListTest.kt
index 9dd17dd..52a36c8 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/intl/LocaleListTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/intl/LocaleListTest.kt
@@ -65,9 +65,7 @@
for (javaLocale in javaLocales) {
java.util.Locale.setDefault(javaLocale)
- assertThat(LocaleList.current.first()).isEqualTo(
- Locale(AndroidLocale(javaLocale))
- )
+ assertThat(LocaleList.current.first()).isEqualTo(Locale(javaLocale))
}
}
}
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/intl/LocaleTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/intl/LocaleTest.kt
index 5a95254..f7c3476 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/intl/LocaleTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/intl/LocaleTest.kt
@@ -59,4 +59,13 @@
assertThat(Locale("sr-Latn-SR")).isEqualTo(Locale("sr-Latn-SR"))
assertThat(Locale("sr-Latn-SR")).isNotEqualTo(Locale("sr-Cyrl-SR"))
}
+
+ @Test
+ fun platformLocale_sharesTheAttributes() {
+ val locale = Locale("en-US")
+ val platformLocale = locale.platformLocale
+ assertThat(locale.language).isEqualTo(platformLocale.language)
+ assertThat(locale.script).isEqualTo(platformLocale.script)
+ assertThat(locale.region).isEqualTo(platformLocale.country)
+ }
}
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
index 295491d..06fb55f 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
@@ -202,8 +202,11 @@
// Brush is not fully realized on text until layout is complete and size information
// is known. Brush can now be applied to the overall textpaint and all the spans.
textPaint.setBrush(style.brush, Size(width, height), style.alpha)
- layout.getShaderBrushSpans().forEach { shaderBrushSpan ->
- shaderBrushSpan.size = Size(width, height)
+ val shaderBrushSpans = layout.getShaderBrushSpans()
+ if (shaderBrushSpans != null) {
+ for (shaderBrushSpan in shaderBrushSpans) {
+ shaderBrushSpan.size = Size(width, height)
+ }
}
}
@@ -378,9 +381,13 @@
)
}
- private val wordBoundary: WordBoundary by lazy(LazyThreadSafetyMode.NONE) {
- WordBoundary(textLocale, layout.text)
- }
+ private var backingWordBoundary: WordBoundary? = null
+ private val wordBoundary: WordBoundary
+ get() {
+ val finalWordBoundary = backingWordBoundary
+ if (finalWordBoundary != null) return finalWordBoundary
+ return WordBoundary(textLocale, layout.text).also { backingWordBoundary = it }
+ }
override fun getWordBoundary(offset: Int): TextRange {
return TextRange(wordBoundary.getWordStart(offset), wordBoundary.getWordEnd(offset))
@@ -437,15 +444,19 @@
ResolvedTextDirection.Ltr
}
- private fun TextLayout.getShaderBrushSpans(): Array<ShaderBrushSpan> {
- if (text !is Spanned) return emptyArray()
+ private fun TextLayout.getShaderBrushSpans(): Array<ShaderBrushSpan>? {
+ if (text !is Spanned) return null
+ if (!(text as Spanned).hasSpan(ShaderBrushSpan::class.java)) return null
val brushSpans = (text as Spanned).getSpans(
0, text.length, ShaderBrushSpan::class.java
)
- if (brushSpans.isEmpty()) return emptyArray()
return brushSpans
}
+ private fun Spanned.hasSpan(clazz: Class<*>): Boolean {
+ return nextSpanTransition(-1, length, clazz) != length
+ }
+
override fun paint(
canvas: Canvas,
color: Color,
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/intl/AndroidLocaleDelegate.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/intl/AndroidLocaleDelegate.android.kt
index 602c01f..04495e3 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/intl/AndroidLocaleDelegate.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/intl/AndroidLocaleDelegate.android.kt
@@ -22,31 +22,15 @@
import java.util.Locale as JavaLocale
/**
- * An Android implementation of Locale object
- */
-internal class AndroidLocale(val javaLocale: JavaLocale) : PlatformLocale {
- override val language: String
- get() = javaLocale.language
-
- override val script: String
- get() = javaLocale.script
-
- override val region: String
- get() = javaLocale.country
-
- override fun toLanguageTag(): String = javaLocale.toLanguageTag()
-}
-
-/**
* An Android implementation of LocaleDelegate object for API 23
*/
internal class AndroidLocaleDelegateAPI23 : PlatformLocaleDelegate {
override val current: LocaleList
- get() = LocaleList(listOf(Locale(AndroidLocale(JavaLocale.getDefault()))))
+ get() = LocaleList(listOf(Locale(JavaLocale.getDefault())))
override fun parseLanguageTag(languageTag: String): PlatformLocale =
- AndroidLocale(JavaLocale.forLanguageTag(languageTag))
+ JavaLocale.forLanguageTag(languageTag)
}
/**
@@ -69,7 +53,7 @@
// this is faster than adding to an empty mutableList
val localeList = LocaleList(
List(platformLocaleList.size()) { position ->
- Locale(AndroidLocale(platformLocaleList[position]))
+ Locale(platformLocaleList[position])
}
)
// cache the platform result and compose result
@@ -80,5 +64,5 @@
}
override fun parseLanguageTag(languageTag: String): PlatformLocale =
- AndroidLocale(JavaLocale.forLanguageTag(languageTag))
+ JavaLocale.forLanguageTag(languageTag)
}
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
index f3f75a6..3868c83 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
@@ -35,7 +35,6 @@
import androidx.compose.ui.text.font.FontSynthesis
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.font.TypefaceResult
-import androidx.compose.ui.text.intl.AndroidLocale
import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.ui.text.platform.extensions.applySpanStyle
import androidx.compose.ui.text.platform.extensions.setTextMotion
@@ -161,7 +160,7 @@
TextDirection.Rtl -> LayoutCompat.TEXT_DIRECTION_RTL
TextDirection.Content, TextDirection.Unspecified -> {
val currentLocale = localeList?.let {
- (it[0].platformLocale as AndroidLocale).javaLocale
+ it[0].platformLocale
} ?: Locale.getDefault()
when (TextUtilsCompat.getLayoutDirectionFromLocale(currentLocale)) {
View.LAYOUT_DIRECTION_LTR -> LayoutCompat.TEXT_DIRECTION_FIRST_STRONG_LTR
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidStringDelegate.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidStringDelegate.android.kt
index bbaab79..14a88d7 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidStringDelegate.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidStringDelegate.android.kt
@@ -17,7 +17,6 @@
package androidx.compose.ui.text.platform
import androidx.compose.ui.text.PlatformStringDelegate
-import androidx.compose.ui.text.intl.AndroidLocale
import androidx.compose.ui.text.intl.PlatformLocale
/**
@@ -25,20 +24,20 @@
*/
internal class AndroidStringDelegate : PlatformStringDelegate {
override fun toUpperCase(string: String, locale: PlatformLocale): String =
- string.uppercase((locale as AndroidLocale).javaLocale)
+ string.uppercase(locale)
override fun toLowerCase(string: String, locale: PlatformLocale): String =
- string.lowercase((locale as AndroidLocale).javaLocale)
+ string.lowercase(locale)
override fun capitalize(string: String, locale: PlatformLocale): String =
string.replaceFirstChar {
if (it.isLowerCase())
- it.titlecase((locale as AndroidLocale).javaLocale)
+ it.titlecase(locale)
else it.toString()
}
override fun decapitalize(string: String, locale: PlatformLocale): String =
- string.replaceFirstChar { it.lowercase((locale as AndroidLocale).javaLocale) }
+ string.replaceFirstChar { it.lowercase(locale) }
}
internal actual fun ActualStringDelegate(): PlatformStringDelegate =
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt
index 708f622..1b8d827 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt
@@ -32,6 +32,7 @@
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.asComposePaint
+import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.DrawStyle
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
@@ -49,10 +50,18 @@
}
// A wrapper to use Compose Paint APIs on this TextPaint
- private val composePaint: Paint = this.asComposePaint()
+ private var backingComposePaint: Paint? = null
+ private val composePaint: Paint
+ get() {
+ val finalBackingComposePaint = backingComposePaint
+ if (finalBackingComposePaint != null) return finalBackingComposePaint
+ return this.asComposePaint().also { backingComposePaint = it }
+ }
private var textDecoration: TextDecoration = TextDecoration.None
+ private var backingBlendMode: BlendMode = DrawScope.DefaultBlendMode
+
@VisibleForTesting
internal var shadow: Shadow = Shadow.None
@@ -94,7 +103,7 @@
fun setColor(color: Color) {
if (color.isSpecified) {
- composePaint.color = color
+ this.color = color.toArgb()
clearShader()
}
}
@@ -142,7 +151,9 @@
// Stroke properties such as strokeWidth, strokeMiter are not re-set because
// Fill style should make those properties no-op. Next time the style is set
// as Stroke, stroke properties get re-set as well.
- composePaint.style = PaintingStyle.Fill
+
+ // avoid unnecessarily allocating a composePaint object in hot path.
+ this.style = Style.FILL
}
is Stroke -> {
composePaint.style = PaintingStyle.Stroke
@@ -158,7 +169,15 @@
// BlendMode is only available to DrawScope.drawText.
// not intended to be used by TextStyle/SpanStyle.
- var blendMode: BlendMode by composePaint::blendMode
+ var blendMode: BlendMode
+ get() {
+ return backingBlendMode
+ }
+ set(value) {
+ if (value == backingBlendMode) return
+ composePaint.blendMode = value
+ backingBlendMode = value
+ }
/**
* Clears all shader related cache parameters and native shader property.
@@ -167,7 +186,7 @@
this.shaderState = null
this.brush = null
this.brushSize = null
- composePaint.shader = null
+ this.shader = null
}
}
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/LocaleExtensions.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/LocaleExtensions.android.kt
index 16ebd74..a47c62e 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/LocaleExtensions.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/LocaleExtensions.android.kt
@@ -19,13 +19,9 @@
import android.text.style.LocaleSpan
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
-import androidx.compose.ui.text.intl.AndroidLocale
-import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.ui.text.platform.AndroidTextPaint
-internal fun Locale.toJavaLocale(): java.util.Locale = (platformLocale as AndroidLocale).javaLocale
-
/**
* This class is here to ensure that the classes that use this API will get verified and can be
* AOT compiled. It is expected that this class will soft-fail verification, but the classes
@@ -37,14 +33,14 @@
@DoNotInline
fun localeSpan(localeList: LocaleList): Any =
LocaleSpan(
- android.os.LocaleList(*localeList.map { it.toJavaLocale() }.toTypedArray())
+ android.os.LocaleList(*localeList.map { it.platformLocale }.toTypedArray())
)
@RequiresApi(24)
@DoNotInline
fun setTextLocales(textPaint: AndroidTextPaint, localeList: LocaleList) {
textPaint.textLocales = android.os.LocaleList(
- *localeList.map { it.toJavaLocale() }.toTypedArray()
+ *localeList.map { it.platformLocale }.toTypedArray()
)
}
}
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
index c7d3794..028c137 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
@@ -461,7 +461,7 @@
LocaleListHelperMethods.localeSpan(it)
} else {
val locale = if (it.isEmpty()) Locale.current else it[0]
- LocaleSpan(locale.toJavaLocale())
+ LocaleSpan(locale.platformLocale)
},
start,
end
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/TextPaintExtensions.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/TextPaintExtensions.android.kt
index bc686ff..b231989 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/TextPaintExtensions.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/TextPaintExtensions.android.kt
@@ -77,7 +77,7 @@
} else {
style.localeList[0]
}
- textLocale = locale.toJavaLocale()
+ textLocale = locale.platformLocale
}
}
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
index 8587bda..21bfb54 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
@@ -50,6 +50,7 @@
// TODO(nona): Introduce TextUnit.Original for representing "do not change the original result".
// Need to distinguish from Inherit.
private val DefaultColor = Color.Black
+private val DefaultColorForegroundStyle = TextForegroundStyle.from(DefaultColor)
/**
* Styling configuration for a text span. This configuration only allows character level styling,
@@ -829,9 +830,7 @@
}
internal fun resolveSpanStyleDefaults(style: SpanStyle) = SpanStyle(
- textForegroundStyle = style.textForegroundStyle.takeOrElse {
- TextForegroundStyle.from(DefaultColor)
- },
+ textForegroundStyle = style.textForegroundStyle.takeOrElse { DefaultColorForegroundStyle },
fontSize = if (style.fontSize.isUnspecified) DefaultFontSize else style.fontSize,
fontWeight = style.fontWeight ?: FontWeight.Normal,
fontStyle = style.fontStyle ?: FontStyle.Normal,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/ImeOptions.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/ImeOptions.kt
index 8a45b44..56971b8 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/ImeOptions.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/ImeOptions.kt
@@ -17,6 +17,7 @@
package androidx.compose.ui.text.input
import androidx.compose.runtime.Immutable
+import androidx.compose.ui.text.intl.LocaleList
/**
* The IME configuration options for [TextInputService]. It is not guaranteed if IME
@@ -39,6 +40,10 @@
* on the keyboard. For example, search icon may be shown if [ImeAction.Search] is specified.
* When [singleLine] is false, the IME might show return key rather than the action requested here.
* @param platformImeOptions defines the platform specific IME options.
+ * @param hintLocales List of the languages that the user is supposed to switch to no matter what
+ * input method subtype is currently used. This special "hint" can be used mainly for, but not
+ * limited to, multilingual users who want IMEs to switch language based on editor's context.
+ * Pass null to express the intention that a specific hint should not be set.
*/
@Immutable
class ImeOptions(
@@ -47,7 +52,9 @@
val autoCorrect: Boolean = true,
val keyboardType: KeyboardType = KeyboardType.Text,
val imeAction: ImeAction = ImeAction.Default,
- val platformImeOptions: PlatformImeOptions? = null
+ val platformImeOptions: PlatformImeOptions? = null,
+ @get:Suppress("NullableCollection")
+ val hintLocales: LocaleList? = null
) {
companion object {
/**
@@ -57,6 +64,27 @@
}
@Deprecated(
+ "Please use the new constructor that takes optional hintLocales parameter.",
+ level = DeprecationLevel.HIDDEN
+ )
+ constructor(
+ singleLine: Boolean = false,
+ capitalization: KeyboardCapitalization = KeyboardCapitalization.None,
+ autoCorrect: Boolean = true,
+ keyboardType: KeyboardType = KeyboardType.Text,
+ imeAction: ImeAction = ImeAction.Default,
+ platformImeOptions: PlatformImeOptions? = null
+ ) : this(
+ singleLine = singleLine,
+ capitalization = capitalization,
+ autoCorrect = autoCorrect,
+ keyboardType = keyboardType,
+ imeAction = imeAction,
+ platformImeOptions = platformImeOptions,
+ hintLocales = null
+ )
+
+ @Deprecated(
"Please use the new constructor that takes optional platformImeOptions parameter.",
level = DeprecationLevel.HIDDEN
)
@@ -81,6 +109,30 @@
autoCorrect: Boolean = this.autoCorrect,
keyboardType: KeyboardType = this.keyboardType,
imeAction: ImeAction = this.imeAction,
+ platformImeOptions: PlatformImeOptions? = this.platformImeOptions,
+ hintLocales: LocaleList? = this.hintLocales
+ ): ImeOptions {
+ return ImeOptions(
+ singleLine = singleLine,
+ capitalization = capitalization,
+ autoCorrect = autoCorrect,
+ keyboardType = keyboardType,
+ imeAction = imeAction,
+ platformImeOptions = platformImeOptions,
+ hintLocales = hintLocales
+ )
+ }
+
+ @Deprecated(
+ "Please use the new copy function that takes optional hintLocales parameter.",
+ level = DeprecationLevel.HIDDEN
+ )
+ fun copy(
+ singleLine: Boolean = this.singleLine,
+ capitalization: KeyboardCapitalization = this.capitalization,
+ autoCorrect: Boolean = this.autoCorrect,
+ keyboardType: KeyboardType = this.keyboardType,
+ imeAction: ImeAction = this.imeAction,
platformImeOptions: PlatformImeOptions? = this.platformImeOptions
): ImeOptions {
return ImeOptions(
@@ -89,7 +141,8 @@
autoCorrect = autoCorrect,
keyboardType = keyboardType,
imeAction = imeAction,
- platformImeOptions = platformImeOptions
+ platformImeOptions = platformImeOptions,
+ hintLocales = this.hintLocales
)
}
@@ -110,7 +163,8 @@
autoCorrect = autoCorrect,
keyboardType = keyboardType,
imeAction = imeAction,
- platformImeOptions = this.platformImeOptions
+ platformImeOptions = this.platformImeOptions,
+ hintLocales = this.hintLocales
)
}
@@ -124,6 +178,7 @@
if (keyboardType != other.keyboardType) return false
if (imeAction != other.imeAction) return false
if (platformImeOptions != other.platformImeOptions) return false
+ if (hintLocales != other.hintLocales) return false
return true
}
@@ -135,12 +190,13 @@
result = 31 * result + keyboardType.hashCode()
result = 31 * result + imeAction.hashCode()
result = 31 * result + platformImeOptions.hashCode()
+ result = 31 * result + hintLocales.hashCode()
return result
}
override fun toString(): String {
return "ImeOptions(singleLine=$singleLine, capitalization=$capitalization, " +
"autoCorrect=$autoCorrect, keyboardType=$keyboardType, imeAction=$imeAction, " +
- "platformImeOptions=$platformImeOptions)"
+ "platformImeOptions=$platformImeOptions, hintLocales=$hintLocales)"
}
}
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/Locale.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/Locale.kt
index 797adea..7919976 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/Locale.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/Locale.kt
@@ -27,11 +27,13 @@
* locale-sensitive operation— the number should be formatted according to the customs and
* conventions of the user's native country, region, or culture.
*
+ * @param platformLocale Platform specific Locale object that provides basic functionality.
+ *
* @see TextStyle
* @see SpanStyle
*/
@Immutable
-class Locale internal constructor(internal val platformLocale: PlatformLocale) {
+class Locale internal constructor(val platformLocale: PlatformLocale) {
companion object {
/**
* Returns a [Locale] object which represents current locale
@@ -68,7 +70,7 @@
*
* @return A IETF BCP47 compliant language tag.
*/
- fun toLanguageTag(): String = platformLocale.toLanguageTag()
+ fun toLanguageTag(): String = platformLocale.getLanguageTag()
override fun equals(other: Any?): Boolean {
if (other == null) return false
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/LocaleList.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/LocaleList.kt
index 987e8ef..05b64ea 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/LocaleList.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/LocaleList.kt
@@ -28,7 +28,7 @@
* @see SpanStyle
*/
@Immutable
-class LocaleList constructor(val localeList: List<Locale>) : Collection<Locale> {
+class LocaleList(val localeList: List<Locale>) : Collection<Locale> {
companion object {
/**
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.kt
index de5c613..31b368f 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.kt
@@ -17,30 +17,29 @@
package androidx.compose.ui.text.intl
/**
- * Interface for providing platform dependent locale object.
+ * Class for providing Locale functionality from the current platform.
*/
-internal interface PlatformLocale {
+expect class PlatformLocale
- /**
- * Implementation must give ISO 639 compliant language code.
- */
- val language: String
+/**
+ * Implementation must give ISO 639 compliant language code.
+ */
+internal expect val PlatformLocale.language: String
- /**
- * Implementation must give ISO 15924 compliant 4-letter script code.
- */
- val script: String
+/**
+ * Implementation must give ISO 15924 compliant 4-letter script code.
+ */
+internal expect val PlatformLocale.script: String
- /**
- * Implementation must give ISO 3166 compliant region code.
- */
- val region: String
+/**
+ * Implementation must give ISO 3166 compliant region code.
+ */
+internal expect val PlatformLocale.region: String
- /**
- * Implementation must return IETF BCP47 compliant language tag representation of this Locale.
- */
- fun toLanguageTag(): String
-}
+/**
+ * Implementation must return IETF BCP47 compliant language tag representation of this Locale.
+ */
+internal expect fun PlatformLocale.getLanguageTag(): String
/**
* Interface for providing platform dependent locale non-instance helper functions.
diff --git a/compose/ui/ui-text/src/desktopMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.desktop.kt b/compose/ui/ui-text/src/desktopMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.desktop.kt
index 69ddebd..5470465 100644
--- a/compose/ui/ui-text/src/desktopMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.desktop.kt
+++ b/compose/ui/ui-text/src/desktopMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.desktop.kt
@@ -18,27 +18,10 @@
import java.util.Locale
-internal class DesktopLocale(val locale: Locale) : PlatformLocale {
- override val language: String
- get() = locale.language
-
- override val script: String
- get() = locale.script
-
- override val region: String
- get() = locale.country
-
- override fun toLanguageTag(): String = locale.toLanguageTag()
-}
-
internal actual fun createPlatformLocaleDelegate() = object : PlatformLocaleDelegate {
override val current: LocaleList
- get() = LocaleList(listOf(Locale(DesktopLocale(Locale.getDefault()))))
+ get() = LocaleList(listOf(Locale(Locale.getDefault())))
override fun parseLanguageTag(languageTag: String): PlatformLocale =
- DesktopLocale(
- Locale.forLanguageTag(
- languageTag
- )
- )
+ Locale.forLanguageTag(languageTag)
}
diff --git a/compose/ui/ui-text/src/desktopMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.desktop.kt b/compose/ui/ui-text/src/desktopMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.desktop.kt
index 8a42784..a610728 100644
--- a/compose/ui/ui-text/src/desktopMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.desktop.kt
+++ b/compose/ui/ui-text/src/desktopMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.desktop.kt
@@ -17,7 +17,6 @@
package androidx.compose.ui.text.platform
import androidx.compose.ui.text.PlatformStringDelegate
-import androidx.compose.ui.text.intl.DesktopLocale
import androidx.compose.ui.text.intl.PlatformLocale
/**
@@ -25,21 +24,21 @@
*/
internal class DesktopStringDelegate : PlatformStringDelegate {
override fun toUpperCase(string: String, locale: PlatformLocale): String =
- string.uppercase((locale as DesktopLocale).locale)
+ string.uppercase(locale)
override fun toLowerCase(string: String, locale: PlatformLocale): String =
- string.lowercase((locale as DesktopLocale).locale)
+ string.lowercase(locale)
override fun capitalize(string: String, locale: PlatformLocale): String =
string.replaceFirstChar {
if (it.isLowerCase())
- it.titlecase((locale as DesktopLocale).locale)
+ it.titlecase(locale)
else
it.toString()
}
override fun decapitalize(string: String, locale: PlatformLocale): String =
- string.replaceFirstChar { it.lowercase((locale as DesktopLocale).locale) }
+ string.replaceFirstChar { it.lowercase(locale) }
}
internal actual fun ActualStringDelegate(): PlatformStringDelegate =
diff --git a/compose/ui/ui-text/src/jvmMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.jvm.kt b/compose/ui/ui-text/src/jvmMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.jvm.kt
new file mode 100644
index 0000000..816fa80
--- /dev/null
+++ b/compose/ui/ui-text/src/jvmMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.jvm.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+
+package androidx.compose.ui.text.intl
+
+actual typealias PlatformLocale = java.util.Locale
+
+internal actual val PlatformLocale.language: String
+ get() = language
+
+internal actual val PlatformLocale.script: String
+ get() = script
+
+internal actual val PlatformLocale.region: String
+ get() = country
+
+internal actual fun PlatformLocale.getLanguageTag(): String = toLanguageTag()
diff --git a/compose/ui/ui-tooling-data/build.gradle b/compose/ui/ui-tooling-data/build.gradle
index ad21576..f813317 100644
--- a/compose/ui/ui-tooling-data/build.gradle
+++ b/compose/ui/ui-tooling-data/build.gradle
@@ -41,7 +41,7 @@
dependencies {
implementation(libs.kotlinStdlib)
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
api(project(":compose:ui:ui"))
}
}
@@ -123,7 +123,7 @@
androidx {
name = "Compose Tooling Data"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2021"
description = "Compose tooling library data. This library provides data about compose" +
" for different tooling purposes."
diff --git a/compose/ui/ui-tooling-preview/build.gradle b/compose/ui/ui-tooling-preview/build.gradle
index e48247e..b600b85 100644
--- a/compose/ui/ui-tooling-preview/build.gradle
+++ b/compose/ui/ui-tooling-preview/build.gradle
@@ -40,7 +40,7 @@
commonMain {
dependencies {
implementation(libs.kotlinStdlibCommon)
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
}
}
@@ -105,7 +105,7 @@
androidx {
name = "Compose UI Preview Tooling"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2021"
description = "Compose tooling library API. This library provides the API required to declare" +
" @Preview composables in user apps."
diff --git a/compose/ui/ui-tooling/api/current.ignore b/compose/ui/ui-tooling/api/current.ignore
deleted file mode 100644
index 4f3e328..0000000
--- a/compose/ui/ui-tooling/api/current.ignore
+++ /dev/null
@@ -1,15 +0,0 @@
-// Baseline format: 1.0
-BecameUnchecked: androidx.compose.ui.tooling.ComposableInvoker:
- Removed class androidx.compose.ui.tooling.ComposableInvoker from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.tooling.ComposableInvoker#INSTANCE:
- Removed field androidx.compose.ui.tooling.ComposableInvoker.INSTANCE from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.tooling.ComposableInvoker#invokeComposable(String, String, androidx.compose.runtime.Composer, java.lang.Object...):
- Removed method androidx.compose.ui.tooling.ComposableInvoker.invokeComposable(String,String,androidx.compose.runtime.Composer,java.lang.Object...) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.tooling.ComposableInvoker#invokeComposable(String, String, androidx.compose.runtime.Composer, java.lang.Object...) parameter #0:
- Removed parameter className in androidx.compose.ui.tooling.ComposableInvoker.invokeComposable(String className, String methodName, androidx.compose.runtime.Composer composer, java.lang.Object... args) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.tooling.ComposableInvoker#invokeComposable(String, String, androidx.compose.runtime.Composer, java.lang.Object...) parameter #1:
- Removed parameter methodName in androidx.compose.ui.tooling.ComposableInvoker.invokeComposable(String className, String methodName, androidx.compose.runtime.Composer composer, java.lang.Object... args) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.tooling.ComposableInvoker#invokeComposable(String, String, androidx.compose.runtime.Composer, java.lang.Object...) parameter #2:
- Removed parameter composer in androidx.compose.ui.tooling.ComposableInvoker.invokeComposable(String className, String methodName, androidx.compose.runtime.Composer composer, java.lang.Object... args) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.tooling.ComposableInvoker#invokeComposable(String, String, androidx.compose.runtime.Composer, java.lang.Object...) parameter #3:
- Removed parameter args in androidx.compose.ui.tooling.ComposableInvoker.invokeComposable(String className, String methodName, androidx.compose.runtime.Composer composer, java.lang.Object... args) from compatibility checked API surface
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index a8680ca..e0e50d7 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -40,7 +40,7 @@
commonMain {
dependencies {
implementation(libs.kotlinStdlibCommon)
- api("androidx.compose.runtime:runtime:1.6.0")
+ api(project(":compose:runtime:runtime"))
api(project(":compose:ui:ui-tooling-preview"))
api(project(":compose:ui:ui"))
api(project(":compose:ui:ui-tooling-data"))
@@ -133,7 +133,7 @@
androidx {
name = "Compose Tooling"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2019"
description = "Compose tooling library. This library exposes information to our tools for better IDE support."
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt
index f63f600..4b63f95 100644
--- a/compose/ui/ui-unit/api/current.txt
+++ b/compose/ui/ui-unit/api/current.txt
@@ -328,8 +328,6 @@
}
public enum LayoutDirection {
- method public static androidx.compose.ui.unit.LayoutDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.unit.LayoutDirection[] values();
enum_constant public static final androidx.compose.ui.unit.LayoutDirection Ltr;
enum_constant public static final androidx.compose.ui.unit.LayoutDirection Rtl;
}
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt
index a02b992..2f0dd28 100644
--- a/compose/ui/ui-unit/api/restricted_current.txt
+++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -328,8 +328,6 @@
}
public enum LayoutDirection {
- method public static androidx.compose.ui.unit.LayoutDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.unit.LayoutDirection[] values();
enum_constant public static final androidx.compose.ui.unit.LayoutDirection Ltr;
enum_constant public static final androidx.compose.ui.unit.LayoutDirection Rtl;
}
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index 7338576..ba792904 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -43,7 +43,7 @@
api("androidx.annotation:annotation:1.1.0")
api(project(":compose:ui:ui-geometry"))
implementation("androidx.collection:collection:1.4.0")
- implementation("androidx.compose.runtime:runtime:1.6.0")
+ implementation(project(":compose:runtime:runtime"))
implementation(project(":compose:ui:ui-util"))
}
}
@@ -110,7 +110,7 @@
androidx {
name = "Compose Unit"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose classes for simple units"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-util/build.gradle b/compose/ui/ui-util/build.gradle
index b71de8d..1639263 100644
--- a/compose/ui/ui-util/build.gradle
+++ b/compose/ui/ui-util/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
+
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
@@ -95,7 +95,7 @@
androidx {
name = "Compose Util"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Internal Compose utilities used by other modules"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui-viewbinding/build.gradle b/compose/ui/ui-viewbinding/build.gradle
index bbb555f..550edf7 100644
--- a/compose/ui/ui-viewbinding/build.gradle
+++ b/compose/ui/ui-viewbinding/build.gradle
@@ -50,7 +50,7 @@
androidx {
name = "Compose ViewBinding"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose integration with ViewBinding"
legacyDisableKotlinStrictApiMode = true
diff --git a/compose/ui/ui/api/current.ignore b/compose/ui/ui/api/current.ignore
index 3c0ee01..6d98574 100644
--- a/compose/ui/ui/api/current.ignore
+++ b/compose/ui/ui/api/current.ignore
@@ -1,8 +1,6 @@
// Baseline format: 1.0
RemovedClass: androidx.compose.ui.draganddrop.DragAndDropKt:
Removed class androidx.compose.ui.draganddrop.DragAndDropKt
-RemovedClass: androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat_androidKt:
- Removed class androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat_androidKt
RemovedMethod: androidx.compose.ui.input.pointer.PointerEventKt#indexOfFirstPressed(int):
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 1193a31..a9eff8a 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -209,8 +209,6 @@
}
@SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public enum AutofillType {
- method public static androidx.compose.ui.autofill.AutofillType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.autofill.AutofillType[] values();
enum_constant public static final androidx.compose.ui.autofill.AutofillType AddressAuxiliaryDetails;
enum_constant public static final androidx.compose.ui.autofill.AutofillType AddressCountry;
enum_constant public static final androidx.compose.ui.autofill.AutofillType AddressLocality;
@@ -550,14 +548,14 @@
}
public static final class FocusRequester.Companion {
- method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory createRefs();
+ method public androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory createRefs();
method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.focus.FocusRequester getCancel();
method public androidx.compose.ui.focus.FocusRequester getDefault();
property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public final androidx.compose.ui.focus.FocusRequester Cancel;
property public final androidx.compose.ui.focus.FocusRequester Default;
}
- @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final class FocusRequester.Companion.FocusRequesterFactory {
+ public static final class FocusRequester.Companion.FocusRequesterFactory {
method public operator androidx.compose.ui.focus.FocusRequester component1();
method public operator androidx.compose.ui.focus.FocusRequester component10();
method public operator androidx.compose.ui.focus.FocusRequester component11();
@@ -1769,8 +1767,6 @@
}
public enum PointerEventPass {
- method public static androidx.compose.ui.input.pointer.PointerEventPass valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.input.pointer.PointerEventPass[] values();
enum_constant public static final androidx.compose.ui.input.pointer.PointerEventPass Final;
enum_constant public static final androidx.compose.ui.input.pointer.PointerEventPass Initial;
enum_constant public static final androidx.compose.ui.input.pointer.PointerEventPass Main;
@@ -2582,6 +2578,10 @@
method public static androidx.compose.ui.unit.LayoutDirection requireLayoutDirection(androidx.compose.ui.node.DelegatableNode);
}
+ public final class DelegatableNode_androidKt {
+ method public static android.view.View requireView(androidx.compose.ui.node.DelegatableNode);
+ }
+
public abstract class DelegatingNode extends androidx.compose.ui.Modifier.Node {
ctor public DelegatingNode();
method protected final <T extends androidx.compose.ui.node.DelegatableNode> T delegate(T delegatableNode);
@@ -2710,8 +2710,6 @@
}
public enum TraversableNode.Companion.TraverseDescendantsAction {
- method public static androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction[] values();
enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction CancelTraversal;
enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction ContinueTraversal;
enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction SkipSubtreeAndContinueTraversal;
@@ -2997,8 +2995,6 @@
}
public enum TextToolbarStatus {
- method public static androidx.compose.ui.platform.TextToolbarStatus valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.platform.TextToolbarStatus[] values();
enum_constant public static final androidx.compose.ui.platform.TextToolbarStatus Hidden;
enum_constant public static final androidx.compose.ui.platform.TextToolbarStatus Shown;
}
@@ -3275,6 +3271,7 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getCutText();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getDismiss();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getExpand();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.util.List<java.lang.Float>,java.lang.Boolean>>> getGetScrollViewportLength();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.util.List<androidx.compose.ui.text.TextLayoutResult>,java.lang.Boolean>>> getGetTextLayoutResult();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<androidx.compose.ui.text.AnnotatedString,java.lang.Boolean>>> getInsertTextAtCursor();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getOnClick();
@@ -3301,6 +3298,7 @@
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> CutText;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> Dismiss;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> Expand;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.util.List<java.lang.Float>,java.lang.Boolean>>> GetScrollViewportLength;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.util.List<androidx.compose.ui.text.TextLayoutResult>,java.lang.Boolean>>> GetTextLayoutResult;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<androidx.compose.ui.text.AnnotatedString,java.lang.Boolean>>> InsertTextAtCursor;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> OnClick;
@@ -3507,6 +3505,7 @@
method public static String getPaneTitle(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.semantics.ProgressBarRangeInfo getProgressBarRangeInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static int getRole(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static void getScrollViewportLength(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.util.List<java.lang.Float>,java.lang.Boolean> action);
method public static boolean getSelected(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getStateDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getTestTag(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -3596,8 +3595,6 @@
package androidx.compose.ui.state {
public enum ToggleableState {
- method public static androidx.compose.ui.state.ToggleableState valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.state.ToggleableState[] values();
enum_constant public static final androidx.compose.ui.state.ToggleableState Indeterminate;
enum_constant public static final androidx.compose.ui.state.ToggleableState Off;
enum_constant public static final androidx.compose.ui.state.ToggleableState On;
@@ -3687,8 +3684,6 @@
}
public enum SecureFlagPolicy {
- method public static androidx.compose.ui.window.SecureFlagPolicy valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.window.SecureFlagPolicy[] values();
enum_constant public static final androidx.compose.ui.window.SecureFlagPolicy Inherit;
enum_constant public static final androidx.compose.ui.window.SecureFlagPolicy SecureOff;
enum_constant public static final androidx.compose.ui.window.SecureFlagPolicy SecureOn;
diff --git a/compose/ui/ui/api/restricted_current.ignore b/compose/ui/ui/api/restricted_current.ignore
index 3c0ee01..6d98574 100644
--- a/compose/ui/ui/api/restricted_current.ignore
+++ b/compose/ui/ui/api/restricted_current.ignore
@@ -1,8 +1,6 @@
// Baseline format: 1.0
RemovedClass: androidx.compose.ui.draganddrop.DragAndDropKt:
Removed class androidx.compose.ui.draganddrop.DragAndDropKt
-RemovedClass: androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat_androidKt:
- Removed class androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat_androidKt
RemovedMethod: androidx.compose.ui.input.pointer.PointerEventKt#indexOfFirstPressed(int):
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 2f25cc8..d95c62c 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -209,8 +209,6 @@
}
@SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public enum AutofillType {
- method public static androidx.compose.ui.autofill.AutofillType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.autofill.AutofillType[] values();
enum_constant public static final androidx.compose.ui.autofill.AutofillType AddressAuxiliaryDetails;
enum_constant public static final androidx.compose.ui.autofill.AutofillType AddressCountry;
enum_constant public static final androidx.compose.ui.autofill.AutofillType AddressLocality;
@@ -550,14 +548,14 @@
}
public static final class FocusRequester.Companion {
- method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory createRefs();
+ method public androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory createRefs();
method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.focus.FocusRequester getCancel();
method public androidx.compose.ui.focus.FocusRequester getDefault();
property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public final androidx.compose.ui.focus.FocusRequester Cancel;
property public final androidx.compose.ui.focus.FocusRequester Default;
}
- @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final class FocusRequester.Companion.FocusRequesterFactory {
+ public static final class FocusRequester.Companion.FocusRequesterFactory {
method public operator androidx.compose.ui.focus.FocusRequester component1();
method public operator androidx.compose.ui.focus.FocusRequester component10();
method public operator androidx.compose.ui.focus.FocusRequester component11();
@@ -1769,8 +1767,6 @@
}
public enum PointerEventPass {
- method public static androidx.compose.ui.input.pointer.PointerEventPass valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.input.pointer.PointerEventPass[] values();
enum_constant public static final androidx.compose.ui.input.pointer.PointerEventPass Final;
enum_constant public static final androidx.compose.ui.input.pointer.PointerEventPass Initial;
enum_constant public static final androidx.compose.ui.input.pointer.PointerEventPass Main;
@@ -2635,6 +2631,10 @@
method public static androidx.compose.ui.unit.LayoutDirection requireLayoutDirection(androidx.compose.ui.node.DelegatableNode);
}
+ public final class DelegatableNode_androidKt {
+ method public static android.view.View requireView(androidx.compose.ui.node.DelegatableNode);
+ }
+
public abstract class DelegatingNode extends androidx.compose.ui.Modifier.Node {
ctor public DelegatingNode();
method protected final <T extends androidx.compose.ui.node.DelegatableNode> T delegate(T delegatableNode);
@@ -2763,8 +2763,6 @@
}
public enum TraversableNode.Companion.TraverseDescendantsAction {
- method public static androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction[] values();
enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction CancelTraversal;
enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction ContinueTraversal;
enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction SkipSubtreeAndContinueTraversal;
@@ -3055,8 +3053,6 @@
}
public enum TextToolbarStatus {
- method public static androidx.compose.ui.platform.TextToolbarStatus valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.platform.TextToolbarStatus[] values();
enum_constant public static final androidx.compose.ui.platform.TextToolbarStatus Hidden;
enum_constant public static final androidx.compose.ui.platform.TextToolbarStatus Shown;
}
@@ -3335,6 +3331,7 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getCutText();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getDismiss();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getExpand();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.util.List<java.lang.Float>,java.lang.Boolean>>> getGetScrollViewportLength();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.util.List<androidx.compose.ui.text.TextLayoutResult>,java.lang.Boolean>>> getGetTextLayoutResult();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<androidx.compose.ui.text.AnnotatedString,java.lang.Boolean>>> getInsertTextAtCursor();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getOnClick();
@@ -3361,6 +3358,7 @@
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> CutText;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> Dismiss;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> Expand;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.util.List<java.lang.Float>,java.lang.Boolean>>> GetScrollViewportLength;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.util.List<androidx.compose.ui.text.TextLayoutResult>,java.lang.Boolean>>> GetTextLayoutResult;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<androidx.compose.ui.text.AnnotatedString,java.lang.Boolean>>> InsertTextAtCursor;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> OnClick;
@@ -3567,6 +3565,7 @@
method public static String getPaneTitle(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.semantics.ProgressBarRangeInfo getProgressBarRangeInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static int getRole(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static void getScrollViewportLength(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.util.List<java.lang.Float>,java.lang.Boolean> action);
method public static boolean getSelected(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getStateDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getTestTag(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -3656,8 +3655,6 @@
package androidx.compose.ui.state {
public enum ToggleableState {
- method public static androidx.compose.ui.state.ToggleableState valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.state.ToggleableState[] values();
enum_constant public static final androidx.compose.ui.state.ToggleableState Indeterminate;
enum_constant public static final androidx.compose.ui.state.ToggleableState Off;
enum_constant public static final androidx.compose.ui.state.ToggleableState On;
@@ -3747,8 +3744,6 @@
}
public enum SecureFlagPolicy {
- method public static androidx.compose.ui.window.SecureFlagPolicy valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.ui.window.SecureFlagPolicy[] values();
enum_constant public static final androidx.compose.ui.window.SecureFlagPolicy Inherit;
enum_constant public static final androidx.compose.ui.window.SecureFlagPolicy SecureOff;
enum_constant public static final androidx.compose.ui.window.SecureFlagPolicy SecureOn;
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/utils.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/utils.kt
index f3e935c..1f955e2 100644
--- a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/utils.kt
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/utils.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.benchmark.input.pointer
+import android.view.InputDevice.SOURCE_TOUCHSCREEN
import android.view.MotionEvent
import android.view.View
@@ -36,6 +37,9 @@
dispatchTarget: View
): MotionEvent {
+ // It's important we get the absolute coordinates first for the construction of the MotionEvent,
+ // and after it is created, adjust it back to the local coordinates. This way there is a history
+ // of the absolute coordinates for developers who rely on that (ViewGroup does this as well).
val locationOnScreen = IntArray(2) { 0 }
dispatchTarget.getLocationOnScreen(locationOnScreen)
@@ -57,7 +61,7 @@
0f,
0,
0,
- 0,
+ SOURCE_TOUCHSCREEN, // Required for offsetLocation() to work correctly
0
).apply {
offsetLocation(-locationOnScreen[0].toFloat(), -locationOnScreen[1].toFloat())
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 5835d87..286d46a 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -22,6 +22,7 @@
* modifying its settings.
*/
import androidx.build.LibraryType
+import androidx.build.KmpPlatformsKt
import androidx.build.PlatformIdentifier
import static androidx.inspection.gradle.InspectionPluginKt.packageInspector
@@ -46,8 +47,8 @@
api("androidx.annotation:annotation:1.6.0")
implementation("androidx.collection:collection:1.4.0")
// when updating the runtime version please also update the runtime-saveable version
- implementation("androidx.compose.runtime:runtime:1.6.0")
- api("androidx.compose.runtime:runtime-saveable:1.6.0")
+ implementation(project(":compose:runtime:runtime"))
+ api(project(":compose:runtime:runtime-saveable"))
api(project(":compose:ui:ui-geometry"))
api(project(":compose:ui:ui-graphics"))
@@ -212,14 +213,16 @@
androidx {
name = "Compose UI"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2019"
description = "Compose UI primitives. This library contains the primitives that form the Compose UI Toolkit, such as drawing, measurement and layout."
legacyDisableKotlinStrictApiMode = true
}
-tasks.findByName("desktopTest").configure {
- systemProperties["GOLDEN_PATH"] = project.rootDir.absolutePath + "/../../golden"
+if (KmpPlatformsKt.enableDesktop(project)) {
+ tasks.findByName("desktopTest").configure {
+ systemProperties["GOLDEN_PATH"] = project.rootDir.absolutePath + "/../../golden"
+ }
}
android {
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt
index 289e230..6e9fb60 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt
@@ -44,7 +44,6 @@
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.unit.dp
-@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ConditionalFocusabilityDemo() {
val localInputModeManager = LocalInputModeManager.current
@@ -109,6 +108,7 @@
.focusRequester(item4)
.pointerInput(item4) {
detectTapGestures {
+ @OptIn(ExperimentalComposeUiApi::class)
if (localInputModeManager.requestInputMode(Keyboard)) {
item4.requestFocus()
}
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
index 7c9c0d6..48ad51e 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
@@ -32,7 +32,6 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.FocusRequester.Companion.Default
@@ -47,7 +46,6 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun CustomFocusOrderDemo() {
Column {
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt
index 0a09ea0..a33bbe3 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt
@@ -34,7 +34,6 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection.Companion.Down
import androidx.compose.ui.focus.FocusDirection.Companion.Left
@@ -55,7 +54,6 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun FocusManagerMoveFocusDemo() {
val focusManager = LocalFocusManager.current
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
index 4041e63..82fc661 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
@@ -203,7 +203,6 @@
}
}
-@ExperimentalComposeUiApi
@Sampled
@Composable
fun CreateFocusRequesterRefsSample() {
@@ -216,7 +215,6 @@
}
}
-@ExperimentalComposeUiApi
@Sampled
@Composable
fun CustomFocusOrderSample() {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/accessibility/ScrollingTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/accessibility/ScrollingTest.kt
index b7386f0..1bc82d6 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/accessibility/ScrollingTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/accessibility/ScrollingTest.kt
@@ -1,4 +1,3 @@
-
/*
* Copyright 2020 The Android Open Source Project
*
@@ -44,7 +43,9 @@
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.ScrollAxisRange
+import androidx.compose.ui.semantics.getScrollViewportLength
import androidx.compose.ui.semantics.horizontalScrollAxisRange
+import androidx.compose.ui.semantics.scrollBy
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.semantics.verticalScrollAxisRange
@@ -59,6 +60,7 @@
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
@@ -465,6 +467,78 @@
}
@Test
+ fun scrollViewPort_notProvided_shouldUseFallbackViewPort() {
+ // Arrange.
+ var actualScrolledAmount = 0f
+ val viewPortSize = 100
+ rule.setContentWithAccessibilityEnabled {
+ Box(
+ Modifier
+ .size(viewPortSize.toDp())
+ .semantics(mergeDescendants = true) {
+ testTag = tag
+ horizontalScrollAxisRange = ScrollAxisRange(
+ value = { 0.5f },
+ maxValue = { 1f },
+ reverseScrolling = true
+ )
+
+ scrollBy { x, _ ->
+ actualScrolledAmount += x
+ false
+ }
+ }
+ )
+ }
+
+ val virtualViewId = rule.onNodeWithTag(tag).semanticsId
+ rule.runOnIdle {
+ androidComposeView.accessibilityNodeProvider
+ .performAction(virtualViewId, ACTION_SCROLL_BACKWARD, null)
+ }
+ assertThat(actualScrolledAmount).isEqualTo(viewPortSize)
+ }
+
+ @Test
+ fun scrollViewPort_provided_shouldUseScrollProvidedValues() {
+ // Arrange.
+ var actualScrolledAmount = 0f
+ val viewPortSize = 100
+ val contentPadding = 5f
+ rule.setContentWithAccessibilityEnabled {
+ Box(
+ Modifier
+ .size(viewPortSize.toDp())
+ .semantics(mergeDescendants = true) {
+ testTag = tag
+ horizontalScrollAxisRange = ScrollAxisRange(
+ value = { 0.5f },
+ maxValue = { 1f },
+ reverseScrolling = true
+ )
+
+ scrollBy { x, _ ->
+ actualScrolledAmount += x
+ false
+ }
+
+ getScrollViewportLength {
+ it.add((viewPortSize - contentPadding))
+ true
+ }
+ }
+ )
+ }
+
+ val virtualViewId = rule.onNodeWithTag(tag).semanticsId
+ rule.runOnIdle {
+ androidComposeView.accessibilityNodeProvider
+ .performAction(virtualViewId, ACTION_SCROLL_BACKWARD, null)
+ }
+ assertThat(actualScrolledAmount).isEqualTo(viewPortSize - contentPadding)
+ }
+
+ @Test
fun canScroll_returnsFalse_whenTouchIsOutsideBounds() {
// Arrange.
rule.setContentWithAccessibilityEnabled {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt
index 7b954bd..6c9bf4d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt
@@ -17,7 +17,6 @@
package androidx.compose.ui.focus
import androidx.compose.foundation.layout.Box
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -55,7 +54,6 @@
rule.runOnIdle { assertThat(focusState.isFocused).isTrue() }
}
- @ExperimentalComposeUiApi
@Test
fun activeParent_requestFocus() {
// Arrange.
@@ -140,7 +138,6 @@
rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
}
- @ExperimentalComposeUiApi
@Test
fun deactivatedParent_requestFocus() {
// Arrange.
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
index 000ce26..e393205 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
@@ -20,7 +20,6 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusStateImpl.Active
import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
@@ -34,9 +33,12 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+private const val UseOnFocusEvent = "onFocusEvent"
+private const val UseFocusEventModifier = "FocusEventModifier"
+
@MediumTest
@RunWith(Parameterized::class)
-class FocusEventCountTest(val focusEventType: String) {
+class FocusEventCountTest(private val focusEventType: String) {
private val onFocusEvent = if (focusEventType == UseOnFocusEvent) {
OnFocusEventCall
} else {
@@ -53,8 +55,6 @@
val FocusEventModifierCall: Modifier.((FocusState) -> Unit) -> Modifier = {
focusEventModifier(it)
}
- private const val UseOnFocusEvent = "onFocusEvent"
- private const val UseFocusEventModifier = "FocusEventModifier"
@JvmStatic
@Parameterized.Parameters(name = "onFocusEvent = {0}")
@@ -120,7 +120,6 @@
rule.runOnIdle { assertThat(focusStates).isExactly(Active) }
}
- @OptIn(ExperimentalComposeUiApi::class)
@Test
fun whenFocusMovesWithinParent_onFocusEventIsNotCalled() {
// Arrange.
@@ -611,7 +610,6 @@
rule.runOnIdle { assertThat(focusStates).isEmpty() }
}
- @OptIn(ExperimentalComposeUiApi::class)
@Test
fun changingFocusProperty_onFocusEventIsNotCalled() {
// Arrange.
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt
index faa13ae..b7977b7 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt
@@ -684,7 +684,6 @@
}
}
- @ExperimentalComposeUiApi
@Test
fun requestFocusForAnyChild_triggersOnFocusChangedInParent() {
// Arrange.
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt
index 30f4e1a..487357d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt
@@ -42,7 +42,6 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-@OptIn(ExperimentalComposeUiApi::class)
@MediumTest
@RunWith(Parameterized::class)
class TwoDimensionalFocusTraversalImplicitEnterTest(param: Param) {
@@ -79,6 +78,7 @@
var (upItem, downItem, leftItem, rightItem) = FocusRequester.createRefs()
val (child1, child2, child3, child4) = FocusRequester.createRefs()
val customFocusEnter = Modifier.focusProperties {
+ @OptIn(ExperimentalComposeUiApi::class)
enter = {
when (it) {
Left -> child1
@@ -158,6 +158,7 @@
val child = mutableStateOf(false)
var (upItem, downItem, leftItem, rightItem, childItem) = FocusRequester.createRefs()
var directionSentToEnter: FocusDirection? = null
+ @OptIn(ExperimentalComposeUiApi::class)
val customFocusEnter = Modifier.focusProperties {
enter = {
directionSentToEnter = it
@@ -238,6 +239,7 @@
val child = mutableStateOf(false)
var (upItem, downItem, leftItem, rightItem, childItem) = FocusRequester.createRefs()
var directionSentToEnter: FocusDirection? = null
+ @OptIn(ExperimentalComposeUiApi::class)
val customFocusEnter = Modifier.focusProperties {
enter = {
directionSentToEnter = it
@@ -348,6 +350,7 @@
val (up, down, left, right) = List(4) { mutableStateOf(false) }
val (item, other) = List(2) { mutableStateOf(false) }
var (upItem, downItem, leftItem, rightItem) = FocusRequester.createRefs()
+ @OptIn(ExperimentalComposeUiApi::class)
val customFocusEnter = Modifier.focusProperties { enter = { Cancel } }
when (focusDirection) {
Left -> rightItem = initialFocus
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt
index e85483e..7d3d165 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt
@@ -22,7 +22,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection.Companion.Down
import androidx.compose.ui.focus.FocusDirection.Companion.Left
@@ -64,7 +63,6 @@
}
@FlakyTest(bugId = 233373546)
- @OptIn(ExperimentalComposeUiApi::class)
@Test
fun movesFocusAmongSiblingsDeepInTheFocusHierarchy() {
// Arrange.
@@ -113,7 +111,6 @@
}
}
- @OptIn(ExperimentalComposeUiApi::class)
@Test
fun movesFocusOutsideCurrentParent() {
// Arrange.
@@ -149,7 +146,6 @@
}
}
- @OptIn(ExperimentalComposeUiApi::class)
@Test
fun movesOutsideDeactivatedParent() {
// Arrange.
@@ -188,7 +184,6 @@
}
}
- @OptIn(ExperimentalComposeUiApi::class)
@Test
fun skipsChild() {
// Arrange.
@@ -224,7 +219,6 @@
}
}
- @OptIn(ExperimentalComposeUiApi::class)
@Test
fun DoesNotSkipChildOfDeactivatedItem() {
// Arrange.
@@ -264,7 +258,6 @@
}
}
- @OptIn(ExperimentalComposeUiApi::class)
@Test
fun movesFocusAmongSiblingsDeepInTheFocusHierarchy_skipsDeactivatedSibling() {
// Arrange.
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/ApproachLayoutTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/ApproachLayoutTest.kt
index 66eb9a1..c1168d2 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/ApproachLayoutTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/ApproachLayoutTest.kt
@@ -623,6 +623,101 @@
}
}
+ /**
+ * Test that the ApproachLayoutModifierNode does not leave child in a forced lookahead
+ * placement state when removed.
+ */
+ @Test
+ fun testForcedPlacementReset() {
+ var measureWithFixedConstraints by mutableStateOf(false)
+ var removeChild by mutableStateOf(false)
+ val parentNode = object : TestApproachLayoutModifierNode() {
+ override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+ return false
+ }
+
+ override fun Placeable.PlacementScope.isPlacementApproachComplete(
+ lookaheadCoordinates: LayoutCoordinates
+ ): Boolean {
+ return true
+ }
+
+ @ExperimentalComposeUiApi
+ override fun ApproachMeasureScope.approachMeasure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ return measurable.measure(
+ if (measureWithFixedConstraints)
+ Constraints.fixed(0, 0)
+ else
+ constraints
+ ).run {
+ layout(width, height) {
+ place(0, 0)
+ }
+ }
+ }
+ }
+
+ val childNode = object : TestApproachLayoutModifierNode() {
+ override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+ return true
+ }
+
+ override fun Placeable.PlacementScope.isPlacementApproachComplete(
+ lookaheadCoordinates: LayoutCoordinates
+ ): Boolean {
+ return true
+ }
+
+ @ExperimentalComposeUiApi
+ override fun ApproachMeasureScope.approachMeasure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ return measurable.measure(constraints).run {
+ layout(width, height) {
+ place(0, 0)
+ }
+ }
+ }
+ }
+ var position = Offset(-1f, -1f)
+ rule.setContent {
+ CompositionLocalProvider(LocalDensity provides Density(1f)) {
+ Box(Modifier
+ .then(TestApproachElement(parentNode))
+ .layout { measurable, constraints ->
+ measurable
+ .measure(constraints)
+ .run {
+ layout(width, height) {
+ place(0, 0)
+ }
+ }
+ }
+ .then(if (removeChild) Modifier else TestApproachElement(childNode))
+ .requiredSize(200.dp, 200.dp)
+ .onGloballyPositioned {
+ position = it.positionInRoot()
+ }
+ )
+ }
+ }
+ rule.runOnIdle {
+ assertEquals(Offset(0f, 0f), position)
+ }
+ removeChild = true
+ rule.runOnIdle {
+ assertEquals(Offset(0f, 0f), position)
+ }
+ measureWithFixedConstraints = true
+ rule.runOnIdle {
+ assertEquals(Offset(-100f, -100f), position)
+ }
+ }
+
private class TestPlacementScope : Placeable.PlacementScope() {
override val parentWidth: Int
get() = TODO("Not yet implemented")
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
index cd2db02..6f00d9d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
@@ -222,7 +222,7 @@
override fun calculatePositionInWindow(localPosition: Offset) = TODO("Not yet implemented")
override fun calculateLocalPosition(positionInWindow: Offset) = TODO("Not yet implemented")
override fun requestFocus() = TODO("Not yet implemented")
- override fun onSemanticsChange() = TODO("Not yet implemented")
+ override fun onSemanticsChange() {}
override fun getFocusDirection(keyEvent: KeyEvent) = TODO("Not yet implemented")
}
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 7b64fa9..62198f2 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
@@ -2420,6 +2420,36 @@
}
@Test
+ fun deactivedNodesInMeasureOnly() {
+ val root = node()
+ val delegate = createDelegate(root)
+ val toBeDeactivated = node()
+ root.add(
+ node {
+ add(
+ // This is the LookaheadScope equivalent
+ LayoutNode(isVirtual = true).apply {
+ isVirtualLookaheadRoot = true
+ add(node())
+ add(toBeDeactivated)
+ add(node())
+ }
+ )
+ }
+ )
+ rule.runOnIdle {
+ assertEquals(3, root.children[0].children.size)
+ assertEquals(toBeDeactivated, root.children[0].children[1])
+ delegate.measureAndLayout()
+ }
+ rule.runOnIdle {
+ toBeDeactivated.requestLookaheadRemeasure()
+ toBeDeactivated.onDeactivate()
+ delegate.measureOnly()
+ }
+ }
+
+ @Test
fun multiMeasureLayoutInLookahead() {
var horizontal by mutableStateOf(true)
rule.setContent {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/RulerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/RulerTest.kt
index eee8688..d4e5cb5 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/RulerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/RulerTest.kt
@@ -46,6 +46,8 @@
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -600,6 +602,7 @@
var offset by mutableIntStateOf(0)
var rulerValue = 0f
var rootX = 0f
+ var rulerChanged = CountDownLatch(1)
rule.setContent {
Box(
Modifier
@@ -624,6 +627,7 @@
val p = measurable.measure(constraints)
layout(p.width, p.height) {
rulerValue = verticalRuler.current(Float.NaN)
+ rulerChanged.countDown()
}
})
}
@@ -632,11 +636,15 @@
})
}
}
+ assertThat(rulerChanged.await(1, TimeUnit.SECONDS)).isTrue()
rule.runOnUiThread {
assertThat(rulerValue).isWithin(0.01f).of(-rootX)
+ rulerChanged = CountDownLatch(1)
offset = 100
+ rule.activity.window.decorView.invalidate()
}
- rule.runOnIdle {
+ assertThat(rulerChanged.await(1, TimeUnit.SECONDS)).isTrue()
+ rule.runOnUiThread {
assertThat(rulerValue).isWithin(0.01f).of(-100f - rootX)
}
}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/RequireViewTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/RequireViewTest.kt
new file mode 100644
index 0000000..eb60d8c
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/RequireViewTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import android.view.View
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+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.LocalView
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNotNull
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RequireViewTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun requireView_returnsView() {
+ lateinit var view: View
+ lateinit var node: TestModifierNode
+ rule.setContent {
+ view = LocalView.current
+ Box(
+ Modifier
+ .then(TestModifier(onNode = { node = it }))
+ .size(1.dp)
+ )
+ }
+
+ rule.runOnIdle {
+ assertNotNull(node)
+ val modifierView = node.requireView()
+ assertThat(modifierView).isSameInstanceAs(view)
+ }
+ }
+
+ @Test
+ fun requireView_throws_whenDetached() {
+ var attach by mutableStateOf(true)
+ lateinit var node: TestModifierNode
+ rule.setContent {
+ Box(
+ Modifier
+ .then(if (attach) TestModifier(onNode = { node = it }) else Modifier)
+ .size(1.dp)
+ )
+ }
+
+ rule.waitForIdle()
+ attach = false
+
+ rule.runOnIdle {
+ assertFailsWith<IllegalStateException> { node.requireView() }
+ }
+ }
+
+ private data class TestModifier(
+ val onNode: (TestModifierNode) -> Unit
+ ) : ModifierNodeElement<TestModifierNode>() {
+ override fun create(): TestModifierNode {
+ return TestModifierNode(onNode)
+ }
+
+ override fun update(node: TestModifierNode) {
+ }
+ }
+
+ private class TestModifierNode(
+ private val onNode: (TestModifierNode) -> Unit
+ ) : Modifier.Node() {
+ override fun onAttach() {
+ onNode(this)
+ }
+ }
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt
index 44d7703..97c6c59 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/PopupSecureFlagTest.kt
@@ -143,6 +143,14 @@
inheritSecurePolicy = true,
)
assertThat(isSecureFlagEnabledForPopup()).isEqualTo(setSecureFlagOnActivity)
+
+ // Check that inherited value overrides `flags` value
+ properties = PopupProperties(
+ flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+ WindowManager.LayoutParams.FLAG_SECURE,
+ inheritSecurePolicy = true,
+ )
+ assertThat(isSecureFlagEnabledForPopup()).isEqualTo(setSecureFlagOnActivity)
}
@Composable
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/DelegatableNode.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/DelegatableNode.android.kt
new file mode 100644
index 0000000..e5cda8f5
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/DelegatableNode.android.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import android.view.View
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.internal.checkPrecondition
+
+/**
+ * The Android [View] hosting the composition.
+ *
+ * @throws IllegalStateException If the modifier node is not [attached][Modifier.Node.isAttached].
+ */
+fun DelegatableNode.requireView(): View {
+ checkPrecondition(node.isAttached) {
+ "Cannot get View because the Modifier node is not currently attached."
+ }
+ return requireLayoutNode().requireOwner() as View
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index d98f11e..df549cf 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -1739,7 +1739,8 @@
return amount < 0 && value() > 0 || amount > 0 && value() < maxValue()
}
- val viewport = node.layoutInfo.coordinates.boundsInParent().size
+ val fallbackViewport = node.layoutInfo.coordinates.boundsInParent().size
+ val activeViewPortForScroll = getScrollViewportLength(node.unmergedConfig)
// The lint warning text is unstable because anonymous lambdas have an autogenerated
// name, so suppress this lint warning with @SuppressLint instead of a baseline.
@@ -1748,8 +1749,10 @@
val xScrollState =
node.unmergedConfig.getOrNull(SemanticsProperties.HorizontalScrollAxisRange)
+
if (xScrollState != null && scrollHorizontal) {
- var amountToScroll = viewport.width
+ var amountToScroll = (activeViewPortForScroll ?: fallbackViewport.width)
+
if (scrollLeft || scrollBackward) {
amountToScroll = -amountToScroll
}
@@ -1767,7 +1770,8 @@
val yScrollState =
node.unmergedConfig.getOrNull(SemanticsProperties.VerticalScrollAxisRange)
if (yScrollState != null && scrollVertical) {
- var amountToScroll = viewport.height
+ var amountToScroll = (activeViewPortForScroll ?: fallbackViewport.height)
+
if (scrollUp || scrollBackward) {
amountToScroll = -amountToScroll
}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
index 2637624..314fa5c 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.platform
+import android.annotation.SuppressLint
import android.graphics.Region
import android.view.View
import androidx.collection.IntObjectMap
@@ -70,6 +71,18 @@
}
}
+@SuppressLint("PrimitiveInCollection")
+internal fun getScrollViewportLength(configuration: SemanticsConfiguration): Float? {
+ val viewPortCalculationsResult = mutableListOf<Float>()
+ val actionResult = configuration.getOrNull(SemanticsActions.GetScrollViewportLength)
+ ?.action?.invoke(viewPortCalculationsResult) ?: return null
+ return if (actionResult) {
+ viewPortCalculationsResult[0]
+ } else {
+ null
+ }
+}
+
/**
* These objects are used as snapshot observation scopes for the purpose of sending accessibility
* scroll events whenever the scroll offset changes. There is one per scroller and their lifecycle
@@ -108,7 +121,7 @@
internal fun SemanticsNode.isImportantForAccessibility() =
isVisible &&
(unmergedConfig.isMergingSemanticsOfDescendants ||
- unmergedConfig.containsImportantForAccessibility())
+ unmergedConfig.containsImportantForAccessibility())
@OptIn(ExperimentalComposeUiApi::class)
internal val SemanticsNode.isVisible: Boolean
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/accessibility/android_a11y_implementation_notes.md b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/accessibility/android_a11y_implementation_notes.md
new file mode 100644
index 0000000..faae554
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/accessibility/android_a11y_implementation_notes.md
@@ -0,0 +1,346 @@
+# Android accessibility implementation notes
+
+_aelias@, February 2024_
+
+This document summarizes decisions made during Compose accessibility implementation, primarily about
+how Compose communicates with Android accessibility services at the system API level, and why. From
+the point of view of app developers, these are largely implementation details, but contributors to
+Compose itself may need to know about these topics when investigating a11y bugs, optimizing
+performance, or adding additional use cases for `semantics`.
+
+Compose provides an a11y API defined in terms of `semantics`, but Compose itself is built on top of
+an Android View System-oriented API defined in terms of `AccessibilityNodeInfo` and
+`AccessibilityEvent`. Every a11y-relevant property in Compose semantics is internally mapped in
+some way to this underlying, preexisting API. Because Compose apps can run back to API level 21,
+during Compose's development we didn't try to change or add features to the system a11y API, but
+instead adapted Compose's implementation to it.
+
+Prior to reading this doc, I recommend reading the materials available on developer.android.com, not
+just about a11y in Compose but also [a11y in the View
+System](https://developer.android.com/reference/android/view/accessibility/package-summary).
+
+This document is divided into three broad categories of topics: tree structure, scheduling, and
+interaction of accessibility with testing.
+
+## Tree structure
+
+This section is about the semantics tree structure and the various ways it's transformed to
+produce the `AccessibilityNodeInfo` tree.
+
+### Merging
+
+Merging is [covered in Compose's API
+documentation](https://developer.android.com/jetpack/compose/semantics#merging-behavior) because
+it's controllable using the `mergeDescendants` semantics property. Here I will cover some notable
+implementation details about it.
+
+Merging is not actually a new concept invented by Compose. It's a preexisting undocumented behavior
+dating from the early days of Android accessibility. TalkBack has its own merging algorithm which
+activates implicitly when it sees one of several accessibility properties. In View System apps, the
+most common trigger for TalkBack to merge a subtree is the presence of a click action. And just
+like Compose, TalkBack's algorithm has the rule "children which are themselves merging are not
+merged", which becomes intuitive if you imagine the key use case of a Button contained inside a
+clickable Card (the button needs to remain focusable independently).
+
+Somewhat counterintuitively, Compose actually converts the _unmerged_ semantics tree to
+`AccessibilityNodeInfo`s. `mergeDescendants` maps to an ordinary AccessibilityNodeInfo property that
+activates merging on the a11y service side. The key property Compose uses for this is called
+[`isScreenReaderFocusable`](https://developer.android.com/reference/androidx/core/view/accessibility/AccessibilityNodeInfoCompat#isScreenReaderFocusable())
+. `isScreenReaderFocusable` triggers merging and has no other side effects.
+
+* `isScreenReaderFocusable` is not to be confused with `isFocusable`, which reflects keyboard/D-Pad
+focusability. Keyboard-focusable nodes will always be screen-reader focusable, but not necessarily
+the other way around.
+
+* Historical note: in Compose we first wrote our own merging logic at the level of `SemanticsNode`,
+but then we realized that if we created the `AccessibilityNodeInfo`s that way, we'd be hiding a lot
+of potentially relevant substructural information from a11y services. We'd also have wound up in a
+hard-to-reason-about situation where two merging algorithms get applied one after the other, because
+there exists no API to turn off the screenreader-side merging. So Compose's own merging logic ended
+up being used exclusively for unit testing.
+
+### Collapsing
+
+Distinct from merging and more fundamental, Compose's semantics implementation has a concept of
+"collapsing" all the semantics modifiers chained on one `LayoutNode`. Thanks to this, only one
+`AccessibilityNodeInfo` will be created per LayoutNode, even if it has multiple semantics Modifiers.
+
+* Collapsing is applied _right to left_ (or _bottom to top_) in code order. So if several semantics
+lambdas in a chain set the same property, the leftmost wins and the others are ignored.
+ * The principle here is the left side of the Modifier chain is usually
+ higher-level component with more context. So if a composable from a library sets an undesirable
+ semantics property, the app can replace or remove that property by passing a semantics
+ Modifier into its Modifier parameter.
+* Collapsing raises a question around the bounds of the `AccessibilityNodeInfo` because
+ different parts of the Modifier chain can have different positions and sizes. The policy on this is:
+ * If there is one or more `mergeDescendants` semantics block, then the bounds is the leftmost of those.
+ * `mergeDescendants` usually indicates a key interactable element such as a Button, so we
+ empirically observed that selecting it led to the most sensible bounds.
+ * Otherwise, the bounds is the leftmost of all semantics Modifiers.
+
+### isImportantForAccessibility
+
+AccessibilityNodeInfo has a property called `isImportantForAccessibility`. For the most part,
+TalkBack will ignore nodes with this property set to `false`, so it should be set to true if a node
+is intended to have an effect on screenreader behavior.
+
+The policy as of Compose 1.6 is that semantics properties are categorized either as
+`AccessibilityKey` (most properties) or plain `SemanticsPropertyKey` (`testTag`, `isTraversalGroup`,
+and a few rarer properties). If there exists at least one `AccessibilityKey` on a collapsed semantics node, then
+`isImportantForAccessibility = true`.
+
+In View System, this property was `false` by default. The original idea was it that it should be
+`false` for structural Views with little relevance to accessibility like `LinearLayout`, but
+`true` for any node that's relevant to screenreader behavior. In deep View hierarchies, most Views
+tend to be marked unimportant.
+
+In contrast, this property is usually `true` in Compose, because most non-accessibility-related
+elements like `Column` don't have `semantics`, and therefore no AccessibilityNodeInfo is generated
+for them in the first place. However, Compose does have a smaller subset of nodes that have
+semantics but are unimportant to screenreaders, particularly ones that only have semantics relevant
+to testing (like `testTag`) and custom developer-defined semantics.
+
+### Pruning
+
+Pruning is an automatic behavior to remove nodes that are automatically detected as invisible. This
+prevents screenreaders from focusing on elements that can't be seen.
+
+There are two main causes of pruning:
+1. When a node is invisible due to graphics `alpha = 0f`.
+ * This is the only place a graphics property is taken into account in Compose accessibility. It
+ covers for an app-side pattern where an element is marked `alpha = 0f` instead of
+ actually removed from the scene.
+2. Occlusion: when every pixel of the node bounds is covered by visible nodes with important
+semantics that are above it in placement order or z-order.
+
+Both styles of pruning are analogous to a preexisting behavior in View System.
+
+### Fake nodes
+
+Fake nodes are a workaround for some TalkBack behaviors that we wanted to avoid in the context of
+Compose. They are only created for the specific semantics properties `contentDescription` and
+`role`, which are normal properties when they're placed on leaf `AccessibilityNodeInfo`, but have
+more problematic behaviors when they're placed on an `AccessibilityNodeInfo` that has children.
+
+When one of these two properties is present on a non-leaf SemanticsNode, the Compose a11y
+implementation conjures up a new, "fake" rightmost AccessibilityNodeInfo child which was not present
+in the original semantics tree, and "moves" the property into the fake node. Fake nodes don't
+correspond to any `LayoutNode` (which is where semantics `id`s are normally generated and held), so
+the `id` of the fake node is the `id` of the original SemanticsNode plus a very large constant.
+
+The following explains the distinct motivations for doing this for `contentDescription` and `role`
+respectively:
+
+#### contentDescription
+
+In the View System API, `contentDescription` is not only a way of sprinkling in some explanatory
+text, but is also a powerful tool which overrides most other screenreader text. It can thus be used
+to replace the entire speech of a complex widget with multiple children.
+
+In Compose's accessibility API, we put the overriding capability into a distinct API
+`clearAndSetSemantics` which is harder to use by accident. We wanted `contentDescription` to be
+simply additive like the other string properties. The way we achieved that is to move it into a
+fake node, so that the a11y service sees it as a leaf with nothing to override.
+
+#### Role
+
+Without fake nodes, roles were observed to be spoken in the wrong order when placed on a non-leaf
+node. In View System apps, nodes with both roles and children were very rare, whereas in Compose
+they are very common (the classic example is `ButtonView` vs `Button { Text("...") }`). By spinning
+off roles into a fake rightmost child, they are spoken after the contents of the node, as expected.
+
+### AndroidView children
+
+A Compose scene can contain Android View children using the `AndroidView` composable. In terms of
+accessibility, the main interoperability challenge is that the AndroidView's parent is a Compose
+leaf `AccessibilityNodeInfo`. But the `View` class itself only provides an API for specifying a
+parent which is another `View`, not a virtual `AccessibilityNodeInfo`. And if we used that API to
+tell screenreaders its parent is the `AndroidComposeView`, the screenreader would get the wrong idea
+about its location relative to the rest of the scene (it would perceive it as a sibling to the root
+Compose node).
+
+Thus, in order to communicate to screenreaders the detailed location of the AndroidView in the hierarchy, we
+[create a special small `AccessibilityDelegate`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt;l=989;drc=7234c0fd1e47e7be05ef3235b8d8aa826318433d)
+on each Compose `AndroidViewHolder`. This delegate creates one small `AccessibilityNodeInfo` with
+the `parent` and `traversalBefore` properties set to the appropriate Compose semantics id. It's
+only about 50 lines and contained within one function, as compared to the main Compose
+AccessibilityDelegate which runs into the thousands of lines: that's because it can rely on the
+accessibility properties of the developer-provided View children for everything else.
+
+Unfortunately, we had to use a few workarounds to use a delegate like this:
+* The View System will ignore delegates unless `isImportantForAccessibility = true` on the View
+containing the delegate, so we had to set it to true. The problem is that the `AndroidViewHolder`
+is precisely the kind of purely structural node that normally ought to set this property to `false`.
+As a result of setting this property, TalkBack treated it as so important that it prioritized
+focusing it and made it impossible to focus a child `TextView`.
+* To workaround that issue, we [set `isVisibleToUser = false`](https://android-review.googlesource.com/c/platform/frameworks/support/+/2735015),
+which empirically prevents TalkBack from ever focusing the `AndroidViewHolder`.
+
+## Scheduling events
+
+Accessibility services maintain a model of the AccessibilityNodeInfo currently on the screen within
+their process. So there is a need to keep a11y services informed of changes to the scene over time
+using `AccessibilityEvent`s.
+
+`AccessibilityEvent`s have a high CPU cost. Although the event data structure itself is a small bundle containing a few
+[change type enums](https://developer.android.com/reference/android/view/accessibility/AccessibilityEvent#constants_1) and the `id` of the node that changed, the a11y service will typically use them as
+a trigger to mark its model dirty and query the app about every node in the subtree of that
+`id`. So in the aftermath of an event you will see a large amount of IPC traffic between the a11y
+service process and the app process.
+
+The complexity in Compose's handling of accessibility events is in order to minimize this cost. There are 3 categories of changes with different processing:
+
+### Semantics property changes: diffing (minimally batched)
+
+When a `semantics` block re-runs, we [post a `semanticsChangeChecker`
+task](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt;l=2199;drc=f8f18ee800fcc26361813931f509da19d5abcc86)
+on the UI message loop (if one is not already posted). This task compares the old and new semantics
+data structures to see if anything actually changed before sending `AccessibilityEvent`s.
+
+The reason to post a task instead of simply running synchronously is that it provides a minimal
+amount of batching. When an composition happens it's typical that a subtree of `semantics {}`
+blocks all need to run in close succession. By waiting until control returns to the message loop,
+we only run the diffing after composition has completed.
+
+### Bounds changes: 100ms throttling
+
+Bounds changes (changes to the x/y/width/height of the semantics nodes) also need to trigger
+accessibility events, since many screenreader behaviors depend on positions and sizes. Such changes
+can happen due to layout, animation or scrolling, and not necessarily involve the `semantics {}`
+lambdas getting run. This category of changes is heavily batched and throttled.
+
+When a bounds change occurs, Compose [wakes up a coroutine that runs on 100ms
+intervals](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt;l=2208;drc=f8f18ee800fcc26361813931f509da19d5abcc86).
+The first bounds change causes events to be sent immediately. But any follow-up bounds changes
+will only have an effect after 100ms has elapsed. This continues until a full 100ms passes
+with zero bounds changes observed, at which point the coroutine hibernates again.
+
+The motivation here is that scrolling and animated navigation typically involve a rapid series of
+positional changes before the scene stabilizes. If we sent these at 60/120fps, there would be a
+tremendous amount of event spam, overwhelming the a11y service. Throttling to 100ms intervals
+allows us to batch much of this work.
+
+The 100ms number is copied from a [preexisting number in View
+System](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/View.java;l=8600;drc=8aa453263f85d469ef056680b01ea90ddf300786)
+which has a similar function. Because this 100ms delay has a long history on Android, we can
+assume that a11y services have evolved their behavior and performance around it.
+
+### Scrolling changes: fast path to update green focus rect
+
+As mentioned in the above section, in terms of events, scrolling is handled similarly to layout
+(there can be a 100ms delay between updates). As far as screenreader-side behavior is concerned,
+this is fast enough, but the biggest problem is that we ideally want the green a11y focus rectangle
+to scroll precisely at the same rate as the rest of the screen during two-finger scrolling. If it
+lagged behind at 100ms intervals (or even smaller intervals), that would appear visibly glitchy and
+distracting.
+
+Some background context is that the green focus rectangle is [actually drawn by `ViewRootImpl` in
+the app
+process](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/ViewRootImpl.java;l=4954;drc=f5dff995c4939ffe9e41d709cca3b198435a55de),
+not the a11y service. Thus, although Android Views have even more aggressive 100ms batching in
+terms of a11y events than Compose does, they manage to avoid the "laggy focus rect" problem with a
+[fast path in
+ViewRootImpl](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/ViewRootImpl.java;l=4967;drc=f5dff995c4939ffe9e41d709cca3b198435a55de)
+where it calls `getBoundsOnScreen` on the focused View at draw time.
+
+Unfortunately, ViewRootImpl didn't intentionally add an analogous fast path API for
+AccessibilityNodeInfo. Fortunately, it still proved to be possible for Compose to create its own
+fast path by combining several tricks:
+
+1. The `ScrollAxisRange` [values are held in a lambda](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt;l=697;drc=53478385c1311bec3d3bdf86c8c3162ba667081c) so only that lambda needs to rerun when
+the scroll offset changes, instead of the entire semantics block.
+
+2. A ScrollObservationScope is registered to [trigger a11y-side scrolling
+logic](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt;l=2764;drc=f8f18ee800fcc26361813931f509da19d5abcc86)
+when it changes (an additional form of task scheduling which bypasses the rest of the a11y system
+after initial setup).
+
+3. Compose [keeps a
+reference](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt;l=3118;drc=f8f18ee800fcc26361813931f509da19d5abcc86)
+to the AccessibilityNodeInfo which ViewRootImpl uses as the canonical source of information for the
+green rect position. When a scroll happens, Compose causes the green rect to move by [mutating
+the bounds](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt;l=2793;drc=f8f18ee800fcc26361813931f509da19d5abcc86)
+on that AccessibilityNodeInfo and triggering a View invalidation, both cheap actions relative to
+sending an a11y event.
+
+## Interaction of accessibility and testing
+
+Compose `semantics` API was designed to support testing and accessibility use cases at the same
+time. Not only does this lead to natural code sharing as they both involve programmatic inspection
+of the UI, it means that a Compose app's unit tests naturally test it for a baseline level of
+accessibility support.
+
+### Compose unit tests
+
+Here is a brief summary of how the tree structure transformations described above apply or don't
+apply in the unit test context:
+
+#### Merging
+Tests inspect the merged tree by default, in order to test the scene that's relevant to
+accessibility, and because it avoids the need for awkward & brittle parent/child traversal code (for
+example, on the merged tree, you can find a `Button` by the text inside it, then trigger its click
+action, because they are the same node).
+
+To inspect the unmerged tree instead, most test matchers provide an optional parameter
+`useUnmergedTree`. When this flag is true, it shows all nodes that were present in the original
+semantics tree, including even the ones replaced by `clearAndSetSemantics`.
+
+As mentioned earlier, Compose's a11y implemention passes the unmerged semantics tree to the
+accessibility service and relies on the service-process-side merging algorithm. The Compose merged
+semantics tree can be thought of as a local simulation of the service's merging algorithm.
+
+#### Collapsing
+Collapsing transformations fully apply to unit tests as well. For example, when a test takes a
+screenshot of one Compose node, the screenshot's size is the size of its semantic bounds, chosen
+according to the policy described above.
+
+#### Pruning
+Compose unit tests aren't affected by pruning and can match all nodes that were composed, even if
+they are hidden behind another node. But note that visibility-related assertions may still be
+affected by the graphics `alpha = 0f` visibility policy, for example if asserting too early for an
+element with a fade-in animation.
+
+#### Fake nodes
+Unit tests do not see any fake nodes even when `useUnmergedTree = true`. This is to avoid confusion
+and maintain flexibility to change them in the future.
+
+### UIAutomator
+
+UIAutomator is a Android system instrumentation tool to perform actions like clicking and
+scrolling via a PC USB connection. Originally intended for testing and automation in general, it
+has today mostly been superceded by other tools, except for one key use case: performance
+benchmarking. It is uniquely well-suited to performance benchmarking because it works even on very
+old builds of Android and requires no debug modes or special privileges. Thus, it can be used to
+benchmark the final release build of the app on an ordinary phone.
+
+What lets UIAutomator work on every app is that it makes use of the accessibility data structures.
+It's not exactly an a11y service, as it doesn't appear in the list of services if you query
+AccessibilityManager for them. But it's vaguely analogous to a screenreader in the sense that it
+examines the contents of the screen by querying the app for AccessibilityNodeInfo, and triggers
+clicks and scrolls via a11y actions. Thus Compose's accessibility code "automatically" supports
+UIAutomator as well, but in practice some adjustments are needed.
+
+Compose [detects that UIAutomator is
+the one that enabled
+accessibility](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt;l=253)
+by observing that system accessibility is currently enabled, yet the AccessibilityManager service
+list is empty. In this mode, Compose will provide AccessibilityNodeInfos when queried as usual, but
+it will refrain from sending any AccessibilityEvents events as the scene changes. The reason is
+that sending such events has a performance cost, which would introduce noise in UIAutomator-based
+benchmarks. Furthermore, UIAutomator mostly relies on polling instead of relying on such events anyway.
+
+Also, one Compose semantic property was introduced primarily for use by UIAutomator-based
+benchmarks:
+[`testTagsAsResourceId`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.android.kt;l=37;drc=dc23dcdc4276814e69cc00639c617eac494c7ead).
+Although `testTag`s are always provided in AccessibilityNodeInfo `extras`, UIAutomator was written
+before `extras` existed so it unfortunately some versions of it have no matcher for this type of
+AccessibilityNodeInfo property. One matcher it does have is for Android resource IDs, and indeed
+this is the main one used in UIAutomator-based View System app benchmarks. So we added a feature to
+map one type of test-matching tag to the other.
+
+- Note 1: `testTag`'s use in UIAutomator is also why `testTag`-only nodes aren't fully pruned from
+the AccessibilityNodeInfo tree, but marked unimportant instead.
+
+- Note 2: if you are interested in reading the source code of UIAutomator, there are many old
+versions of it floating around so be careful you are looking at the right one. The current version
+as of early 2024 is [in the android-support-test branch here](https://android.googlesource.com/platform/frameworks/uiautomator/+/android-support-test/src/main/java/android/support/test/uiautomator/UiObject2.java).
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
index 5c2c808..36c9254 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
@@ -951,10 +951,12 @@
private fun PopupProperties.flagsWithSecureFlagInherited(
isParentFlagSecureEnabled: Boolean,
-): Int = if (this.inheritSecurePolicy && isParentFlagSecureEnabled) {
- this.flags or WindowManager.LayoutParams.FLAG_SECURE
-} else {
- this.flags
+): Int = when {
+ this.inheritSecurePolicy && isParentFlagSecureEnabled ->
+ this.flags or WindowManager.LayoutParams.FLAG_SECURE
+ this.inheritSecurePolicy && !isParentFlagSecureEnabled ->
+ this.flags and WindowManager.LayoutParams.FLAG_SECURE.inv()
+ else -> this.flags
}
private fun Rect.toIntBounds() = IntRect(
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/PolyFitLeastSquaresTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/PolyFitLeastSquaresTest.kt
index 5675abc..4727854 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/PolyFitLeastSquaresTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/PolyFitLeastSquaresTest.kt
@@ -238,27 +238,13 @@
}
@Test
- fun polyFitLeastSquares_extremeSlope_throwsException() {
- val x = floatArrayOf(0f, Float.MIN_VALUE)
- val y = floatArrayOf(0f, Float.MAX_VALUE)
-
- val throwable = catchThrowable {
- polyFitLeastSquares(x, y, x.size, 1)
- }
-
- assertThat(throwable is IllegalArgumentException).isTrue()
- }
-
- @Test
- fun polyFitLeastSquares_3Points2IdenticalDegree2_throwsException() {
+ fun polyFitLeastSquares_3Points2IdenticalDegree2() {
val x = floatArrayOf(0f, 0f, 1f)
val y = floatArrayOf(0f, 0f, 1f)
- val throwable = catchThrowable {
- polyFitLeastSquares(x, y, x.size, 2)
- }
+ val actual = polyFitLeastSquares(x, y, x.size, 2)
- assertThat(throwable is IllegalArgumentException).isTrue()
+ assertIsCloseToEquals(actual, floatArrayOf(0f, 0f))
}
private fun catchThrowable(lambda: () -> Unit): Throwable? {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDropNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDropNode.kt
index 1c9942f..552889f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDropNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDropNode.kt
@@ -20,10 +20,14 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.internal.checkPrecondition
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.TraversableNode
import androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction
+import androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction.CancelTraversal
+import androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction.ContinueTraversal
+import androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction.SkipSubtreeAndContinueTraversal
import androidx.compose.ui.node.requireLayoutNode
import androidx.compose.ui.node.requireOwner
import androidx.compose.ui.node.traverseDescendants
@@ -112,23 +116,6 @@
DragAndDropModifierNode {
companion object {
private object DragAndDropTraversableKey
-
- private inline fun DragAndDropModifierNode.firstChildOrNull(
- crossinline predicate: (DragAndDropModifierNode) -> Boolean
- ): DragAndDropModifierNode? {
- // TODO: b/303904810 unattached nodes should not be found from an attached
- // root drag and drop node
- if (!node.isAttached) return null
- var match: DragAndDropModifierNode? = null
- traverseDescendants(DragAndDropTraversableKey) { child ->
- if (child is DragAndDropModifierNode && predicate(child)) {
- match = child
- return@traverseDescendants TraverseDescendantsAction.CancelTraversal
- }
- TraverseDescendantsAction.ContinueTraversal
- }
- return match
- }
}
override val traverseKey: Any = DragAndDropTraversableKey
@@ -163,31 +150,30 @@
}
override fun acceptDragAndDropTransfer(startEvent: DragAndDropEvent): Boolean {
- // TODO: b/303904810 unattached nodes should not be found from an attached
- // root drag and drop node
- if (!isAttached) return false
-
- check(thisDragAndDropTarget == null) {
- "DragAndDropTarget self reference must be null at the start of a drag and drop session"
- }
-
- // Start receiving events
- thisDragAndDropTarget = onDragAndDropStart(startEvent)
-
- var handledByChild = false
-
- // TODO(b/324935431) Use flatter API when available
- traverseDescendants { child ->
- handledByChild = handledByChild or child.acceptDragAndDropTransfer(
- startEvent = startEvent,
- ).also { accepted ->
- if (accepted) requireOwner().dragAndDropManager.registerNodeInterest(child)
+ var handled = false
+ traverseSelfAndDescendants { currentNode ->
+ // TODO: b/303904810 unattached nodes should not be found from an attached
+ // root drag and drop node
+ if (!currentNode.isAttached) {
+ return@traverseSelfAndDescendants SkipSubtreeAndContinueTraversal
}
- // Only find the first descendant in any trees
- TraverseDescendantsAction.SkipSubtreeAndContinueTraversal
- }
- return handledByChild || thisDragAndDropTarget != null
+ checkPrecondition(currentNode.thisDragAndDropTarget == null) {
+ "DragAndDropTarget self reference must be null" +
+ " at the start of a drag and drop session"
+ }
+
+ // Start receiving events
+ currentNode.thisDragAndDropTarget = currentNode.onDragAndDropStart(startEvent)
+
+ val accepted = currentNode.thisDragAndDropTarget != null
+ if (accepted) {
+ requireOwner().dragAndDropManager.registerNodeInterest(currentNode)
+ }
+ handled = handled || accepted
+ ContinueTraversal
+ }
+ return handled
}
// end DragAndDropModifierNode
@@ -214,7 +200,7 @@
// Moved within child.
currentChildNode?.contains(event.positionInRoot) == true -> currentChildNode
// Position is now outside active child, maybe it entered a different one.
- else -> firstChildOrNull { child ->
+ else -> firstDescendantOrNull { child ->
// Only dispatch to children who previously accepted the onStart gesture
requireOwner().dragAndDropManager.isInterestedNode(child) &&
child.contains(event.positionInRoot)
@@ -268,20 +254,16 @@
}
}
- override fun onEnded(event: DragAndDropEvent) {
+ override fun onEnded(event: DragAndDropEvent) = traverseSelfAndDescendants { currentNode ->
// TODO: b/303904810 unattached nodes should not be found from an attached
// root drag and drop node
- if (!node.isAttached) return
-
- // TODO(b/324935431) Use flatter API when available
- traverseDescendants { child ->
- child.onEnded(event = event)
- // Only find the first descendant in any trees
- TraverseDescendantsAction.SkipSubtreeAndContinueTraversal
+ if (!currentNode.node.isAttached) {
+ return@traverseSelfAndDescendants SkipSubtreeAndContinueTraversal
}
- thisDragAndDropTarget?.onEnded(event = event)
- thisDragAndDropTarget = null
- lastChildDragAndDropModifierNode = null
+ currentNode.thisDragAndDropTarget?.onEnded(event = event)
+ currentNode.thisDragAndDropTarget = null
+ currentNode.lastChildDragAndDropModifierNode = null
+ ContinueTraversal
}
// end DropTarget
}
@@ -308,3 +290,27 @@
return position.x in x1..x2 && position.y in y1..y2
}
+
+private fun <T : TraversableNode> T.traverseSelfAndDescendants(
+ block: (T) -> TraverseDescendantsAction
+) {
+ if (block(this) != ContinueTraversal) return
+ traverseDescendants(block)
+}
+
+private inline fun <T : TraversableNode> T.firstDescendantOrNull(
+ crossinline predicate: (T) -> Boolean
+): T? {
+ // TODO: b/303904810 unattached nodes should not be found from an attached
+ // root drag and drop node
+ if (!node.isAttached) return null
+ var match: T? = null
+ traverseDescendants { child ->
+ if (predicate(child)) {
+ match = child
+ return@traverseDescendants CancelTraversal
+ }
+ ContinueTraversal
+ }
+ return match
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
index 8c11a94..7ed63b7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
@@ -190,7 +190,6 @@
*
* @sample androidx.compose.ui.samples.CreateFocusRequesterRefsSample
*/
- @ExperimentalComposeUiApi
object FocusRequesterFactory {
operator fun component1() = FocusRequester()
operator fun component2() = FocusRequester()
@@ -216,8 +215,7 @@
*
* @sample androidx.compose.ui.samples.CreateFocusRequesterRefsSample
*/
- @ExperimentalComposeUiApi
- fun createRefs() = FocusRequesterFactory
+ fun createRefs(): FocusRequesterFactory = FocusRequesterFactory
}
/**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
index 9c7917e..04a0bce 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
@@ -376,7 +376,20 @@
* The [position] represents the position of the pointer relative to the element that
* this [PointerInputChange] is being dispatched to.
*
- * Note: The [position] values can be outside the actual bounds of the element itself meaning the
+ * Note 1: A [PointerEvent]'s [PointerEventType] is the cause of an event and the associated
+ * [PointerInputChange]'s properties reflecting that. Most of those are exclusive, in other words,
+ * a Press/Release [PointerEventType] will not cause a scrollDelta change and a
+ * Scroll [PointerEventType] will not cause a pressed or previousPressed change. However, either a
+ * a Scroll or a Press/Release may contain a position change in its [PointerInputChange]s.
+ * (You can imagine one finger moving while another is lifted up.)
+ *
+ * Examples of [PointerEventType] and the associated [PointerInputChange] property changes:
+ * - Press -> press will change (position may change) but scroll delta will not.
+ * - Release -> press will change (position may change) but scroll delta will not.
+ * - Move -> position will change but press and scroll delta will not.
+ * - Scroll -> scroll delta will change (position may change) but press will not.
+ *
+ * Note 2: The [position] values can be outside the actual bounds of the element itself meaning the
* numbers can be negative or larger than the element bounds.
*
* The [previousPosition] represents the position of the pointer offset to the current
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
index 0db00be7..1ac4767 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
@@ -22,7 +22,9 @@
import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.internal.checkPrecondition
+import androidx.compose.ui.internal.throwIllegalArgumentException
import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.util.fastCoerceAtLeast
import androidx.compose.ui.util.fastForEach
import kotlin.math.abs
import kotlin.math.sign
@@ -475,10 +477,10 @@
coefficients: FloatArray = FloatArray((degree + 1).coerceAtLeast(0))
): FloatArray {
if (degree < 1) {
- throw IllegalArgumentException("The degree must be at positive integer")
+ throwIllegalArgumentException("The degree must be at positive integer")
}
if (sampleCount == 0) {
- throw IllegalArgumentException("At least one point must be provided")
+ throwIllegalArgumentException("At least one point must be provided")
}
val truncatedDegree =
@@ -489,8 +491,8 @@
}
// Shorthands for the purpose of notation equivalence to original C++ code.
- val m: Int = sampleCount
- val n: Int = truncatedDegree + 1
+ val m = sampleCount
+ val n = truncatedDegree + 1
// Expand the X vector to a matrix A, pre-multiplied by the weights.
val a = Matrix(n, m)
@@ -509,10 +511,8 @@
val r = Matrix(n, n)
for (j in 0 until n) {
val w = q[j]
- val aw = a[j]
- for (h in 0 until m) {
- w[h] = aw[h]
- }
+ a[j].copyInto(w, 0, 0, m)
+
for (i in 0 until j) {
val z = q[i]
val dot = w.dot(z)
@@ -521,22 +521,11 @@
}
}
- val norm: Float = w.norm()
- if (norm < 0.000001f) {
- // TODO(b/129494471): Determine what this actually means and see if there are
- // alternatives to throwing an Exception here.
-
- // Vectors are linearly dependent or zero so no solution.
- throw IllegalArgumentException(
- "Vectors are linearly dependent or zero so no " +
- "solution. TODO(shepshapard), actually determine what this means"
- )
- }
-
- val inverseNorm: Float = 1.0f / norm
+ val inverseNorm = 1.0f / w.norm().fastCoerceAtLeast(1e-6f)
for (h in 0 until m) {
w[h] *= inverseNorm
}
+
val v = r[j]
for (i in 0 until n) {
v[i] = if (i < j) 0.0f else w.dot(a[i])
@@ -561,11 +550,12 @@
}
for (i in n - 1 downTo 0) {
- coefficients[i] = q[i].dot(wy)
+ var c = q[i].dot(wy)
+ val ri = r[i]
for (j in n - 1 downTo i + 1) {
- coefficients[i] -= r[i, j] * coefficients[j]
+ c -= ri[j] * coefficients[j]
}
- coefficients[i] /= r[i, i]
+ coefficients[i] = c / ri[i]
}
return coefficients
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt
index d07ac38..7bac5cd 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt
@@ -253,7 +253,7 @@
}
}
measureResult.placeChildren()
- wrappedNonNull.forceMeasureWithLookaheadConstraints = false
+ wrappedNonNull.forcePlaceWithLookaheadOffset = false
}
override fun calculateAlignmentLine(alignmentLine: AlignmentLine): Int {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
index 1323a40..3f80b72 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
@@ -565,6 +565,9 @@
* [LayoutNode.lookaheadMeasurePending].
*/
private fun remeasureOnly(layoutNode: LayoutNode, affectsLookahead: Boolean) {
+ if (layoutNode.isDeactivated) {
+ return
+ }
val constraints = if (layoutNode === root) rootConstraints!! else null
if (affectsLookahead) {
doLookaheadRemeasure(layoutNode, constraints)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
index 55e9b11..7f7220d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
@@ -418,6 +418,12 @@
* @see SemanticsPropertyReceiver.pageRight
*/
val PageRight = ActionPropertyKey<() -> Boolean>("PageRight")
+
+ /**
+ * @see SemanticsPropertyReceiver.getScrollViewportLength
+ */
+ val GetScrollViewportLength =
+ ActionPropertyKey<(MutableList<Float>) -> Boolean>("GetScrollViewportLength")
}
/**
@@ -1560,3 +1566,18 @@
) {
this[SemanticsActions.PageRight] = AccessibilityAction(label, action)
}
+
+/**
+ * Action to get a scrollable's active view port amount for scrolling actions. The result is the
+ * first element of the array in the argument of the AccessibilityAction.
+ *
+ * @param label Optional label for this action.
+ * @param action Action to be performed when the [SemanticsActions.GetScrollViewportLength] is
+ * called.
+ */
+fun SemanticsPropertyReceiver.getScrollViewportLength(
+ label: String? = null,
+ action: ((MutableList<Float>) -> Boolean)
+) {
+ this[SemanticsActions.GetScrollViewportLength] = AccessibilityAction(label, action)
+}
diff --git a/concurrent/concurrent-futures-ktx/build.gradle b/concurrent/concurrent-futures-ktx/build.gradle
index 15e8be0..692ada9 100644
--- a/concurrent/concurrent-futures-ktx/build.gradle
+++ b/concurrent/concurrent-futures-ktx/build.gradle
@@ -21,8 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -42,7 +41,7 @@
androidx {
name = "Futures Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2019"
description = "Kotlin Extensions for Androidx implementation of Guava's ListenableFuture"
metalavaK2UastEnabled = true
diff --git a/constraintlayout/constraintlayout-compose/api/current.txt b/constraintlayout/constraintlayout-compose/api/current.txt
index 5863f7a..73c8f6c 100644
--- a/constraintlayout/constraintlayout-compose/api/current.txt
+++ b/constraintlayout/constraintlayout-compose/api/current.txt
@@ -606,8 +606,6 @@
}
public enum LayoutInfoFlags {
- method public static androidx.constraintlayout.compose.LayoutInfoFlags valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.constraintlayout.compose.LayoutInfoFlags[] values();
enum_constant public static final androidx.constraintlayout.compose.LayoutInfoFlags BOUNDS;
enum_constant public static final androidx.constraintlayout.compose.LayoutInfoFlags NONE;
}
@@ -647,16 +645,12 @@
}
public enum MotionLayoutDebugFlags {
- method public static androidx.constraintlayout.compose.MotionLayoutDebugFlags valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.constraintlayout.compose.MotionLayoutDebugFlags[] values();
enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags NONE;
enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags SHOW_ALL;
enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags UNKNOWN;
}
@Deprecated public enum MotionLayoutFlag {
- method @Deprecated public static androidx.constraintlayout.compose.MotionLayoutFlag valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.constraintlayout.compose.MotionLayoutFlag[] values();
enum_constant @Deprecated public static final androidx.constraintlayout.compose.MotionLayoutFlag Default;
enum_constant @Deprecated public static final androidx.constraintlayout.compose.MotionLayoutFlag FullMeasure;
}
diff --git a/constraintlayout/constraintlayout-compose/api/restricted_current.txt b/constraintlayout/constraintlayout-compose/api/restricted_current.txt
index 2f01664..05078ff 100644
--- a/constraintlayout/constraintlayout-compose/api/restricted_current.txt
+++ b/constraintlayout/constraintlayout-compose/api/restricted_current.txt
@@ -53,8 +53,6 @@
}
@kotlin.PublishedApi internal enum CompositionSource {
- method public static androidx.constraintlayout.compose.CompositionSource valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.constraintlayout.compose.CompositionSource[] values();
enum_constant public static final androidx.constraintlayout.compose.CompositionSource Content;
enum_constant public static final androidx.constraintlayout.compose.CompositionSource Unknown;
}
@@ -655,8 +653,6 @@
}
public enum LayoutInfoFlags {
- method public static androidx.constraintlayout.compose.LayoutInfoFlags valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.constraintlayout.compose.LayoutInfoFlags[] values();
enum_constant public static final androidx.constraintlayout.compose.LayoutInfoFlags BOUNDS;
enum_constant public static final androidx.constraintlayout.compose.LayoutInfoFlags NONE;
}
@@ -730,16 +726,12 @@
}
public enum MotionLayoutDebugFlags {
- method public static androidx.constraintlayout.compose.MotionLayoutDebugFlags valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.constraintlayout.compose.MotionLayoutDebugFlags[] values();
enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags NONE;
enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags SHOW_ALL;
enum_constant public static final androidx.constraintlayout.compose.MotionLayoutDebugFlags UNKNOWN;
}
@Deprecated public enum MotionLayoutFlag {
- method @Deprecated public static androidx.constraintlayout.compose.MotionLayoutFlag valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.constraintlayout.compose.MotionLayoutFlag[] values();
enum_constant @Deprecated public static final androidx.constraintlayout.compose.MotionLayoutFlag Default;
enum_constant @Deprecated public static final androidx.constraintlayout.compose.MotionLayoutFlag FullMeasure;
}
diff --git a/constraintlayout/constraintlayout-compose/build.gradle b/constraintlayout/constraintlayout-compose/build.gradle
index 070ebb48..a052358 100644
--- a/constraintlayout/constraintlayout-compose/build.gradle
+++ b/constraintlayout/constraintlayout-compose/build.gradle
@@ -104,7 +104,7 @@
androidx {
name = "ConstraintLayout Compose"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
mavenVersion = LibraryVersions.CONSTRAINTLAYOUT_COMPOSE
inceptionYear = "2022"
description = "This library offers a flexible and adaptable way to position and animate widgets in Compose"
diff --git a/core/core-ktx/api/1.13.0-beta01.txt b/core/core-ktx/api/1.13.0-beta01.txt
new file mode 100644
index 0000000..93481c1
--- /dev/null
+++ b/core/core-ktx/api/1.13.0-beta01.txt
@@ -0,0 +1,634 @@
+// Signature format: 4.0
+package androidx.core.animation {
+
+ public final class AnimatorKt {
+ method public static inline android.animation.Animator.AnimatorListener addListener(android.animation.Animator, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onEnd, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onStart, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onCancel, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onRepeat);
+ method public static android.animation.Animator.AnimatorPauseListener addPauseListener(android.animation.Animator, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onResume, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onPause);
+ method public static inline android.animation.Animator.AnimatorListener doOnCancel(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static inline android.animation.Animator.AnimatorListener doOnEnd(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static android.animation.Animator.AnimatorPauseListener doOnPause(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static inline android.animation.Animator.AnimatorListener doOnRepeat(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static android.animation.Animator.AnimatorPauseListener doOnResume(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static inline android.animation.Animator.AnimatorListener doOnStart(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ }
+
+}
+
+package androidx.core.content {
+
+ public final class ContentValuesKt {
+ method public static android.content.ContentValues contentValuesOf(kotlin.Pair<java.lang.String,?>... pairs);
+ }
+
+ public final class ContextKt {
+ method public static inline <reified T> T? getSystemService(android.content.Context);
+ method public static inline void withStyledAttributes(android.content.Context, optional android.util.AttributeSet? set, int[] attrs, optional @AttrRes int defStyleAttr, optional @StyleRes int defStyleRes, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,kotlin.Unit> block);
+ method public static inline void withStyledAttributes(android.content.Context, @StyleRes int resourceId, int[] attrs, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,kotlin.Unit> block);
+ }
+
+ public final class SharedPreferencesKt {
+ method public static inline void edit(android.content.SharedPreferences, optional boolean commit, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action);
+ }
+
+}
+
+package androidx.core.content.res {
+
+ public final class TypedArrayKt {
+ method public static boolean getBooleanOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @ColorInt public static int getColorOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static android.content.res.ColorStateList getColorStateListOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static float getDimensionOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @Dimension public static int getDimensionPixelOffsetOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @Dimension public static int getDimensionPixelSizeOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static android.graphics.drawable.Drawable getDrawableOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static float getFloatOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @RequiresApi(26) public static android.graphics.Typeface getFontOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static int getIntOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static int getIntegerOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @AnyRes public static int getResourceIdOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static String getStringOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static CharSequence[] getTextArrayOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static CharSequence getTextOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static inline <R> R use(android.content.res.TypedArray, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,? extends R> block);
+ }
+
+}
+
+package androidx.core.database {
+
+ public final class CursorKt {
+ method public static inline byte[]? getBlobOrNull(android.database.Cursor, int index);
+ method public static inline Double? getDoubleOrNull(android.database.Cursor, int index);
+ method public static inline Float? getFloatOrNull(android.database.Cursor, int index);
+ method public static inline Integer? getIntOrNull(android.database.Cursor, int index);
+ method public static inline Long? getLongOrNull(android.database.Cursor, int index);
+ method public static inline Short? getShortOrNull(android.database.Cursor, int index);
+ method public static inline String? getStringOrNull(android.database.Cursor, int index);
+ }
+
+}
+
+package androidx.core.database.sqlite {
+
+ public final class SQLiteDatabaseKt {
+ method public static inline <T> T transaction(android.database.sqlite.SQLiteDatabase, optional boolean exclusive, kotlin.jvm.functions.Function1<? super android.database.sqlite.SQLiteDatabase,? extends T> body);
+ }
+
+}
+
+package androidx.core.graphics {
+
+ public final class BitmapKt {
+ method public static inline android.graphics.Bitmap applyCanvas(android.graphics.Bitmap, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline operator boolean contains(android.graphics.Bitmap, android.graphics.Point p);
+ method public static inline operator boolean contains(android.graphics.Bitmap, android.graphics.PointF p);
+ method public static inline android.graphics.Bitmap createBitmap(int width, int height, optional android.graphics.Bitmap.Config config);
+ method @RequiresApi(26) public static inline android.graphics.Bitmap createBitmap(int width, int height, optional android.graphics.Bitmap.Config config, optional boolean hasAlpha, optional android.graphics.ColorSpace colorSpace);
+ method public static inline operator int get(android.graphics.Bitmap, int x, int y);
+ method public static inline android.graphics.Bitmap scale(android.graphics.Bitmap, int width, int height, optional boolean filter);
+ method public static inline operator void set(android.graphics.Bitmap, int x, int y, @ColorInt int color);
+ }
+
+ public final class CanvasKt {
+ method public static inline void withClip(android.graphics.Canvas, android.graphics.Path clipPath, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, android.graphics.Rect clipRect, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, android.graphics.RectF clipRect, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, float left, float top, float right, float bottom, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, int left, int top, int right, int bottom, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withMatrix(android.graphics.Canvas, optional android.graphics.Matrix matrix, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withRotation(android.graphics.Canvas, optional float degrees, optional float pivotX, optional float pivotY, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withSave(android.graphics.Canvas, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withScale(android.graphics.Canvas, optional float x, optional float y, optional float pivotX, optional float pivotY, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withSkew(android.graphics.Canvas, optional float x, optional float y, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withTranslation(android.graphics.Canvas, optional float x, optional float y, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ }
+
+ public final class ColorKt {
+ method @RequiresApi(26) public static inline operator float component1(android.graphics.Color);
+ method public static inline operator int component1(@ColorInt int);
+ method @RequiresApi(26) public static inline operator float component1(@ColorLong long);
+ method @RequiresApi(26) public static inline operator float component2(android.graphics.Color);
+ method public static inline operator int component2(@ColorInt int);
+ method @RequiresApi(26) public static inline operator float component2(@ColorLong long);
+ method @RequiresApi(26) public static inline operator float component3(android.graphics.Color);
+ method public static inline operator int component3(@ColorInt int);
+ method @RequiresApi(26) public static inline operator float component3(@ColorLong long);
+ method @RequiresApi(26) public static inline operator float component4(android.graphics.Color);
+ method public static inline operator int component4(@ColorInt int);
+ method @RequiresApi(26) public static inline operator float component4(@ColorLong long);
+ method @RequiresApi(26) public static inline infix android.graphics.Color convertTo(android.graphics.Color, android.graphics.ColorSpace colorSpace);
+ method @RequiresApi(26) public static inline infix android.graphics.Color convertTo(android.graphics.Color, android.graphics.ColorSpace.Named colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(@ColorInt int, android.graphics.ColorSpace colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(@ColorInt int, android.graphics.ColorSpace.Named colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(@ColorLong long, android.graphics.ColorSpace colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(@ColorLong long, android.graphics.ColorSpace.Named colorSpace);
+ method public static inline int getAlpha(@ColorInt int);
+ method @RequiresApi(26) public static inline float getAlpha(@ColorLong long);
+ method public static inline int getBlue(@ColorInt int);
+ method @RequiresApi(26) public static inline float getBlue(@ColorLong long);
+ method @RequiresApi(26) public static inline android.graphics.ColorSpace getColorSpace(@ColorLong long);
+ method public static inline int getGreen(@ColorInt int);
+ method @RequiresApi(26) public static inline float getGreen(@ColorLong long);
+ method @RequiresApi(26) public static inline float getLuminance(@ColorInt int);
+ method @RequiresApi(26) public static inline float getLuminance(@ColorLong long);
+ method public static inline int getRed(@ColorInt int);
+ method @RequiresApi(26) public static inline float getRed(@ColorLong long);
+ method @RequiresApi(26) public static inline boolean isSrgb(@ColorLong long);
+ method @RequiresApi(26) public static inline boolean isWideGamut(@ColorLong long);
+ method @RequiresApi(26) public static operator android.graphics.Color plus(android.graphics.Color, android.graphics.Color c);
+ method @RequiresApi(26) public static inline android.graphics.Color toColor(@ColorInt int);
+ method @RequiresApi(26) public static inline android.graphics.Color toColor(@ColorLong long);
+ method @ColorInt public static inline int toColorInt(String);
+ method @ColorInt @RequiresApi(26) public static inline int toColorInt(@ColorLong long);
+ method @ColorLong @RequiresApi(26) public static inline long toColorLong(@ColorInt int);
+ }
+
+ public final class ImageDecoderKt {
+ method @RequiresApi(28) public static inline android.graphics.Bitmap decodeBitmap(android.graphics.ImageDecoder.Source, kotlin.jvm.functions.Function3<? super android.graphics.ImageDecoder,? super android.graphics.ImageDecoder.ImageInfo,? super android.graphics.ImageDecoder.Source,kotlin.Unit> action);
+ method @RequiresApi(28) public static inline android.graphics.drawable.Drawable decodeDrawable(android.graphics.ImageDecoder.Source, kotlin.jvm.functions.Function3<? super android.graphics.ImageDecoder,? super android.graphics.ImageDecoder.ImageInfo,? super android.graphics.ImageDecoder.Source,kotlin.Unit> action);
+ }
+
+ public final class MatrixKt {
+ method public static android.graphics.Matrix rotationMatrix(float degrees, optional float px, optional float py);
+ method public static android.graphics.Matrix scaleMatrix(optional float sx, optional float sy);
+ method public static inline operator android.graphics.Matrix times(android.graphics.Matrix, android.graphics.Matrix m);
+ method public static android.graphics.Matrix translationMatrix(optional float tx, optional float ty);
+ method public static inline float[] values(android.graphics.Matrix);
+ }
+
+ public final class PaintKt {
+ method public static inline boolean setBlendMode(android.graphics.Paint, androidx.core.graphics.BlendModeCompat? blendModeCompat);
+ }
+
+ public final class PathKt {
+ method public static inline infix android.graphics.Path and(android.graphics.Path, android.graphics.Path p);
+ method @RequiresApi(26) public static Iterable<androidx.core.graphics.PathSegment> flatten(android.graphics.Path, optional float error);
+ method public static inline operator android.graphics.Path minus(android.graphics.Path, android.graphics.Path p);
+ method public static inline infix android.graphics.Path or(android.graphics.Path, android.graphics.Path p);
+ method public static inline operator android.graphics.Path plus(android.graphics.Path, android.graphics.Path p);
+ method public static inline infix android.graphics.Path xor(android.graphics.Path, android.graphics.Path p);
+ }
+
+ public final class PictureKt {
+ method public static inline android.graphics.Picture record(android.graphics.Picture, int width, int height, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ }
+
+ public final class PointKt {
+ method public static inline operator int component1(android.graphics.Point);
+ method public static inline operator float component1(android.graphics.PointF);
+ method public static inline operator int component2(android.graphics.Point);
+ method public static inline operator float component2(android.graphics.PointF);
+ method public static inline operator android.graphics.Point div(android.graphics.Point, float scalar);
+ method public static inline operator android.graphics.PointF div(android.graphics.PointF, float scalar);
+ method public static inline operator android.graphics.Point minus(android.graphics.Point, android.graphics.Point p);
+ method public static inline operator android.graphics.Point minus(android.graphics.Point, int xy);
+ method public static inline operator android.graphics.PointF minus(android.graphics.PointF, android.graphics.PointF p);
+ method public static inline operator android.graphics.PointF minus(android.graphics.PointF, float xy);
+ method public static inline operator android.graphics.Point plus(android.graphics.Point, android.graphics.Point p);
+ method public static inline operator android.graphics.Point plus(android.graphics.Point, int xy);
+ method public static inline operator android.graphics.PointF plus(android.graphics.PointF, android.graphics.PointF p);
+ method public static inline operator android.graphics.PointF plus(android.graphics.PointF, float xy);
+ method public static inline operator android.graphics.Point times(android.graphics.Point, float scalar);
+ method public static inline operator android.graphics.PointF times(android.graphics.PointF, float scalar);
+ method public static inline android.graphics.Point toPoint(android.graphics.PointF);
+ method public static inline android.graphics.PointF toPointF(android.graphics.Point);
+ method public static inline operator android.graphics.Point unaryMinus(android.graphics.Point);
+ method public static inline operator android.graphics.PointF unaryMinus(android.graphics.PointF);
+ }
+
+ public final class PorterDuffKt {
+ method public static inline android.graphics.PorterDuffColorFilter toColorFilter(android.graphics.PorterDuff.Mode, int color);
+ method public static inline android.graphics.PorterDuffXfermode toXfermode(android.graphics.PorterDuff.Mode);
+ }
+
+ public final class RectKt {
+ method public static inline infix android.graphics.Rect and(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline infix android.graphics.RectF and(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator int component1(android.graphics.Rect);
+ method public static inline operator float component1(android.graphics.RectF);
+ method public static inline operator int component2(android.graphics.Rect);
+ method public static inline operator float component2(android.graphics.RectF);
+ method public static inline operator int component3(android.graphics.Rect);
+ method public static inline operator float component3(android.graphics.RectF);
+ method public static inline operator int component4(android.graphics.Rect);
+ method public static inline operator float component4(android.graphics.RectF);
+ method public static inline operator boolean contains(android.graphics.Rect, android.graphics.Point p);
+ method public static inline operator boolean contains(android.graphics.RectF, android.graphics.PointF p);
+ method public static inline operator android.graphics.Rect minus(android.graphics.Rect, android.graphics.Point xy);
+ method public static inline operator android.graphics.Region minus(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline operator android.graphics.Rect minus(android.graphics.Rect, int xy);
+ method public static inline operator android.graphics.RectF minus(android.graphics.RectF, android.graphics.PointF xy);
+ method public static inline operator android.graphics.Region minus(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator android.graphics.RectF minus(android.graphics.RectF, float xy);
+ method public static inline infix android.graphics.Rect or(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline infix android.graphics.RectF or(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator android.graphics.Rect plus(android.graphics.Rect, android.graphics.Point xy);
+ method public static inline operator android.graphics.Rect plus(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline operator android.graphics.Rect plus(android.graphics.Rect, int xy);
+ method public static inline operator android.graphics.RectF plus(android.graphics.RectF, android.graphics.PointF xy);
+ method public static inline operator android.graphics.RectF plus(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator android.graphics.RectF plus(android.graphics.RectF, float xy);
+ method public static inline operator android.graphics.Rect times(android.graphics.Rect, int factor);
+ method public static inline operator android.graphics.RectF times(android.graphics.RectF, float factor);
+ method public static inline operator android.graphics.RectF times(android.graphics.RectF, int factor);
+ method public static inline android.graphics.Rect toRect(android.graphics.RectF);
+ method public static inline android.graphics.RectF toRectF(android.graphics.Rect);
+ method public static inline android.graphics.Region toRegion(android.graphics.Rect);
+ method public static inline android.graphics.Region toRegion(android.graphics.RectF);
+ method public static inline android.graphics.RectF transform(android.graphics.RectF, android.graphics.Matrix m);
+ method public static inline infix android.graphics.Region xor(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region xor(android.graphics.RectF, android.graphics.RectF r);
+ }
+
+ public final class RegionKt {
+ method public static inline infix android.graphics.Region and(android.graphics.Region, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region and(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator boolean contains(android.graphics.Region, android.graphics.Point p);
+ method public static inline void forEach(android.graphics.Region, kotlin.jvm.functions.Function1<? super android.graphics.Rect,kotlin.Unit> action);
+ method public static operator java.util.Iterator<android.graphics.Rect> iterator(android.graphics.Region);
+ method public static inline operator android.graphics.Region minus(android.graphics.Region, android.graphics.Rect r);
+ method public static inline operator android.graphics.Region minus(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator android.graphics.Region not(android.graphics.Region);
+ method public static inline infix android.graphics.Region or(android.graphics.Region, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region or(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator android.graphics.Region plus(android.graphics.Region, android.graphics.Rect r);
+ method public static inline operator android.graphics.Region plus(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator android.graphics.Region unaryMinus(android.graphics.Region);
+ method public static inline infix android.graphics.Region xor(android.graphics.Region, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region xor(android.graphics.Region, android.graphics.Region r);
+ }
+
+ public final class ShaderKt {
+ method public static inline void transform(android.graphics.Shader, kotlin.jvm.functions.Function1<? super android.graphics.Matrix,kotlin.Unit> block);
+ }
+
+}
+
+package androidx.core.graphics.drawable {
+
+ public final class BitmapDrawableKt {
+ method public static inline android.graphics.drawable.BitmapDrawable toDrawable(android.graphics.Bitmap, android.content.res.Resources resources);
+ }
+
+ public final class ColorDrawableKt {
+ method @RequiresApi(26) public static inline android.graphics.drawable.ColorDrawable toDrawable(android.graphics.Color);
+ method public static inline android.graphics.drawable.ColorDrawable toDrawable(@ColorInt int);
+ }
+
+ public final class DrawableKt {
+ method public static android.graphics.Bitmap toBitmap(android.graphics.drawable.Drawable, optional @Px int width, optional @Px int height, optional android.graphics.Bitmap.Config? config);
+ method public static android.graphics.Bitmap? toBitmapOrNull(android.graphics.drawable.Drawable, optional @Px int width, optional @Px int height, optional android.graphics.Bitmap.Config? config);
+ method public static void updateBounds(android.graphics.drawable.Drawable, optional @Px int left, optional @Px int top, optional @Px int right, optional @Px int bottom);
+ }
+
+ public final class IconKt {
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toAdaptiveIcon(android.graphics.Bitmap);
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toIcon(android.graphics.Bitmap);
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toIcon(android.net.Uri);
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toIcon(byte[]);
+ }
+
+}
+
+package androidx.core.location {
+
+ public final class LocationKt {
+ method public static inline operator double component1(android.location.Location);
+ method public static inline operator double component2(android.location.Location);
+ }
+
+}
+
+package androidx.core.net {
+
+ public final class UriKt {
+ method public static java.io.File toFile(android.net.Uri);
+ method public static inline android.net.Uri toUri(java.io.File);
+ method public static inline android.net.Uri toUri(String);
+ }
+
+}
+
+package androidx.core.os {
+
+ public final class BundleKt {
+ method public static android.os.Bundle bundleOf();
+ method public static android.os.Bundle bundleOf(kotlin.Pair<java.lang.String,?>... pairs);
+ }
+
+ public final class HandlerKt {
+ method public static inline Runnable postAtTime(android.os.Handler, long uptimeMillis, optional Object? token, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static inline Runnable postDelayed(android.os.Handler, long delayInMillis, optional Object? token, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ }
+
+ @RequiresApi(31) public final class OutcomeReceiverKt {
+ method @RequiresApi(31) public static <R, E extends java.lang.Throwable> android.os.OutcomeReceiver<R,E> asOutcomeReceiver(kotlin.coroutines.Continuation<? super R>);
+ }
+
+ public final class PersistableBundleKt {
+ method @RequiresApi(21) public static android.os.PersistableBundle persistableBundleOf();
+ method @RequiresApi(21) public static android.os.PersistableBundle persistableBundleOf(kotlin.Pair<java.lang.String,?>... pairs);
+ method @RequiresApi(21) public static android.os.PersistableBundle toPersistableBundle(java.util.Map<java.lang.String,?>);
+ }
+
+ public final class TraceKt {
+ method @Deprecated public static inline <T> T trace(String sectionName, kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+}
+
+package androidx.core.text {
+
+ public final class CharSequenceKt {
+ method public static inline boolean isDigitsOnly(CharSequence);
+ method public static inline int trimmedLength(CharSequence);
+ }
+
+ public final class HtmlKt {
+ method public static inline android.text.Spanned parseAsHtml(String, optional int flags, optional android.text.Html.ImageGetter? imageGetter, optional android.text.Html.TagHandler? tagHandler);
+ method public static inline String toHtml(android.text.Spanned, optional int option);
+ }
+
+ public final class LocaleKt {
+ method public static inline int getLayoutDirection(java.util.Locale);
+ }
+
+ public final class SpannableStringBuilderKt {
+ method public static inline android.text.SpannableStringBuilder backgroundColor(android.text.SpannableStringBuilder, @ColorInt int color, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder bold(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannedString buildSpannedString(kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder color(android.text.SpannableStringBuilder, @ColorInt int color, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder inSpans(android.text.SpannableStringBuilder, Object span, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder inSpans(android.text.SpannableStringBuilder, Object[] spans, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder italic(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder scale(android.text.SpannableStringBuilder, float proportion, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder strikeThrough(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder subscript(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder superscript(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder underline(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ }
+
+ public final class SpannableStringKt {
+ method public static inline void clearSpans(android.text.Spannable);
+ method public static inline operator void set(android.text.Spannable, int start, int end, Object span);
+ method public static inline operator void set(android.text.Spannable, kotlin.ranges.IntRange range, Object span);
+ method public static inline android.text.Spannable toSpannable(CharSequence);
+ }
+
+ public final class SpannedStringKt {
+ method public static inline <reified T> T[] getSpans(android.text.Spanned, optional int start, optional int end);
+ method public static inline android.text.Spanned toSpanned(CharSequence);
+ }
+
+ public final class StringKt {
+ method public static inline String htmlEncode(String);
+ }
+
+}
+
+package androidx.core.transition {
+
+ public final class TransitionKt {
+ method public static inline android.transition.Transition.TransitionListener addListener(android.transition.Transition, optional kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onEnd, optional kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onStart, optional kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onCancel, optional kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onResume, optional kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onPause);
+ method public static inline android.transition.Transition.TransitionListener doOnCancel(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method public static inline android.transition.Transition.TransitionListener doOnEnd(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method public static inline android.transition.Transition.TransitionListener doOnPause(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method public static inline android.transition.Transition.TransitionListener doOnResume(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method public static inline android.transition.Transition.TransitionListener doOnStart(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ }
+
+}
+
+package androidx.core.util {
+
+ public final class AndroidXConsumerKt {
+ method public static <T> androidx.core.util.Consumer<T> asAndroidXConsumer(kotlin.coroutines.Continuation<? super T>);
+ }
+
+ public final class AtomicFileKt {
+ method public static inline byte[] readBytes(android.util.AtomicFile);
+ method public static String readText(android.util.AtomicFile, optional java.nio.charset.Charset charset);
+ method public static inline void tryWrite(android.util.AtomicFile, kotlin.jvm.functions.Function1<? super java.io.FileOutputStream,kotlin.Unit> block);
+ method public static void writeBytes(android.util.AtomicFile, byte[] array);
+ method public static void writeText(android.util.AtomicFile, String text, optional java.nio.charset.Charset charset);
+ }
+
+ @RequiresApi(24) public final class ConsumerKt {
+ method @RequiresApi(24) public static <T> java.util.function.Consumer<T> asConsumer(kotlin.coroutines.Continuation<? super T>);
+ }
+
+ public final class HalfKt {
+ method @RequiresApi(26) public static inline android.util.Half toHalf(double);
+ method @RequiresApi(26) public static inline android.util.Half toHalf(float);
+ method @RequiresApi(26) public static inline android.util.Half toHalf(String);
+ method @RequiresApi(26) public static inline android.util.Half toHalf(@HalfFloat short);
+ }
+
+ public final class LongSparseArrayKt {
+ method public static inline operator <T> boolean contains(android.util.LongSparseArray<T>, long key);
+ method public static inline <T> boolean containsKey(android.util.LongSparseArray<T>, long key);
+ method public static inline <T> boolean containsValue(android.util.LongSparseArray<T>, T value);
+ method public static inline <T> void forEach(android.util.LongSparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Long,? super T,kotlin.Unit> action);
+ method public static inline <T> T getOrDefault(android.util.LongSparseArray<T>, long key, T defaultValue);
+ method public static inline <T> T getOrElse(android.util.LongSparseArray<T>, long key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(android.util.LongSparseArray<T>);
+ method public static inline <T> boolean isEmpty(android.util.LongSparseArray<T>);
+ method public static inline <T> boolean isNotEmpty(android.util.LongSparseArray<T>);
+ method public static <T> kotlin.collections.LongIterator keyIterator(android.util.LongSparseArray<T>);
+ method public static operator <T> android.util.LongSparseArray<T> plus(android.util.LongSparseArray<T>, android.util.LongSparseArray<T> other);
+ method public static <T> void putAll(android.util.LongSparseArray<T>, android.util.LongSparseArray<T> other);
+ method public static <T> boolean remove(android.util.LongSparseArray<T>, long key, T value);
+ method public static inline operator <T> void set(android.util.LongSparseArray<T>, long key, T value);
+ method public static <T> java.util.Iterator<T> valueIterator(android.util.LongSparseArray<T>);
+ }
+
+ public final class LruCacheKt {
+ method public static inline <K, V> android.util.LruCache<K,V> lruCache(int maxSize, optional kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf, optional kotlin.jvm.functions.Function1<? super K,? extends V?> create, optional kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V?,kotlin.Unit> onEntryRemoved);
+ }
+
+ public final class PairKt {
+ method public static inline operator <F, S> F component1(android.util.Pair<F,S>);
+ method public static inline operator <F, S> F component1(androidx.core.util.Pair<F,S>);
+ method public static inline operator <F, S> S component2(android.util.Pair<F,S>);
+ method public static inline operator <F, S> S component2(androidx.core.util.Pair<F,S>);
+ method public static inline <F, S> android.util.Pair<F,S> toAndroidPair(kotlin.Pair<? extends F,? extends S>);
+ method public static inline <F, S> androidx.core.util.Pair<F,S> toAndroidXPair(kotlin.Pair<? extends F,? extends S>);
+ method public static inline <F, S> kotlin.Pair<F,S> toKotlinPair(android.util.Pair<F,S>);
+ method public static inline <F, S> kotlin.Pair<F,S> toKotlinPair(androidx.core.util.Pair<F,S>);
+ }
+
+ public final class RangeKt {
+ method @RequiresApi(21) public static inline infix <T extends java.lang.Comparable<? super T>> android.util.Range<T> and(android.util.Range<T>, android.util.Range<T> other);
+ method @RequiresApi(21) public static inline operator <T extends java.lang.Comparable<? super T>> android.util.Range<T> plus(android.util.Range<T>, android.util.Range<T> other);
+ method @RequiresApi(21) public static inline operator <T extends java.lang.Comparable<? super T>> android.util.Range<T> plus(android.util.Range<T>, T value);
+ method @RequiresApi(21) public static inline infix <T extends java.lang.Comparable<? super T>> android.util.Range<T> rangeTo(T, T that);
+ method @RequiresApi(21) public static <T extends java.lang.Comparable<? super T>> kotlin.ranges.ClosedRange<T> toClosedRange(android.util.Range<T>);
+ method @RequiresApi(21) public static <T extends java.lang.Comparable<? super T>> android.util.Range<T> toRange(kotlin.ranges.ClosedRange<T>);
+ }
+
+ public final class RunnableKt {
+ method public static Runnable asRunnable(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ }
+
+ public final class SizeKt {
+ method @RequiresApi(21) public static inline operator int component1(android.util.Size);
+ method @RequiresApi(21) public static inline operator float component1(android.util.SizeF);
+ method public static inline operator float component1(androidx.core.util.SizeFCompat);
+ method @RequiresApi(21) public static inline operator int component2(android.util.Size);
+ method @RequiresApi(21) public static inline operator float component2(android.util.SizeF);
+ method public static inline operator float component2(androidx.core.util.SizeFCompat);
+ }
+
+ public final class SparseArrayKt {
+ method public static inline operator <T> boolean contains(android.util.SparseArray<T>, int key);
+ method public static inline <T> boolean containsKey(android.util.SparseArray<T>, int key);
+ method public static inline <T> boolean containsValue(android.util.SparseArray<T>, T value);
+ method public static inline <T> void forEach(android.util.SparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+ method public static inline <T> T getOrDefault(android.util.SparseArray<T>, int key, T defaultValue);
+ method public static inline <T> T getOrElse(android.util.SparseArray<T>, int key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(android.util.SparseArray<T>);
+ method public static inline <T> boolean isEmpty(android.util.SparseArray<T>);
+ method public static inline <T> boolean isNotEmpty(android.util.SparseArray<T>);
+ method public static <T> kotlin.collections.IntIterator keyIterator(android.util.SparseArray<T>);
+ method public static operator <T> android.util.SparseArray<T> plus(android.util.SparseArray<T>, android.util.SparseArray<T> other);
+ method public static <T> void putAll(android.util.SparseArray<T>, android.util.SparseArray<T> other);
+ method public static <T> boolean remove(android.util.SparseArray<T>, int key, T value);
+ method public static inline operator <T> void set(android.util.SparseArray<T>, int key, T value);
+ method public static <T> java.util.Iterator<T> valueIterator(android.util.SparseArray<T>);
+ }
+
+ public final class SparseBooleanArrayKt {
+ method public static inline operator boolean contains(android.util.SparseBooleanArray, int key);
+ method public static inline boolean containsKey(android.util.SparseBooleanArray, int key);
+ method public static inline boolean containsValue(android.util.SparseBooleanArray, boolean value);
+ method public static inline void forEach(android.util.SparseBooleanArray, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> action);
+ method public static inline boolean getOrDefault(android.util.SparseBooleanArray, int key, boolean defaultValue);
+ method public static inline boolean getOrElse(android.util.SparseBooleanArray, int key, kotlin.jvm.functions.Function0<java.lang.Boolean> defaultValue);
+ method public static inline int getSize(android.util.SparseBooleanArray);
+ method public static inline boolean isEmpty(android.util.SparseBooleanArray);
+ method public static inline boolean isNotEmpty(android.util.SparseBooleanArray);
+ method public static kotlin.collections.IntIterator keyIterator(android.util.SparseBooleanArray);
+ method public static operator android.util.SparseBooleanArray plus(android.util.SparseBooleanArray, android.util.SparseBooleanArray other);
+ method public static void putAll(android.util.SparseBooleanArray, android.util.SparseBooleanArray other);
+ method public static boolean remove(android.util.SparseBooleanArray, int key, boolean value);
+ method public static inline operator void set(android.util.SparseBooleanArray, int key, boolean value);
+ method public static kotlin.collections.BooleanIterator valueIterator(android.util.SparseBooleanArray);
+ }
+
+ public final class SparseIntArrayKt {
+ method public static inline operator boolean contains(android.util.SparseIntArray, int key);
+ method public static inline boolean containsKey(android.util.SparseIntArray, int key);
+ method public static inline boolean containsValue(android.util.SparseIntArray, int value);
+ method public static inline void forEach(android.util.SparseIntArray, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> action);
+ method public static inline int getOrDefault(android.util.SparseIntArray, int key, int defaultValue);
+ method public static inline int getOrElse(android.util.SparseIntArray, int key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+ method public static inline int getSize(android.util.SparseIntArray);
+ method public static inline boolean isEmpty(android.util.SparseIntArray);
+ method public static inline boolean isNotEmpty(android.util.SparseIntArray);
+ method public static kotlin.collections.IntIterator keyIterator(android.util.SparseIntArray);
+ method public static operator android.util.SparseIntArray plus(android.util.SparseIntArray, android.util.SparseIntArray other);
+ method public static void putAll(android.util.SparseIntArray, android.util.SparseIntArray other);
+ method public static boolean remove(android.util.SparseIntArray, int key, int value);
+ method public static inline operator void set(android.util.SparseIntArray, int key, int value);
+ method public static kotlin.collections.IntIterator valueIterator(android.util.SparseIntArray);
+ }
+
+ public final class SparseLongArrayKt {
+ method public static inline operator boolean contains(android.util.SparseLongArray, int key);
+ method public static inline boolean containsKey(android.util.SparseLongArray, int key);
+ method public static inline boolean containsValue(android.util.SparseLongArray, long value);
+ method public static inline void forEach(android.util.SparseLongArray, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,kotlin.Unit> action);
+ method public static inline long getOrDefault(android.util.SparseLongArray, int key, long defaultValue);
+ method public static inline long getOrElse(android.util.SparseLongArray, int key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+ method public static inline int getSize(android.util.SparseLongArray);
+ method public static inline boolean isEmpty(android.util.SparseLongArray);
+ method public static inline boolean isNotEmpty(android.util.SparseLongArray);
+ method public static kotlin.collections.IntIterator keyIterator(android.util.SparseLongArray);
+ method public static operator android.util.SparseLongArray plus(android.util.SparseLongArray, android.util.SparseLongArray other);
+ method public static void putAll(android.util.SparseLongArray, android.util.SparseLongArray other);
+ method public static boolean remove(android.util.SparseLongArray, int key, long value);
+ method public static inline operator void set(android.util.SparseLongArray, int key, long value);
+ method public static kotlin.collections.LongIterator valueIterator(android.util.SparseLongArray);
+ }
+
+}
+
+package androidx.core.view {
+
+ public final class MenuKt {
+ method public static operator boolean contains(android.view.Menu, android.view.MenuItem item);
+ method public static inline void forEach(android.view.Menu, kotlin.jvm.functions.Function1<? super android.view.MenuItem,kotlin.Unit> action);
+ method public static inline void forEachIndexed(android.view.Menu, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super android.view.MenuItem,kotlin.Unit> action);
+ method public static inline operator android.view.MenuItem get(android.view.Menu, int index);
+ method public static kotlin.sequences.Sequence<android.view.MenuItem> getChildren(android.view.Menu);
+ method public static inline int getSize(android.view.Menu);
+ method public static inline boolean isEmpty(android.view.Menu);
+ method public static inline boolean isNotEmpty(android.view.Menu);
+ method public static operator java.util.Iterator<android.view.MenuItem> iterator(android.view.Menu);
+ method public static inline operator void minusAssign(android.view.Menu, android.view.MenuItem item);
+ method public static inline void removeItemAt(android.view.Menu, int index);
+ }
+
+ public final class ViewGroupKt {
+ method public static inline operator boolean contains(android.view.ViewGroup, android.view.View view);
+ method public static inline void forEach(android.view.ViewGroup, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline void forEachIndexed(android.view.ViewGroup, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super android.view.View,kotlin.Unit> action);
+ method public static operator android.view.View get(android.view.ViewGroup, int index);
+ method public static kotlin.sequences.Sequence<android.view.View> getChildren(android.view.ViewGroup);
+ method public static kotlin.sequences.Sequence<android.view.View> getDescendants(android.view.ViewGroup);
+ method public static inline kotlin.ranges.IntRange getIndices(android.view.ViewGroup);
+ method public static inline int getSize(android.view.ViewGroup);
+ method public static inline boolean isEmpty(android.view.ViewGroup);
+ method public static inline boolean isNotEmpty(android.view.ViewGroup);
+ method public static operator java.util.Iterator<android.view.View> iterator(android.view.ViewGroup);
+ method public static inline operator void minusAssign(android.view.ViewGroup, android.view.View view);
+ method public static inline operator void plusAssign(android.view.ViewGroup, android.view.View view);
+ method public static inline void setMargins(android.view.ViewGroup.MarginLayoutParams, @Px int size);
+ method public static inline void updateMargins(android.view.ViewGroup.MarginLayoutParams, optional @Px int left, optional @Px int top, optional @Px int right, optional @Px int bottom);
+ method public static inline void updateMarginsRelative(android.view.ViewGroup.MarginLayoutParams, optional @Px int start, optional @Px int top, optional @Px int end, optional @Px int bottom);
+ }
+
+ public final class ViewKt {
+ method public static inline void doOnAttach(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline void doOnDetach(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline void doOnLayout(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline void doOnNextLayout(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline androidx.core.view.OneShotPreDrawListener doOnPreDraw(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static android.graphics.Bitmap drawToBitmap(android.view.View, optional android.graphics.Bitmap.Config config);
+ method public static kotlin.sequences.Sequence<android.view.View> getAllViews(android.view.View);
+ method public static kotlin.sequences.Sequence<android.view.ViewParent> getAncestors(android.view.View);
+ method public static inline int getMarginBottom(android.view.View);
+ method public static inline int getMarginEnd(android.view.View);
+ method public static inline int getMarginLeft(android.view.View);
+ method public static inline int getMarginRight(android.view.View);
+ method public static inline int getMarginStart(android.view.View);
+ method public static inline int getMarginTop(android.view.View);
+ method public static inline boolean isGone(android.view.View);
+ method public static inline boolean isInvisible(android.view.View);
+ method public static inline boolean isVisible(android.view.View);
+ method public static inline Runnable postDelayed(android.view.View, long delayInMillis, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static Runnable postOnAnimationDelayed(android.view.View, long delayInMillis, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static inline void setGone(android.view.View, boolean);
+ method public static inline void setInvisible(android.view.View, boolean);
+ method public static inline void setPadding(android.view.View, @Px int size);
+ method public static inline void setVisible(android.view.View, boolean);
+ method public static inline void updateLayoutParams(android.view.View, kotlin.jvm.functions.Function1<? super android.view.ViewGroup.LayoutParams,kotlin.Unit> block);
+ method public static inline <reified T extends android.view.ViewGroup.LayoutParams> void updateLayoutParamsTyped(android.view.View, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> block);
+ method public static inline void updatePadding(android.view.View, optional @Px int left, optional @Px int top, optional @Px int right, optional @Px int bottom);
+ method public static inline void updatePaddingRelative(android.view.View, optional @Px int start, optional @Px int top, optional @Px int end, optional @Px int bottom);
+ }
+
+}
+
+package androidx.core.widget {
+
+ public final class TextViewKt {
+ method public static inline android.text.TextWatcher addTextChangedListener(android.widget.TextView, optional kotlin.jvm.functions.Function4<? super java.lang.CharSequence?,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> beforeTextChanged, optional kotlin.jvm.functions.Function4<? super java.lang.CharSequence?,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> onTextChanged, optional kotlin.jvm.functions.Function1<? super android.text.Editable?,kotlin.Unit> afterTextChanged);
+ method public static inline android.text.TextWatcher doAfterTextChanged(android.widget.TextView, kotlin.jvm.functions.Function1<? super android.text.Editable?,kotlin.Unit> action);
+ method public static inline android.text.TextWatcher doBeforeTextChanged(android.widget.TextView, kotlin.jvm.functions.Function4<? super java.lang.CharSequence?,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> action);
+ method public static inline android.text.TextWatcher doOnTextChanged(android.widget.TextView, kotlin.jvm.functions.Function4<? super java.lang.CharSequence?,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> action);
+ }
+
+}
+
diff --git a/core/core-ktx/api/res-1.13.0-beta01.txt b/core/core-ktx/api/res-1.13.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/core-ktx/api/res-1.13.0-beta01.txt
diff --git a/core/core-ktx/api/restricted_1.13.0-beta01.txt b/core/core-ktx/api/restricted_1.13.0-beta01.txt
new file mode 100644
index 0000000..93481c1
--- /dev/null
+++ b/core/core-ktx/api/restricted_1.13.0-beta01.txt
@@ -0,0 +1,634 @@
+// Signature format: 4.0
+package androidx.core.animation {
+
+ public final class AnimatorKt {
+ method public static inline android.animation.Animator.AnimatorListener addListener(android.animation.Animator, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onEnd, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onStart, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onCancel, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onRepeat);
+ method public static android.animation.Animator.AnimatorPauseListener addPauseListener(android.animation.Animator, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onResume, optional kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onPause);
+ method public static inline android.animation.Animator.AnimatorListener doOnCancel(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static inline android.animation.Animator.AnimatorListener doOnEnd(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static android.animation.Animator.AnimatorPauseListener doOnPause(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static inline android.animation.Animator.AnimatorListener doOnRepeat(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static android.animation.Animator.AnimatorPauseListener doOnResume(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static inline android.animation.Animator.AnimatorListener doOnStart(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ }
+
+}
+
+package androidx.core.content {
+
+ public final class ContentValuesKt {
+ method public static android.content.ContentValues contentValuesOf(kotlin.Pair<java.lang.String,?>... pairs);
+ }
+
+ public final class ContextKt {
+ method public static inline <reified T> T? getSystemService(android.content.Context);
+ method public static inline void withStyledAttributes(android.content.Context, optional android.util.AttributeSet? set, int[] attrs, optional @AttrRes int defStyleAttr, optional @StyleRes int defStyleRes, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,kotlin.Unit> block);
+ method public static inline void withStyledAttributes(android.content.Context, @StyleRes int resourceId, int[] attrs, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,kotlin.Unit> block);
+ }
+
+ public final class SharedPreferencesKt {
+ method public static inline void edit(android.content.SharedPreferences, optional boolean commit, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action);
+ }
+
+}
+
+package androidx.core.content.res {
+
+ public final class TypedArrayKt {
+ method public static boolean getBooleanOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @ColorInt public static int getColorOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static android.content.res.ColorStateList getColorStateListOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static float getDimensionOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @Dimension public static int getDimensionPixelOffsetOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @Dimension public static int getDimensionPixelSizeOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static android.graphics.drawable.Drawable getDrawableOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static float getFloatOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @RequiresApi(26) public static android.graphics.Typeface getFontOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static int getIntOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static int getIntegerOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @AnyRes public static int getResourceIdOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static String getStringOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static CharSequence[] getTextArrayOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static CharSequence getTextOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static inline <R> R use(android.content.res.TypedArray, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,? extends R> block);
+ }
+
+}
+
+package androidx.core.database {
+
+ public final class CursorKt {
+ method public static inline byte[]? getBlobOrNull(android.database.Cursor, int index);
+ method public static inline Double? getDoubleOrNull(android.database.Cursor, int index);
+ method public static inline Float? getFloatOrNull(android.database.Cursor, int index);
+ method public static inline Integer? getIntOrNull(android.database.Cursor, int index);
+ method public static inline Long? getLongOrNull(android.database.Cursor, int index);
+ method public static inline Short? getShortOrNull(android.database.Cursor, int index);
+ method public static inline String? getStringOrNull(android.database.Cursor, int index);
+ }
+
+}
+
+package androidx.core.database.sqlite {
+
+ public final class SQLiteDatabaseKt {
+ method public static inline <T> T transaction(android.database.sqlite.SQLiteDatabase, optional boolean exclusive, kotlin.jvm.functions.Function1<? super android.database.sqlite.SQLiteDatabase,? extends T> body);
+ }
+
+}
+
+package androidx.core.graphics {
+
+ public final class BitmapKt {
+ method public static inline android.graphics.Bitmap applyCanvas(android.graphics.Bitmap, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline operator boolean contains(android.graphics.Bitmap, android.graphics.Point p);
+ method public static inline operator boolean contains(android.graphics.Bitmap, android.graphics.PointF p);
+ method public static inline android.graphics.Bitmap createBitmap(int width, int height, optional android.graphics.Bitmap.Config config);
+ method @RequiresApi(26) public static inline android.graphics.Bitmap createBitmap(int width, int height, optional android.graphics.Bitmap.Config config, optional boolean hasAlpha, optional android.graphics.ColorSpace colorSpace);
+ method public static inline operator int get(android.graphics.Bitmap, int x, int y);
+ method public static inline android.graphics.Bitmap scale(android.graphics.Bitmap, int width, int height, optional boolean filter);
+ method public static inline operator void set(android.graphics.Bitmap, int x, int y, @ColorInt int color);
+ }
+
+ public final class CanvasKt {
+ method public static inline void withClip(android.graphics.Canvas, android.graphics.Path clipPath, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, android.graphics.Rect clipRect, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, android.graphics.RectF clipRect, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, float left, float top, float right, float bottom, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, int left, int top, int right, int bottom, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withMatrix(android.graphics.Canvas, optional android.graphics.Matrix matrix, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withRotation(android.graphics.Canvas, optional float degrees, optional float pivotX, optional float pivotY, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withSave(android.graphics.Canvas, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withScale(android.graphics.Canvas, optional float x, optional float y, optional float pivotX, optional float pivotY, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withSkew(android.graphics.Canvas, optional float x, optional float y, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withTranslation(android.graphics.Canvas, optional float x, optional float y, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ }
+
+ public final class ColorKt {
+ method @RequiresApi(26) public static inline operator float component1(android.graphics.Color);
+ method public static inline operator int component1(@ColorInt int);
+ method @RequiresApi(26) public static inline operator float component1(@ColorLong long);
+ method @RequiresApi(26) public static inline operator float component2(android.graphics.Color);
+ method public static inline operator int component2(@ColorInt int);
+ method @RequiresApi(26) public static inline operator float component2(@ColorLong long);
+ method @RequiresApi(26) public static inline operator float component3(android.graphics.Color);
+ method public static inline operator int component3(@ColorInt int);
+ method @RequiresApi(26) public static inline operator float component3(@ColorLong long);
+ method @RequiresApi(26) public static inline operator float component4(android.graphics.Color);
+ method public static inline operator int component4(@ColorInt int);
+ method @RequiresApi(26) public static inline operator float component4(@ColorLong long);
+ method @RequiresApi(26) public static inline infix android.graphics.Color convertTo(android.graphics.Color, android.graphics.ColorSpace colorSpace);
+ method @RequiresApi(26) public static inline infix android.graphics.Color convertTo(android.graphics.Color, android.graphics.ColorSpace.Named colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(@ColorInt int, android.graphics.ColorSpace colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(@ColorInt int, android.graphics.ColorSpace.Named colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(@ColorLong long, android.graphics.ColorSpace colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(@ColorLong long, android.graphics.ColorSpace.Named colorSpace);
+ method public static inline int getAlpha(@ColorInt int);
+ method @RequiresApi(26) public static inline float getAlpha(@ColorLong long);
+ method public static inline int getBlue(@ColorInt int);
+ method @RequiresApi(26) public static inline float getBlue(@ColorLong long);
+ method @RequiresApi(26) public static inline android.graphics.ColorSpace getColorSpace(@ColorLong long);
+ method public static inline int getGreen(@ColorInt int);
+ method @RequiresApi(26) public static inline float getGreen(@ColorLong long);
+ method @RequiresApi(26) public static inline float getLuminance(@ColorInt int);
+ method @RequiresApi(26) public static inline float getLuminance(@ColorLong long);
+ method public static inline int getRed(@ColorInt int);
+ method @RequiresApi(26) public static inline float getRed(@ColorLong long);
+ method @RequiresApi(26) public static inline boolean isSrgb(@ColorLong long);
+ method @RequiresApi(26) public static inline boolean isWideGamut(@ColorLong long);
+ method @RequiresApi(26) public static operator android.graphics.Color plus(android.graphics.Color, android.graphics.Color c);
+ method @RequiresApi(26) public static inline android.graphics.Color toColor(@ColorInt int);
+ method @RequiresApi(26) public static inline android.graphics.Color toColor(@ColorLong long);
+ method @ColorInt public static inline int toColorInt(String);
+ method @ColorInt @RequiresApi(26) public static inline int toColorInt(@ColorLong long);
+ method @ColorLong @RequiresApi(26) public static inline long toColorLong(@ColorInt int);
+ }
+
+ public final class ImageDecoderKt {
+ method @RequiresApi(28) public static inline android.graphics.Bitmap decodeBitmap(android.graphics.ImageDecoder.Source, kotlin.jvm.functions.Function3<? super android.graphics.ImageDecoder,? super android.graphics.ImageDecoder.ImageInfo,? super android.graphics.ImageDecoder.Source,kotlin.Unit> action);
+ method @RequiresApi(28) public static inline android.graphics.drawable.Drawable decodeDrawable(android.graphics.ImageDecoder.Source, kotlin.jvm.functions.Function3<? super android.graphics.ImageDecoder,? super android.graphics.ImageDecoder.ImageInfo,? super android.graphics.ImageDecoder.Source,kotlin.Unit> action);
+ }
+
+ public final class MatrixKt {
+ method public static android.graphics.Matrix rotationMatrix(float degrees, optional float px, optional float py);
+ method public static android.graphics.Matrix scaleMatrix(optional float sx, optional float sy);
+ method public static inline operator android.graphics.Matrix times(android.graphics.Matrix, android.graphics.Matrix m);
+ method public static android.graphics.Matrix translationMatrix(optional float tx, optional float ty);
+ method public static inline float[] values(android.graphics.Matrix);
+ }
+
+ public final class PaintKt {
+ method public static inline boolean setBlendMode(android.graphics.Paint, androidx.core.graphics.BlendModeCompat? blendModeCompat);
+ }
+
+ public final class PathKt {
+ method public static inline infix android.graphics.Path and(android.graphics.Path, android.graphics.Path p);
+ method @RequiresApi(26) public static Iterable<androidx.core.graphics.PathSegment> flatten(android.graphics.Path, optional float error);
+ method public static inline operator android.graphics.Path minus(android.graphics.Path, android.graphics.Path p);
+ method public static inline infix android.graphics.Path or(android.graphics.Path, android.graphics.Path p);
+ method public static inline operator android.graphics.Path plus(android.graphics.Path, android.graphics.Path p);
+ method public static inline infix android.graphics.Path xor(android.graphics.Path, android.graphics.Path p);
+ }
+
+ public final class PictureKt {
+ method public static inline android.graphics.Picture record(android.graphics.Picture, int width, int height, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ }
+
+ public final class PointKt {
+ method public static inline operator int component1(android.graphics.Point);
+ method public static inline operator float component1(android.graphics.PointF);
+ method public static inline operator int component2(android.graphics.Point);
+ method public static inline operator float component2(android.graphics.PointF);
+ method public static inline operator android.graphics.Point div(android.graphics.Point, float scalar);
+ method public static inline operator android.graphics.PointF div(android.graphics.PointF, float scalar);
+ method public static inline operator android.graphics.Point minus(android.graphics.Point, android.graphics.Point p);
+ method public static inline operator android.graphics.Point minus(android.graphics.Point, int xy);
+ method public static inline operator android.graphics.PointF minus(android.graphics.PointF, android.graphics.PointF p);
+ method public static inline operator android.graphics.PointF minus(android.graphics.PointF, float xy);
+ method public static inline operator android.graphics.Point plus(android.graphics.Point, android.graphics.Point p);
+ method public static inline operator android.graphics.Point plus(android.graphics.Point, int xy);
+ method public static inline operator android.graphics.PointF plus(android.graphics.PointF, android.graphics.PointF p);
+ method public static inline operator android.graphics.PointF plus(android.graphics.PointF, float xy);
+ method public static inline operator android.graphics.Point times(android.graphics.Point, float scalar);
+ method public static inline operator android.graphics.PointF times(android.graphics.PointF, float scalar);
+ method public static inline android.graphics.Point toPoint(android.graphics.PointF);
+ method public static inline android.graphics.PointF toPointF(android.graphics.Point);
+ method public static inline operator android.graphics.Point unaryMinus(android.graphics.Point);
+ method public static inline operator android.graphics.PointF unaryMinus(android.graphics.PointF);
+ }
+
+ public final class PorterDuffKt {
+ method public static inline android.graphics.PorterDuffColorFilter toColorFilter(android.graphics.PorterDuff.Mode, int color);
+ method public static inline android.graphics.PorterDuffXfermode toXfermode(android.graphics.PorterDuff.Mode);
+ }
+
+ public final class RectKt {
+ method public static inline infix android.graphics.Rect and(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline infix android.graphics.RectF and(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator int component1(android.graphics.Rect);
+ method public static inline operator float component1(android.graphics.RectF);
+ method public static inline operator int component2(android.graphics.Rect);
+ method public static inline operator float component2(android.graphics.RectF);
+ method public static inline operator int component3(android.graphics.Rect);
+ method public static inline operator float component3(android.graphics.RectF);
+ method public static inline operator int component4(android.graphics.Rect);
+ method public static inline operator float component4(android.graphics.RectF);
+ method public static inline operator boolean contains(android.graphics.Rect, android.graphics.Point p);
+ method public static inline operator boolean contains(android.graphics.RectF, android.graphics.PointF p);
+ method public static inline operator android.graphics.Rect minus(android.graphics.Rect, android.graphics.Point xy);
+ method public static inline operator android.graphics.Region minus(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline operator android.graphics.Rect minus(android.graphics.Rect, int xy);
+ method public static inline operator android.graphics.RectF minus(android.graphics.RectF, android.graphics.PointF xy);
+ method public static inline operator android.graphics.Region minus(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator android.graphics.RectF minus(android.graphics.RectF, float xy);
+ method public static inline infix android.graphics.Rect or(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline infix android.graphics.RectF or(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator android.graphics.Rect plus(android.graphics.Rect, android.graphics.Point xy);
+ method public static inline operator android.graphics.Rect plus(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline operator android.graphics.Rect plus(android.graphics.Rect, int xy);
+ method public static inline operator android.graphics.RectF plus(android.graphics.RectF, android.graphics.PointF xy);
+ method public static inline operator android.graphics.RectF plus(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator android.graphics.RectF plus(android.graphics.RectF, float xy);
+ method public static inline operator android.graphics.Rect times(android.graphics.Rect, int factor);
+ method public static inline operator android.graphics.RectF times(android.graphics.RectF, float factor);
+ method public static inline operator android.graphics.RectF times(android.graphics.RectF, int factor);
+ method public static inline android.graphics.Rect toRect(android.graphics.RectF);
+ method public static inline android.graphics.RectF toRectF(android.graphics.Rect);
+ method public static inline android.graphics.Region toRegion(android.graphics.Rect);
+ method public static inline android.graphics.Region toRegion(android.graphics.RectF);
+ method public static inline android.graphics.RectF transform(android.graphics.RectF, android.graphics.Matrix m);
+ method public static inline infix android.graphics.Region xor(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region xor(android.graphics.RectF, android.graphics.RectF r);
+ }
+
+ public final class RegionKt {
+ method public static inline infix android.graphics.Region and(android.graphics.Region, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region and(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator boolean contains(android.graphics.Region, android.graphics.Point p);
+ method public static inline void forEach(android.graphics.Region, kotlin.jvm.functions.Function1<? super android.graphics.Rect,kotlin.Unit> action);
+ method public static operator java.util.Iterator<android.graphics.Rect> iterator(android.graphics.Region);
+ method public static inline operator android.graphics.Region minus(android.graphics.Region, android.graphics.Rect r);
+ method public static inline operator android.graphics.Region minus(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator android.graphics.Region not(android.graphics.Region);
+ method public static inline infix android.graphics.Region or(android.graphics.Region, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region or(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator android.graphics.Region plus(android.graphics.Region, android.graphics.Rect r);
+ method public static inline operator android.graphics.Region plus(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator android.graphics.Region unaryMinus(android.graphics.Region);
+ method public static inline infix android.graphics.Region xor(android.graphics.Region, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region xor(android.graphics.Region, android.graphics.Region r);
+ }
+
+ public final class ShaderKt {
+ method public static inline void transform(android.graphics.Shader, kotlin.jvm.functions.Function1<? super android.graphics.Matrix,kotlin.Unit> block);
+ }
+
+}
+
+package androidx.core.graphics.drawable {
+
+ public final class BitmapDrawableKt {
+ method public static inline android.graphics.drawable.BitmapDrawable toDrawable(android.graphics.Bitmap, android.content.res.Resources resources);
+ }
+
+ public final class ColorDrawableKt {
+ method @RequiresApi(26) public static inline android.graphics.drawable.ColorDrawable toDrawable(android.graphics.Color);
+ method public static inline android.graphics.drawable.ColorDrawable toDrawable(@ColorInt int);
+ }
+
+ public final class DrawableKt {
+ method public static android.graphics.Bitmap toBitmap(android.graphics.drawable.Drawable, optional @Px int width, optional @Px int height, optional android.graphics.Bitmap.Config? config);
+ method public static android.graphics.Bitmap? toBitmapOrNull(android.graphics.drawable.Drawable, optional @Px int width, optional @Px int height, optional android.graphics.Bitmap.Config? config);
+ method public static void updateBounds(android.graphics.drawable.Drawable, optional @Px int left, optional @Px int top, optional @Px int right, optional @Px int bottom);
+ }
+
+ public final class IconKt {
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toAdaptiveIcon(android.graphics.Bitmap);
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toIcon(android.graphics.Bitmap);
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toIcon(android.net.Uri);
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toIcon(byte[]);
+ }
+
+}
+
+package androidx.core.location {
+
+ public final class LocationKt {
+ method public static inline operator double component1(android.location.Location);
+ method public static inline operator double component2(android.location.Location);
+ }
+
+}
+
+package androidx.core.net {
+
+ public final class UriKt {
+ method public static java.io.File toFile(android.net.Uri);
+ method public static inline android.net.Uri toUri(java.io.File);
+ method public static inline android.net.Uri toUri(String);
+ }
+
+}
+
+package androidx.core.os {
+
+ public final class BundleKt {
+ method public static android.os.Bundle bundleOf();
+ method public static android.os.Bundle bundleOf(kotlin.Pair<java.lang.String,?>... pairs);
+ }
+
+ public final class HandlerKt {
+ method public static inline Runnable postAtTime(android.os.Handler, long uptimeMillis, optional Object? token, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static inline Runnable postDelayed(android.os.Handler, long delayInMillis, optional Object? token, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ }
+
+ @RequiresApi(31) public final class OutcomeReceiverKt {
+ method @RequiresApi(31) public static <R, E extends java.lang.Throwable> android.os.OutcomeReceiver<R,E> asOutcomeReceiver(kotlin.coroutines.Continuation<? super R>);
+ }
+
+ public final class PersistableBundleKt {
+ method @RequiresApi(21) public static android.os.PersistableBundle persistableBundleOf();
+ method @RequiresApi(21) public static android.os.PersistableBundle persistableBundleOf(kotlin.Pair<java.lang.String,?>... pairs);
+ method @RequiresApi(21) public static android.os.PersistableBundle toPersistableBundle(java.util.Map<java.lang.String,?>);
+ }
+
+ public final class TraceKt {
+ method @Deprecated public static inline <T> T trace(String sectionName, kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+}
+
+package androidx.core.text {
+
+ public final class CharSequenceKt {
+ method public static inline boolean isDigitsOnly(CharSequence);
+ method public static inline int trimmedLength(CharSequence);
+ }
+
+ public final class HtmlKt {
+ method public static inline android.text.Spanned parseAsHtml(String, optional int flags, optional android.text.Html.ImageGetter? imageGetter, optional android.text.Html.TagHandler? tagHandler);
+ method public static inline String toHtml(android.text.Spanned, optional int option);
+ }
+
+ public final class LocaleKt {
+ method public static inline int getLayoutDirection(java.util.Locale);
+ }
+
+ public final class SpannableStringBuilderKt {
+ method public static inline android.text.SpannableStringBuilder backgroundColor(android.text.SpannableStringBuilder, @ColorInt int color, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder bold(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannedString buildSpannedString(kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder color(android.text.SpannableStringBuilder, @ColorInt int color, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder inSpans(android.text.SpannableStringBuilder, Object span, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder inSpans(android.text.SpannableStringBuilder, Object[] spans, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder italic(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder scale(android.text.SpannableStringBuilder, float proportion, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder strikeThrough(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder subscript(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder superscript(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder underline(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ }
+
+ public final class SpannableStringKt {
+ method public static inline void clearSpans(android.text.Spannable);
+ method public static inline operator void set(android.text.Spannable, int start, int end, Object span);
+ method public static inline operator void set(android.text.Spannable, kotlin.ranges.IntRange range, Object span);
+ method public static inline android.text.Spannable toSpannable(CharSequence);
+ }
+
+ public final class SpannedStringKt {
+ method public static inline <reified T> T[] getSpans(android.text.Spanned, optional int start, optional int end);
+ method public static inline android.text.Spanned toSpanned(CharSequence);
+ }
+
+ public final class StringKt {
+ method public static inline String htmlEncode(String);
+ }
+
+}
+
+package androidx.core.transition {
+
+ public final class TransitionKt {
+ method public static inline android.transition.Transition.TransitionListener addListener(android.transition.Transition, optional kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onEnd, optional kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onStart, optional kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onCancel, optional kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onResume, optional kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onPause);
+ method public static inline android.transition.Transition.TransitionListener doOnCancel(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method public static inline android.transition.Transition.TransitionListener doOnEnd(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method public static inline android.transition.Transition.TransitionListener doOnPause(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method public static inline android.transition.Transition.TransitionListener doOnResume(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method public static inline android.transition.Transition.TransitionListener doOnStart(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ }
+
+}
+
+package androidx.core.util {
+
+ public final class AndroidXConsumerKt {
+ method public static <T> androidx.core.util.Consumer<T> asAndroidXConsumer(kotlin.coroutines.Continuation<? super T>);
+ }
+
+ public final class AtomicFileKt {
+ method public static inline byte[] readBytes(android.util.AtomicFile);
+ method public static String readText(android.util.AtomicFile, optional java.nio.charset.Charset charset);
+ method public static inline void tryWrite(android.util.AtomicFile, kotlin.jvm.functions.Function1<? super java.io.FileOutputStream,kotlin.Unit> block);
+ method public static void writeBytes(android.util.AtomicFile, byte[] array);
+ method public static void writeText(android.util.AtomicFile, String text, optional java.nio.charset.Charset charset);
+ }
+
+ @RequiresApi(24) public final class ConsumerKt {
+ method @RequiresApi(24) public static <T> java.util.function.Consumer<T> asConsumer(kotlin.coroutines.Continuation<? super T>);
+ }
+
+ public final class HalfKt {
+ method @RequiresApi(26) public static inline android.util.Half toHalf(double);
+ method @RequiresApi(26) public static inline android.util.Half toHalf(float);
+ method @RequiresApi(26) public static inline android.util.Half toHalf(String);
+ method @RequiresApi(26) public static inline android.util.Half toHalf(@HalfFloat short);
+ }
+
+ public final class LongSparseArrayKt {
+ method public static inline operator <T> boolean contains(android.util.LongSparseArray<T>, long key);
+ method public static inline <T> boolean containsKey(android.util.LongSparseArray<T>, long key);
+ method public static inline <T> boolean containsValue(android.util.LongSparseArray<T>, T value);
+ method public static inline <T> void forEach(android.util.LongSparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Long,? super T,kotlin.Unit> action);
+ method public static inline <T> T getOrDefault(android.util.LongSparseArray<T>, long key, T defaultValue);
+ method public static inline <T> T getOrElse(android.util.LongSparseArray<T>, long key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(android.util.LongSparseArray<T>);
+ method public static inline <T> boolean isEmpty(android.util.LongSparseArray<T>);
+ method public static inline <T> boolean isNotEmpty(android.util.LongSparseArray<T>);
+ method public static <T> kotlin.collections.LongIterator keyIterator(android.util.LongSparseArray<T>);
+ method public static operator <T> android.util.LongSparseArray<T> plus(android.util.LongSparseArray<T>, android.util.LongSparseArray<T> other);
+ method public static <T> void putAll(android.util.LongSparseArray<T>, android.util.LongSparseArray<T> other);
+ method public static <T> boolean remove(android.util.LongSparseArray<T>, long key, T value);
+ method public static inline operator <T> void set(android.util.LongSparseArray<T>, long key, T value);
+ method public static <T> java.util.Iterator<T> valueIterator(android.util.LongSparseArray<T>);
+ }
+
+ public final class LruCacheKt {
+ method public static inline <K, V> android.util.LruCache<K,V> lruCache(int maxSize, optional kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf, optional kotlin.jvm.functions.Function1<? super K,? extends V?> create, optional kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V?,kotlin.Unit> onEntryRemoved);
+ }
+
+ public final class PairKt {
+ method public static inline operator <F, S> F component1(android.util.Pair<F,S>);
+ method public static inline operator <F, S> F component1(androidx.core.util.Pair<F,S>);
+ method public static inline operator <F, S> S component2(android.util.Pair<F,S>);
+ method public static inline operator <F, S> S component2(androidx.core.util.Pair<F,S>);
+ method public static inline <F, S> android.util.Pair<F,S> toAndroidPair(kotlin.Pair<? extends F,? extends S>);
+ method public static inline <F, S> androidx.core.util.Pair<F,S> toAndroidXPair(kotlin.Pair<? extends F,? extends S>);
+ method public static inline <F, S> kotlin.Pair<F,S> toKotlinPair(android.util.Pair<F,S>);
+ method public static inline <F, S> kotlin.Pair<F,S> toKotlinPair(androidx.core.util.Pair<F,S>);
+ }
+
+ public final class RangeKt {
+ method @RequiresApi(21) public static inline infix <T extends java.lang.Comparable<? super T>> android.util.Range<T> and(android.util.Range<T>, android.util.Range<T> other);
+ method @RequiresApi(21) public static inline operator <T extends java.lang.Comparable<? super T>> android.util.Range<T> plus(android.util.Range<T>, android.util.Range<T> other);
+ method @RequiresApi(21) public static inline operator <T extends java.lang.Comparable<? super T>> android.util.Range<T> plus(android.util.Range<T>, T value);
+ method @RequiresApi(21) public static inline infix <T extends java.lang.Comparable<? super T>> android.util.Range<T> rangeTo(T, T that);
+ method @RequiresApi(21) public static <T extends java.lang.Comparable<? super T>> kotlin.ranges.ClosedRange<T> toClosedRange(android.util.Range<T>);
+ method @RequiresApi(21) public static <T extends java.lang.Comparable<? super T>> android.util.Range<T> toRange(kotlin.ranges.ClosedRange<T>);
+ }
+
+ public final class RunnableKt {
+ method public static Runnable asRunnable(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ }
+
+ public final class SizeKt {
+ method @RequiresApi(21) public static inline operator int component1(android.util.Size);
+ method @RequiresApi(21) public static inline operator float component1(android.util.SizeF);
+ method public static inline operator float component1(androidx.core.util.SizeFCompat);
+ method @RequiresApi(21) public static inline operator int component2(android.util.Size);
+ method @RequiresApi(21) public static inline operator float component2(android.util.SizeF);
+ method public static inline operator float component2(androidx.core.util.SizeFCompat);
+ }
+
+ public final class SparseArrayKt {
+ method public static inline operator <T> boolean contains(android.util.SparseArray<T>, int key);
+ method public static inline <T> boolean containsKey(android.util.SparseArray<T>, int key);
+ method public static inline <T> boolean containsValue(android.util.SparseArray<T>, T value);
+ method public static inline <T> void forEach(android.util.SparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+ method public static inline <T> T getOrDefault(android.util.SparseArray<T>, int key, T defaultValue);
+ method public static inline <T> T getOrElse(android.util.SparseArray<T>, int key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(android.util.SparseArray<T>);
+ method public static inline <T> boolean isEmpty(android.util.SparseArray<T>);
+ method public static inline <T> boolean isNotEmpty(android.util.SparseArray<T>);
+ method public static <T> kotlin.collections.IntIterator keyIterator(android.util.SparseArray<T>);
+ method public static operator <T> android.util.SparseArray<T> plus(android.util.SparseArray<T>, android.util.SparseArray<T> other);
+ method public static <T> void putAll(android.util.SparseArray<T>, android.util.SparseArray<T> other);
+ method public static <T> boolean remove(android.util.SparseArray<T>, int key, T value);
+ method public static inline operator <T> void set(android.util.SparseArray<T>, int key, T value);
+ method public static <T> java.util.Iterator<T> valueIterator(android.util.SparseArray<T>);
+ }
+
+ public final class SparseBooleanArrayKt {
+ method public static inline operator boolean contains(android.util.SparseBooleanArray, int key);
+ method public static inline boolean containsKey(android.util.SparseBooleanArray, int key);
+ method public static inline boolean containsValue(android.util.SparseBooleanArray, boolean value);
+ method public static inline void forEach(android.util.SparseBooleanArray, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> action);
+ method public static inline boolean getOrDefault(android.util.SparseBooleanArray, int key, boolean defaultValue);
+ method public static inline boolean getOrElse(android.util.SparseBooleanArray, int key, kotlin.jvm.functions.Function0<java.lang.Boolean> defaultValue);
+ method public static inline int getSize(android.util.SparseBooleanArray);
+ method public static inline boolean isEmpty(android.util.SparseBooleanArray);
+ method public static inline boolean isNotEmpty(android.util.SparseBooleanArray);
+ method public static kotlin.collections.IntIterator keyIterator(android.util.SparseBooleanArray);
+ method public static operator android.util.SparseBooleanArray plus(android.util.SparseBooleanArray, android.util.SparseBooleanArray other);
+ method public static void putAll(android.util.SparseBooleanArray, android.util.SparseBooleanArray other);
+ method public static boolean remove(android.util.SparseBooleanArray, int key, boolean value);
+ method public static inline operator void set(android.util.SparseBooleanArray, int key, boolean value);
+ method public static kotlin.collections.BooleanIterator valueIterator(android.util.SparseBooleanArray);
+ }
+
+ public final class SparseIntArrayKt {
+ method public static inline operator boolean contains(android.util.SparseIntArray, int key);
+ method public static inline boolean containsKey(android.util.SparseIntArray, int key);
+ method public static inline boolean containsValue(android.util.SparseIntArray, int value);
+ method public static inline void forEach(android.util.SparseIntArray, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> action);
+ method public static inline int getOrDefault(android.util.SparseIntArray, int key, int defaultValue);
+ method public static inline int getOrElse(android.util.SparseIntArray, int key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+ method public static inline int getSize(android.util.SparseIntArray);
+ method public static inline boolean isEmpty(android.util.SparseIntArray);
+ method public static inline boolean isNotEmpty(android.util.SparseIntArray);
+ method public static kotlin.collections.IntIterator keyIterator(android.util.SparseIntArray);
+ method public static operator android.util.SparseIntArray plus(android.util.SparseIntArray, android.util.SparseIntArray other);
+ method public static void putAll(android.util.SparseIntArray, android.util.SparseIntArray other);
+ method public static boolean remove(android.util.SparseIntArray, int key, int value);
+ method public static inline operator void set(android.util.SparseIntArray, int key, int value);
+ method public static kotlin.collections.IntIterator valueIterator(android.util.SparseIntArray);
+ }
+
+ public final class SparseLongArrayKt {
+ method public static inline operator boolean contains(android.util.SparseLongArray, int key);
+ method public static inline boolean containsKey(android.util.SparseLongArray, int key);
+ method public static inline boolean containsValue(android.util.SparseLongArray, long value);
+ method public static inline void forEach(android.util.SparseLongArray, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,kotlin.Unit> action);
+ method public static inline long getOrDefault(android.util.SparseLongArray, int key, long defaultValue);
+ method public static inline long getOrElse(android.util.SparseLongArray, int key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+ method public static inline int getSize(android.util.SparseLongArray);
+ method public static inline boolean isEmpty(android.util.SparseLongArray);
+ method public static inline boolean isNotEmpty(android.util.SparseLongArray);
+ method public static kotlin.collections.IntIterator keyIterator(android.util.SparseLongArray);
+ method public static operator android.util.SparseLongArray plus(android.util.SparseLongArray, android.util.SparseLongArray other);
+ method public static void putAll(android.util.SparseLongArray, android.util.SparseLongArray other);
+ method public static boolean remove(android.util.SparseLongArray, int key, long value);
+ method public static inline operator void set(android.util.SparseLongArray, int key, long value);
+ method public static kotlin.collections.LongIterator valueIterator(android.util.SparseLongArray);
+ }
+
+}
+
+package androidx.core.view {
+
+ public final class MenuKt {
+ method public static operator boolean contains(android.view.Menu, android.view.MenuItem item);
+ method public static inline void forEach(android.view.Menu, kotlin.jvm.functions.Function1<? super android.view.MenuItem,kotlin.Unit> action);
+ method public static inline void forEachIndexed(android.view.Menu, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super android.view.MenuItem,kotlin.Unit> action);
+ method public static inline operator android.view.MenuItem get(android.view.Menu, int index);
+ method public static kotlin.sequences.Sequence<android.view.MenuItem> getChildren(android.view.Menu);
+ method public static inline int getSize(android.view.Menu);
+ method public static inline boolean isEmpty(android.view.Menu);
+ method public static inline boolean isNotEmpty(android.view.Menu);
+ method public static operator java.util.Iterator<android.view.MenuItem> iterator(android.view.Menu);
+ method public static inline operator void minusAssign(android.view.Menu, android.view.MenuItem item);
+ method public static inline void removeItemAt(android.view.Menu, int index);
+ }
+
+ public final class ViewGroupKt {
+ method public static inline operator boolean contains(android.view.ViewGroup, android.view.View view);
+ method public static inline void forEach(android.view.ViewGroup, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline void forEachIndexed(android.view.ViewGroup, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super android.view.View,kotlin.Unit> action);
+ method public static operator android.view.View get(android.view.ViewGroup, int index);
+ method public static kotlin.sequences.Sequence<android.view.View> getChildren(android.view.ViewGroup);
+ method public static kotlin.sequences.Sequence<android.view.View> getDescendants(android.view.ViewGroup);
+ method public static inline kotlin.ranges.IntRange getIndices(android.view.ViewGroup);
+ method public static inline int getSize(android.view.ViewGroup);
+ method public static inline boolean isEmpty(android.view.ViewGroup);
+ method public static inline boolean isNotEmpty(android.view.ViewGroup);
+ method public static operator java.util.Iterator<android.view.View> iterator(android.view.ViewGroup);
+ method public static inline operator void minusAssign(android.view.ViewGroup, android.view.View view);
+ method public static inline operator void plusAssign(android.view.ViewGroup, android.view.View view);
+ method public static inline void setMargins(android.view.ViewGroup.MarginLayoutParams, @Px int size);
+ method public static inline void updateMargins(android.view.ViewGroup.MarginLayoutParams, optional @Px int left, optional @Px int top, optional @Px int right, optional @Px int bottom);
+ method public static inline void updateMarginsRelative(android.view.ViewGroup.MarginLayoutParams, optional @Px int start, optional @Px int top, optional @Px int end, optional @Px int bottom);
+ }
+
+ public final class ViewKt {
+ method public static inline void doOnAttach(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline void doOnDetach(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline void doOnLayout(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline void doOnNextLayout(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline androidx.core.view.OneShotPreDrawListener doOnPreDraw(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static android.graphics.Bitmap drawToBitmap(android.view.View, optional android.graphics.Bitmap.Config config);
+ method public static kotlin.sequences.Sequence<android.view.View> getAllViews(android.view.View);
+ method public static kotlin.sequences.Sequence<android.view.ViewParent> getAncestors(android.view.View);
+ method public static inline int getMarginBottom(android.view.View);
+ method public static inline int getMarginEnd(android.view.View);
+ method public static inline int getMarginLeft(android.view.View);
+ method public static inline int getMarginRight(android.view.View);
+ method public static inline int getMarginStart(android.view.View);
+ method public static inline int getMarginTop(android.view.View);
+ method public static inline boolean isGone(android.view.View);
+ method public static inline boolean isInvisible(android.view.View);
+ method public static inline boolean isVisible(android.view.View);
+ method public static inline Runnable postDelayed(android.view.View, long delayInMillis, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static Runnable postOnAnimationDelayed(android.view.View, long delayInMillis, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static inline void setGone(android.view.View, boolean);
+ method public static inline void setInvisible(android.view.View, boolean);
+ method public static inline void setPadding(android.view.View, @Px int size);
+ method public static inline void setVisible(android.view.View, boolean);
+ method public static inline void updateLayoutParams(android.view.View, kotlin.jvm.functions.Function1<? super android.view.ViewGroup.LayoutParams,kotlin.Unit> block);
+ method public static inline <reified T extends android.view.ViewGroup.LayoutParams> void updateLayoutParamsTyped(android.view.View, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> block);
+ method public static inline void updatePadding(android.view.View, optional @Px int left, optional @Px int top, optional @Px int right, optional @Px int bottom);
+ method public static inline void updatePaddingRelative(android.view.View, optional @Px int start, optional @Px int top, optional @Px int end, optional @Px int bottom);
+ }
+
+}
+
+package androidx.core.widget {
+
+ public final class TextViewKt {
+ method public static inline android.text.TextWatcher addTextChangedListener(android.widget.TextView, optional kotlin.jvm.functions.Function4<? super java.lang.CharSequence?,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> beforeTextChanged, optional kotlin.jvm.functions.Function4<? super java.lang.CharSequence?,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> onTextChanged, optional kotlin.jvm.functions.Function1<? super android.text.Editable?,kotlin.Unit> afterTextChanged);
+ method public static inline android.text.TextWatcher doAfterTextChanged(android.widget.TextView, kotlin.jvm.functions.Function1<? super android.text.Editable?,kotlin.Unit> action);
+ method public static inline android.text.TextWatcher doBeforeTextChanged(android.widget.TextView, kotlin.jvm.functions.Function4<? super java.lang.CharSequence?,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> action);
+ method public static inline android.text.TextWatcher doOnTextChanged(android.widget.TextView, kotlin.jvm.functions.Function4<? super java.lang.CharSequence?,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> action);
+ }
+
+}
+
diff --git a/core/core-ktx/build.gradle b/core/core-ktx/build.gradle
index 24a1d25..6832072 100644
--- a/core/core-ktx/build.gradle
+++ b/core/core-ktx/build.gradle
@@ -5,7 +5,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -34,7 +34,7 @@
androidx {
name = "Core Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
mavenVersion = LibraryVersions.CORE
inceptionYear = "2018"
description = "Kotlin extensions for 'core' artifact"
diff --git a/core/core-testing/api/1.13.0-beta01.txt b/core/core-testing/api/1.13.0-beta01.txt
new file mode 100644
index 0000000..40c6d07
--- /dev/null
+++ b/core/core-testing/api/1.13.0-beta01.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.core.testing.util {
+
+ public final class TestConsumer<T> implements androidx.core.util.Consumer<T> {
+ ctor public TestConsumer();
+ method public void accept(T t);
+ method public void assertValues(java.util.List<? extends T> values);
+ }
+
+}
+
diff --git a/core/core-testing/api/res-1.13.0-beta01.txt b/core/core-testing/api/res-1.13.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/core-testing/api/res-1.13.0-beta01.txt
diff --git a/core/core-testing/api/restricted_1.13.0-beta01.txt b/core/core-testing/api/restricted_1.13.0-beta01.txt
new file mode 100644
index 0000000..40c6d07
--- /dev/null
+++ b/core/core-testing/api/restricted_1.13.0-beta01.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.core.testing.util {
+
+ public final class TestConsumer<T> implements androidx.core.util.Consumer<T> {
+ ctor public TestConsumer();
+ method public void accept(T t);
+ method public void assertValues(java.util.List<? extends T> values);
+ }
+
+}
+
diff --git a/core/core/api/1.13.0-beta01.txt b/core/core/api/1.13.0-beta01.txt
new file mode 100644
index 0000000..6d8023a
--- /dev/null
+++ b/core/core/api/1.13.0-beta01.txt
@@ -0,0 +1,4277 @@
+// Signature format: 4.0
+package androidx.core.accessibilityservice {
+
+ public final class AccessibilityServiceInfoCompat {
+ method public static String capabilityToString(int);
+ method public static String feedbackTypeToString(int);
+ method public static String? flagToString(int);
+ method public static int getCapabilities(android.accessibilityservice.AccessibilityServiceInfo);
+ method public static String? loadDescription(android.accessibilityservice.AccessibilityServiceInfo, android.content.pm.PackageManager);
+ field public static final int CAPABILITY_CAN_FILTER_KEY_EVENTS = 8; // 0x8
+ field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
+ field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
+ field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1
+ field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
+ field public static final int FEEDBACK_BRAILLE = 32; // 0x20
+ field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
+ field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
+ field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
+ field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
+ field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
+ }
+
+}
+
+package androidx.core.app {
+
+ public class ActivityCompat extends androidx.core.content.ContextCompat {
+ ctor protected ActivityCompat();
+ method public static void finishAffinity(android.app.Activity);
+ method public static void finishAfterTransition(android.app.Activity);
+ method public static android.net.Uri? getReferrer(android.app.Activity);
+ method @Deprecated public static boolean invalidateOptionsMenu(android.app.Activity!);
+ method public static boolean isLaunchedFromBubble(android.app.Activity);
+ method public static void postponeEnterTransition(android.app.Activity);
+ method public static void recreate(android.app.Activity);
+ method public static androidx.core.view.DragAndDropPermissionsCompat? requestDragAndDropPermissions(android.app.Activity, android.view.DragEvent);
+ method public static void requestPermissions(android.app.Activity, String![], @IntRange(from=0) int);
+ method public static <T extends android.view.View> T requireViewById(android.app.Activity, @IdRes int);
+ method public static void setEnterSharedElementCallback(android.app.Activity, androidx.core.app.SharedElementCallback?);
+ method public static void setExitSharedElementCallback(android.app.Activity, androidx.core.app.SharedElementCallback?);
+ method public static void setLocusContext(android.app.Activity, androidx.core.content.LocusIdCompat?, android.os.Bundle?);
+ method public static void setPermissionCompatDelegate(androidx.core.app.ActivityCompat.PermissionCompatDelegate?);
+ method public static boolean shouldShowRequestPermissionRationale(android.app.Activity, String);
+ method public static void startActivityForResult(android.app.Activity, android.content.Intent, int, android.os.Bundle?);
+ method public static void startIntentSenderForResult(android.app.Activity, android.content.IntentSender, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+ method public static void startPostponedEnterTransition(android.app.Activity);
+ }
+
+ public static interface ActivityCompat.OnRequestPermissionsResultCallback {
+ method public void onRequestPermissionsResult(int, String![], int[]);
+ }
+
+ public static interface ActivityCompat.PermissionCompatDelegate {
+ method public boolean onActivityResult(android.app.Activity, @IntRange(from=0) int, int, android.content.Intent?);
+ method public boolean requestPermissions(android.app.Activity, String![], @IntRange(from=0) int);
+ }
+
+ public final class ActivityManagerCompat {
+ method public static boolean isLowRamDevice(android.app.ActivityManager);
+ }
+
+ public class ActivityOptionsCompat {
+ ctor protected ActivityOptionsCompat();
+ method public android.graphics.Rect? getLaunchBounds();
+ method public static androidx.core.app.ActivityOptionsCompat makeBasic();
+ method public static androidx.core.app.ActivityOptionsCompat makeClipRevealAnimation(android.view.View, int, int, int, int);
+ method public static androidx.core.app.ActivityOptionsCompat makeCustomAnimation(android.content.Context, int, int);
+ method public static androidx.core.app.ActivityOptionsCompat makeScaleUpAnimation(android.view.View, int, int, int, int);
+ method public static androidx.core.app.ActivityOptionsCompat makeSceneTransitionAnimation(android.app.Activity, android.view.View, String);
+ method public static androidx.core.app.ActivityOptionsCompat makeSceneTransitionAnimation(android.app.Activity, androidx.core.util.Pair<android.view.View!,java.lang.String!>!...?);
+ method public static androidx.core.app.ActivityOptionsCompat makeTaskLaunchBehind();
+ method public static androidx.core.app.ActivityOptionsCompat makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
+ method public void requestUsageTimeReport(android.app.PendingIntent);
+ method public androidx.core.app.ActivityOptionsCompat setLaunchBounds(android.graphics.Rect?);
+ method public androidx.core.app.ActivityOptionsCompat setShareIdentityEnabled(boolean);
+ method public android.os.Bundle? toBundle();
+ method public void update(androidx.core.app.ActivityOptionsCompat);
+ field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
+ field public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
+ }
+
+ public final class AlarmManagerCompat {
+ method public static boolean canScheduleExactAlarms(android.app.AlarmManager);
+ method public static void setAlarmClock(android.app.AlarmManager, long, android.app.PendingIntent, android.app.PendingIntent);
+ method public static void setAndAllowWhileIdle(android.app.AlarmManager, int, long, android.app.PendingIntent);
+ method public static void setExact(android.app.AlarmManager, int, long, android.app.PendingIntent);
+ method public static void setExactAndAllowWhileIdle(android.app.AlarmManager, int, long, android.app.PendingIntent);
+ }
+
+ @RequiresApi(28) public class AppComponentFactory extends android.app.AppComponentFactory {
+ ctor public AppComponentFactory();
+ method public final android.app.Activity instantiateActivity(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.app.Activity instantiateActivityCompat(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.app.Application instantiateApplication(ClassLoader, String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.app.Application instantiateApplicationCompat(ClassLoader, String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.content.ContentProvider instantiateProvider(ClassLoader, String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.content.ContentProvider instantiateProviderCompat(ClassLoader, String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.content.BroadcastReceiver instantiateReceiver(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.content.BroadcastReceiver instantiateReceiverCompat(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.app.Service instantiateService(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.app.Service instantiateServiceCompat(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ }
+
+ public class AppLaunchChecker {
+ ctor @Deprecated public AppLaunchChecker();
+ method public static boolean hasStartedFromLauncher(android.content.Context);
+ method public static void onActivityCreate(android.app.Activity);
+ }
+
+ public final class AppOpsManagerCompat {
+ method public static int checkOrNoteProxyOp(android.content.Context, int, String, String);
+ method public static int noteOp(android.content.Context, String, int, String);
+ method public static int noteOpNoThrow(android.content.Context, String, int, String);
+ method public static int noteProxyOp(android.content.Context, String, String);
+ method public static int noteProxyOpNoThrow(android.content.Context, String, String);
+ method public static String? permissionToOp(String);
+ field public static final int MODE_ALLOWED = 0; // 0x0
+ field public static final int MODE_DEFAULT = 3; // 0x3
+ field public static final int MODE_ERRORED = 2; // 0x2
+ field public static final int MODE_IGNORED = 1; // 0x1
+ }
+
+ @Deprecated public final class BundleCompat {
+ method @Deprecated public static android.os.IBinder? getBinder(android.os.Bundle, String?);
+ method @Deprecated public static void putBinder(android.os.Bundle, String?, android.os.IBinder?);
+ }
+
+ public class DialogCompat {
+ method public static android.view.View requireViewById(android.app.Dialog, int);
+ }
+
+ public class FrameMetricsAggregator {
+ ctor public FrameMetricsAggregator();
+ ctor public FrameMetricsAggregator(int);
+ method public void add(android.app.Activity);
+ method public android.util.SparseIntArray![]? getMetrics();
+ method public android.util.SparseIntArray![]? remove(android.app.Activity);
+ method public android.util.SparseIntArray![]? reset();
+ method public android.util.SparseIntArray![]? stop();
+ field public static final int ANIMATION_DURATION = 256; // 0x100
+ field public static final int ANIMATION_INDEX = 8; // 0x8
+ field public static final int COMMAND_DURATION = 32; // 0x20
+ field public static final int COMMAND_INDEX = 5; // 0x5
+ field public static final int DELAY_DURATION = 128; // 0x80
+ field public static final int DELAY_INDEX = 7; // 0x7
+ field public static final int DRAW_DURATION = 8; // 0x8
+ field public static final int DRAW_INDEX = 3; // 0x3
+ field public static final int EVERY_DURATION = 511; // 0x1ff
+ field public static final int INPUT_DURATION = 2; // 0x2
+ field public static final int INPUT_INDEX = 1; // 0x1
+ field public static final int LAYOUT_MEASURE_DURATION = 4; // 0x4
+ field public static final int LAYOUT_MEASURE_INDEX = 2; // 0x2
+ field public static final int SWAP_DURATION = 64; // 0x40
+ field public static final int SWAP_INDEX = 6; // 0x6
+ field public static final int SYNC_DURATION = 16; // 0x10
+ field public static final int SYNC_INDEX = 4; // 0x4
+ field public static final int TOTAL_DURATION = 1; // 0x1
+ field public static final int TOTAL_INDEX = 0; // 0x0
+ }
+
+ public final class GrammaticalInflectionManagerCompat {
+ method @AnyThread public static int getApplicationGrammaticalGender(android.content.Context);
+ method @AnyThread public static void setRequestedApplicationGrammaticalGender(android.content.Context, int);
+ field public static final int GRAMMATICAL_GENDER_FEMININE = 2; // 0x2
+ field public static final int GRAMMATICAL_GENDER_MASCULINE = 3; // 0x3
+ field public static final int GRAMMATICAL_GENDER_NEUTRAL = 1; // 0x1
+ field public static final int GRAMMATICAL_GENDER_NOT_SPECIFIED = 0; // 0x0
+ }
+
+ @Deprecated public abstract class JobIntentService extends android.app.Service {
+ ctor @Deprecated public JobIntentService();
+ method @Deprecated public static void enqueueWork(android.content.Context, android.content.ComponentName, int, android.content.Intent);
+ method @Deprecated public static void enqueueWork(android.content.Context, Class<?>, int, android.content.Intent);
+ method @Deprecated public boolean isStopped();
+ method @Deprecated public android.os.IBinder! onBind(android.content.Intent);
+ method @Deprecated protected abstract void onHandleWork(android.content.Intent);
+ method @Deprecated public boolean onStopCurrentWork();
+ method @Deprecated public void setInterruptIfStopped(boolean);
+ }
+
+ public final class LocaleManagerCompat {
+ method @AnyThread public static androidx.core.os.LocaleListCompat getApplicationLocales(android.content.Context);
+ method @AnyThread public static androidx.core.os.LocaleListCompat getSystemLocales(android.content.Context);
+ }
+
+ public final class MultiWindowModeChangedInfo {
+ ctor public MultiWindowModeChangedInfo(boolean isInMultiWindowMode);
+ ctor @RequiresApi(26) public MultiWindowModeChangedInfo(boolean isInMultiWindowMode, android.content.res.Configuration newConfig);
+ method @RequiresApi(26) public android.content.res.Configuration getNewConfig();
+ method public boolean isInMultiWindowMode();
+ property public final boolean isInMultiWindowMode;
+ property @RequiresApi(26) public final android.content.res.Configuration newConfig;
+ }
+
+ public final class NavUtils {
+ method public static android.content.Intent? getParentActivityIntent(android.app.Activity);
+ method public static android.content.Intent? getParentActivityIntent(android.content.Context, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static android.content.Intent? getParentActivityIntent(android.content.Context, Class<?>) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static String? getParentActivityName(android.app.Activity);
+ method public static String? getParentActivityName(android.content.Context, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static void navigateUpFromSameTask(android.app.Activity);
+ method public static void navigateUpTo(android.app.Activity, android.content.Intent);
+ method public static boolean shouldUpRecreateTask(android.app.Activity, android.content.Intent);
+ field public static final String PARENT_ACTIVITY = "android.support.PARENT_ACTIVITY";
+ }
+
+ public class NotificationChannelCompat {
+ method public boolean canBubble();
+ method public boolean canBypassDnd();
+ method public boolean canShowBadge();
+ method public android.media.AudioAttributes? getAudioAttributes();
+ method public String? getConversationId();
+ method public String? getDescription();
+ method public String? getGroup();
+ method public String getId();
+ method public int getImportance();
+ method public int getLightColor();
+ method public int getLockscreenVisibility();
+ method public CharSequence? getName();
+ method public String? getParentChannelId();
+ method public android.net.Uri? getSound();
+ method public long[]? getVibrationPattern();
+ method public boolean isImportantConversation();
+ method public boolean shouldShowLights();
+ method public boolean shouldVibrate();
+ method public androidx.core.app.NotificationChannelCompat.Builder toBuilder();
+ field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
+ }
+
+ public static class NotificationChannelCompat.Builder {
+ ctor public NotificationChannelCompat.Builder(String, int);
+ method public androidx.core.app.NotificationChannelCompat build();
+ method public androidx.core.app.NotificationChannelCompat.Builder setConversationId(String, String);
+ method public androidx.core.app.NotificationChannelCompat.Builder setDescription(String?);
+ method public androidx.core.app.NotificationChannelCompat.Builder setGroup(String?);
+ method public androidx.core.app.NotificationChannelCompat.Builder setImportance(int);
+ method public androidx.core.app.NotificationChannelCompat.Builder setLightColor(int);
+ method public androidx.core.app.NotificationChannelCompat.Builder setLightsEnabled(boolean);
+ method public androidx.core.app.NotificationChannelCompat.Builder setName(CharSequence?);
+ method public androidx.core.app.NotificationChannelCompat.Builder setShowBadge(boolean);
+ method public androidx.core.app.NotificationChannelCompat.Builder setSound(android.net.Uri?, android.media.AudioAttributes?);
+ method public androidx.core.app.NotificationChannelCompat.Builder setVibrationEnabled(boolean);
+ method public androidx.core.app.NotificationChannelCompat.Builder setVibrationPattern(long[]?);
+ }
+
+ public class NotificationChannelGroupCompat {
+ method public java.util.List<androidx.core.app.NotificationChannelCompat!> getChannels();
+ method public String? getDescription();
+ method public String getId();
+ method public CharSequence? getName();
+ method public boolean isBlocked();
+ method public androidx.core.app.NotificationChannelGroupCompat.Builder toBuilder();
+ }
+
+ public static class NotificationChannelGroupCompat.Builder {
+ ctor public NotificationChannelGroupCompat.Builder(String);
+ method public androidx.core.app.NotificationChannelGroupCompat build();
+ method public androidx.core.app.NotificationChannelGroupCompat.Builder setDescription(String?);
+ method public androidx.core.app.NotificationChannelGroupCompat.Builder setName(CharSequence?);
+ }
+
+ public class NotificationCompat {
+ ctor @Deprecated public NotificationCompat();
+ method public static androidx.core.app.NotificationCompat.Action? getAction(android.app.Notification, int);
+ method public static int getActionCount(android.app.Notification);
+ method public static boolean getAllowSystemGeneratedContextualActions(android.app.Notification);
+ method public static boolean getAutoCancel(android.app.Notification);
+ method public static int getBadgeIconType(android.app.Notification);
+ method public static androidx.core.app.NotificationCompat.BubbleMetadata? getBubbleMetadata(android.app.Notification);
+ method public static String? getCategory(android.app.Notification);
+ method public static String? getChannelId(android.app.Notification);
+ method public static int getColor(android.app.Notification);
+ method public static CharSequence? getContentInfo(android.app.Notification);
+ method public static CharSequence? getContentText(android.app.Notification);
+ method public static CharSequence? getContentTitle(android.app.Notification);
+ method public static android.os.Bundle? getExtras(android.app.Notification);
+ method public static String? getGroup(android.app.Notification);
+ method public static int getGroupAlertBehavior(android.app.Notification);
+ method @RequiresApi(21) public static java.util.List<androidx.core.app.NotificationCompat.Action!> getInvisibleActions(android.app.Notification);
+ method public static boolean getLocalOnly(android.app.Notification);
+ method public static androidx.core.content.LocusIdCompat? getLocusId(android.app.Notification);
+ method public static boolean getOngoing(android.app.Notification);
+ method public static boolean getOnlyAlertOnce(android.app.Notification);
+ method public static java.util.List<androidx.core.app.Person!> getPeople(android.app.Notification);
+ method public static android.app.Notification? getPublicVersion(android.app.Notification);
+ method public static CharSequence? getSettingsText(android.app.Notification);
+ method public static String? getShortcutId(android.app.Notification);
+ method public static boolean getShowWhen(android.app.Notification);
+ method public static String? getSortKey(android.app.Notification);
+ method public static CharSequence? getSubText(android.app.Notification);
+ method public static long getTimeoutAfter(android.app.Notification);
+ method public static boolean getUsesChronometer(android.app.Notification);
+ method public static int getVisibility(android.app.Notification);
+ method public static boolean isGroupSummary(android.app.Notification);
+ method public static android.graphics.Bitmap? reduceLargeIconSize(android.content.Context, android.graphics.Bitmap?);
+ field public static final int BADGE_ICON_LARGE = 2; // 0x2
+ field public static final int BADGE_ICON_NONE = 0; // 0x0
+ field public static final int BADGE_ICON_SMALL = 1; // 0x1
+ field public static final String CATEGORY_ALARM = "alarm";
+ field public static final String CATEGORY_CALL = "call";
+ field public static final String CATEGORY_EMAIL = "email";
+ field public static final String CATEGORY_ERROR = "err";
+ field public static final String CATEGORY_EVENT = "event";
+ field public static final String CATEGORY_LOCATION_SHARING = "location_sharing";
+ field public static final String CATEGORY_MESSAGE = "msg";
+ field public static final String CATEGORY_MISSED_CALL = "missed_call";
+ field public static final String CATEGORY_NAVIGATION = "navigation";
+ field public static final String CATEGORY_PROGRESS = "progress";
+ field public static final String CATEGORY_PROMO = "promo";
+ field public static final String CATEGORY_RECOMMENDATION = "recommendation";
+ field public static final String CATEGORY_REMINDER = "reminder";
+ field public static final String CATEGORY_SERVICE = "service";
+ field public static final String CATEGORY_SOCIAL = "social";
+ field public static final String CATEGORY_STATUS = "status";
+ field public static final String CATEGORY_STOPWATCH = "stopwatch";
+ field public static final String CATEGORY_SYSTEM = "sys";
+ field public static final String CATEGORY_TRANSPORT = "transport";
+ field public static final String CATEGORY_WORKOUT = "workout";
+ field @ColorInt public static final int COLOR_DEFAULT = 0; // 0x0
+ field public static final int DEFAULT_ALL = -1; // 0xffffffff
+ field public static final int DEFAULT_LIGHTS = 4; // 0x4
+ field public static final int DEFAULT_SOUND = 1; // 0x1
+ field public static final int DEFAULT_VIBRATE = 2; // 0x2
+ field public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
+ field public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
+ field public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
+ field public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
+ field public static final String EXTRA_BIG_TEXT = "android.bigText";
+ field public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo";
+ field public static final String EXTRA_CALL_PERSON = "android.callPerson";
+ field public static final String EXTRA_CALL_PERSON_COMPAT = "android.callPersonCompat";
+ field public static final String EXTRA_CALL_TYPE = "android.callType";
+ field public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
+ field public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
+ field public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+ field public static final String EXTRA_COLORIZED = "android.colorized";
+ field public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
+ field public static final String EXTRA_COMPAT_TEMPLATE = "androidx.core.app.extra.COMPAT_TEMPLATE";
+ field public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+ field public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
+ field public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
+ field public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
+ field public static final String EXTRA_HIDDEN_CONVERSATION_TITLE = "android.hiddenConversationTitle";
+ field public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
+ field public static final String EXTRA_INFO_TEXT = "android.infoText";
+ field public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
+ field public static final String EXTRA_LARGE_ICON = "android.largeIcon";
+ field public static final String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
+ field public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
+ field public static final String EXTRA_MESSAGES = "android.messages";
+ field public static final String EXTRA_MESSAGING_STYLE_USER = "android.messagingStyleUser";
+ field public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
+ field public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
+ field @Deprecated public static final String EXTRA_PEOPLE = "android.people";
+ field public static final String EXTRA_PEOPLE_LIST = "android.people.list";
+ field public static final String EXTRA_PICTURE = "android.picture";
+ field public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = "android.pictureContentDescription";
+ field public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
+ field public static final String EXTRA_PROGRESS = "android.progress";
+ field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
+ field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
+ field public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
+ field public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
+ field public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = "android.showBigPictureWhenCollapsed";
+ field public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
+ field public static final String EXTRA_SHOW_WHEN = "android.showWhen";
+ field public static final String EXTRA_SMALL_ICON = "android.icon";
+ field public static final String EXTRA_SUB_TEXT = "android.subText";
+ field public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
+ field public static final String EXTRA_TEMPLATE = "android.template";
+ field public static final String EXTRA_TEXT = "android.text";
+ field public static final String EXTRA_TEXT_LINES = "android.textLines";
+ field public static final String EXTRA_TITLE = "android.title";
+ field public static final String EXTRA_TITLE_BIG = "android.title.big";
+ field public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
+ field public static final String EXTRA_VERIFICATION_ICON_COMPAT = "android.verificationIconCompat";
+ field public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
+ field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
+ field public static final int FLAG_BUBBLE = 4096; // 0x1000
+ field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
+ field public static final int FLAG_GROUP_SUMMARY = 512; // 0x200
+ field @Deprecated public static final int FLAG_HIGH_PRIORITY = 128; // 0x80
+ field public static final int FLAG_INSISTENT = 4; // 0x4
+ field public static final int FLAG_LOCAL_ONLY = 256; // 0x100
+ field public static final int FLAG_NO_CLEAR = 32; // 0x20
+ field public static final int FLAG_ONGOING_EVENT = 2; // 0x2
+ field public static final int FLAG_ONLY_ALERT_ONCE = 8; // 0x8
+ field public static final int FLAG_SHOW_LIGHTS = 1; // 0x1
+ field public static final int FOREGROUND_SERVICE_DEFAULT = 0; // 0x0
+ field public static final int FOREGROUND_SERVICE_DEFERRED = 2; // 0x2
+ field public static final int FOREGROUND_SERVICE_IMMEDIATE = 1; // 0x1
+ field public static final int GROUP_ALERT_ALL = 0; // 0x0
+ field public static final int GROUP_ALERT_CHILDREN = 2; // 0x2
+ field public static final int GROUP_ALERT_SUMMARY = 1; // 0x1
+ field public static final String GROUP_KEY_SILENT = "silent";
+ field public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES = "android.intent.category.NOTIFICATION_PREFERENCES";
+ field public static final int PRIORITY_DEFAULT = 0; // 0x0
+ field public static final int PRIORITY_HIGH = 1; // 0x1
+ field public static final int PRIORITY_LOW = -1; // 0xffffffff
+ field public static final int PRIORITY_MAX = 2; // 0x2
+ field public static final int PRIORITY_MIN = -2; // 0xfffffffe
+ field public static final int STREAM_DEFAULT = -1; // 0xffffffff
+ field public static final int VISIBILITY_PRIVATE = 0; // 0x0
+ field public static final int VISIBILITY_PUBLIC = 1; // 0x1
+ field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
+ }
+
+ public static class NotificationCompat.Action {
+ ctor public NotificationCompat.Action(androidx.core.graphics.drawable.IconCompat?, CharSequence?, android.app.PendingIntent?);
+ ctor public NotificationCompat.Action(int, CharSequence?, android.app.PendingIntent?);
+ method public android.app.PendingIntent? getActionIntent();
+ method public boolean getAllowGeneratedReplies();
+ method public androidx.core.app.RemoteInput![]? getDataOnlyRemoteInputs();
+ method public android.os.Bundle getExtras();
+ method @Deprecated public int getIcon();
+ method public androidx.core.graphics.drawable.IconCompat? getIconCompat();
+ method public androidx.core.app.RemoteInput![]? getRemoteInputs();
+ method @androidx.core.app.NotificationCompat.Action.SemanticAction public int getSemanticAction();
+ method public boolean getShowsUserInterface();
+ method public CharSequence? getTitle();
+ method public boolean isAuthenticationRequired();
+ method public boolean isContextual();
+ field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
+ field public static final int SEMANTIC_ACTION_CALL = 10; // 0xa
+ field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
+ field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
+ field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3
+ field public static final int SEMANTIC_ACTION_MUTE = 6; // 0x6
+ field public static final int SEMANTIC_ACTION_NONE = 0; // 0x0
+ field public static final int SEMANTIC_ACTION_REPLY = 1; // 0x1
+ field public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; // 0x9
+ field public static final int SEMANTIC_ACTION_THUMBS_UP = 8; // 0x8
+ field public static final int SEMANTIC_ACTION_UNMUTE = 7; // 0x7
+ field public android.app.PendingIntent? actionIntent;
+ field @Deprecated public int icon;
+ field public CharSequence! title;
+ }
+
+ public static final class NotificationCompat.Action.Builder {
+ ctor public NotificationCompat.Action.Builder(androidx.core.app.NotificationCompat.Action);
+ ctor public NotificationCompat.Action.Builder(androidx.core.graphics.drawable.IconCompat?, CharSequence?, android.app.PendingIntent?);
+ ctor public NotificationCompat.Action.Builder(int, CharSequence?, android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.Action.Builder addExtras(android.os.Bundle?);
+ method public androidx.core.app.NotificationCompat.Action.Builder addRemoteInput(androidx.core.app.RemoteInput?);
+ method public androidx.core.app.NotificationCompat.Action build();
+ method public androidx.core.app.NotificationCompat.Action.Builder extend(androidx.core.app.NotificationCompat.Action.Extender);
+ method public android.os.Bundle getExtras();
+ method public androidx.core.app.NotificationCompat.Action.Builder setAllowGeneratedReplies(boolean);
+ method public androidx.core.app.NotificationCompat.Action.Builder setAuthenticationRequired(boolean);
+ method public androidx.core.app.NotificationCompat.Action.Builder setContextual(boolean);
+ method public androidx.core.app.NotificationCompat.Action.Builder setSemanticAction(@androidx.core.app.NotificationCompat.Action.SemanticAction int);
+ method public androidx.core.app.NotificationCompat.Action.Builder setShowsUserInterface(boolean);
+ }
+
+ public static interface NotificationCompat.Action.Extender {
+ method public androidx.core.app.NotificationCompat.Action.Builder extend(androidx.core.app.NotificationCompat.Action.Builder);
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_NONE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_REPLY, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_UNREAD, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_DELETE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_ARCHIVE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_MUTE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_UNMUTE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_THUMBS_UP, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_THUMBS_DOWN, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_CALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.Action.SemanticAction {
+ }
+
+ public static final class NotificationCompat.Action.WearableExtender implements androidx.core.app.NotificationCompat.Action.Extender {
+ ctor public NotificationCompat.Action.WearableExtender();
+ ctor public NotificationCompat.Action.WearableExtender(androidx.core.app.NotificationCompat.Action);
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender clone();
+ method public androidx.core.app.NotificationCompat.Action.Builder extend(androidx.core.app.NotificationCompat.Action.Builder);
+ method @Deprecated public CharSequence? getCancelLabel();
+ method @Deprecated public CharSequence? getConfirmLabel();
+ method public boolean getHintDisplayActionInline();
+ method public boolean getHintLaunchesActivity();
+ method @Deprecated public CharSequence? getInProgressLabel();
+ method public boolean isAvailableOffline();
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender setAvailableOffline(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.Action.WearableExtender setCancelLabel(CharSequence?);
+ method @Deprecated public androidx.core.app.NotificationCompat.Action.WearableExtender setConfirmLabel(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender setHintDisplayActionInline(boolean);
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender setHintLaunchesActivity(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.Action.WearableExtender setInProgressLabel(CharSequence?);
+ }
+
+ public static class NotificationCompat.BigPictureStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.BigPictureStyle();
+ ctor public NotificationCompat.BigPictureStyle(androidx.core.app.NotificationCompat.Builder?);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle bigLargeIcon(android.graphics.Bitmap?);
+ method @RequiresApi(23) public androidx.core.app.NotificationCompat.BigPictureStyle bigLargeIcon(android.graphics.drawable.Icon?);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle bigPicture(android.graphics.Bitmap?);
+ method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle bigPicture(android.graphics.drawable.Icon?);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle setBigContentTitle(CharSequence?);
+ method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle setContentDescription(CharSequence?);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle setSummaryText(CharSequence?);
+ method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle showBigPictureWhenCollapsed(boolean);
+ }
+
+ public static class NotificationCompat.BigTextStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.BigTextStyle();
+ ctor public NotificationCompat.BigTextStyle(androidx.core.app.NotificationCompat.Builder?);
+ method public androidx.core.app.NotificationCompat.BigTextStyle bigText(CharSequence?);
+ method public androidx.core.app.NotificationCompat.BigTextStyle setBigContentTitle(CharSequence?);
+ method public androidx.core.app.NotificationCompat.BigTextStyle setSummaryText(CharSequence?);
+ }
+
+ public static final class NotificationCompat.BubbleMetadata {
+ method public static androidx.core.app.NotificationCompat.BubbleMetadata? fromPlatform(android.app.Notification.BubbleMetadata?);
+ method public boolean getAutoExpandBubble();
+ method public android.app.PendingIntent? getDeleteIntent();
+ method @Dimension(unit=androidx.annotation.Dimension.DP) public int getDesiredHeight();
+ method @DimenRes public int getDesiredHeightResId();
+ method public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method public android.app.PendingIntent? getIntent();
+ method public String? getShortcutId();
+ method public boolean isNotificationSuppressed();
+ method public static android.app.Notification.BubbleMetadata? toPlatform(androidx.core.app.NotificationCompat.BubbleMetadata?);
+ }
+
+ public static final class NotificationCompat.BubbleMetadata.Builder {
+ ctor @Deprecated public NotificationCompat.BubbleMetadata.Builder();
+ ctor public NotificationCompat.BubbleMetadata.Builder(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat);
+ ctor @RequiresApi(30) public NotificationCompat.BubbleMetadata.Builder(String);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata build();
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setAutoExpandBubble(boolean);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setDeleteIntent(android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setDesiredHeight(@Dimension(unit=androidx.annotation.Dimension.DP) int);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setIcon(androidx.core.graphics.drawable.IconCompat);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setIntent(android.app.PendingIntent);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setSuppressNotification(boolean);
+ }
+
+ public static class NotificationCompat.Builder {
+ ctor @Deprecated public NotificationCompat.Builder(android.content.Context);
+ ctor public NotificationCompat.Builder(android.content.Context, android.app.Notification);
+ ctor public NotificationCompat.Builder(android.content.Context, String);
+ method public androidx.core.app.NotificationCompat.Builder addAction(androidx.core.app.NotificationCompat.Action?);
+ method public androidx.core.app.NotificationCompat.Builder addAction(int, CharSequence?, android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.Builder addExtras(android.os.Bundle?);
+ method @RequiresApi(21) public androidx.core.app.NotificationCompat.Builder addInvisibleAction(androidx.core.app.NotificationCompat.Action?);
+ method @RequiresApi(21) public androidx.core.app.NotificationCompat.Builder addInvisibleAction(int, CharSequence?, android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.Builder addPerson(androidx.core.app.Person?);
+ method @Deprecated public androidx.core.app.NotificationCompat.Builder addPerson(String?);
+ method public android.app.Notification build();
+ method public androidx.core.app.NotificationCompat.Builder clearActions();
+ method public androidx.core.app.NotificationCompat.Builder clearInvisibleActions();
+ method public androidx.core.app.NotificationCompat.Builder clearPeople();
+ method public android.widget.RemoteViews? createBigContentView();
+ method public android.widget.RemoteViews? createContentView();
+ method public android.widget.RemoteViews? createHeadsUpContentView();
+ method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Extender);
+ method public android.os.Bundle getExtras();
+ method @Deprecated public android.app.Notification getNotification();
+ method protected static CharSequence? limitCharSequenceLength(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setAllowSystemGeneratedContextualActions(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setAutoCancel(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setBadgeIconType(int);
+ method public androidx.core.app.NotificationCompat.Builder setBubbleMetadata(androidx.core.app.NotificationCompat.BubbleMetadata?);
+ method public androidx.core.app.NotificationCompat.Builder setCategory(String?);
+ method public androidx.core.app.NotificationCompat.Builder setChannelId(String);
+ method @RequiresApi(24) public androidx.core.app.NotificationCompat.Builder setChronometerCountDown(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setColor(@ColorInt int);
+ method public androidx.core.app.NotificationCompat.Builder setColorized(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setContent(android.widget.RemoteViews?);
+ method public androidx.core.app.NotificationCompat.Builder setContentInfo(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setContentIntent(android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.Builder setContentText(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setContentTitle(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setCustomBigContentView(android.widget.RemoteViews?);
+ method public androidx.core.app.NotificationCompat.Builder setCustomContentView(android.widget.RemoteViews?);
+ method public androidx.core.app.NotificationCompat.Builder setCustomHeadsUpContentView(android.widget.RemoteViews?);
+ method public androidx.core.app.NotificationCompat.Builder setDefaults(int);
+ method public androidx.core.app.NotificationCompat.Builder setDeleteIntent(android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.Builder setExtras(android.os.Bundle?);
+ method public androidx.core.app.NotificationCompat.Builder setForegroundServiceBehavior(int);
+ method public androidx.core.app.NotificationCompat.Builder setFullScreenIntent(android.app.PendingIntent?, boolean);
+ method public androidx.core.app.NotificationCompat.Builder setGroup(String?);
+ method public androidx.core.app.NotificationCompat.Builder setGroupAlertBehavior(int);
+ method public androidx.core.app.NotificationCompat.Builder setGroupSummary(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setLargeIcon(android.graphics.Bitmap?);
+ method @RequiresApi(23) public androidx.core.app.NotificationCompat.Builder setLargeIcon(android.graphics.drawable.Icon?);
+ method public androidx.core.app.NotificationCompat.Builder setLights(@ColorInt int, int, int);
+ method public androidx.core.app.NotificationCompat.Builder setLocalOnly(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setLocusId(androidx.core.content.LocusIdCompat?);
+ method @Deprecated public androidx.core.app.NotificationCompat.Builder setNotificationSilent();
+ method public androidx.core.app.NotificationCompat.Builder setNumber(int);
+ method public androidx.core.app.NotificationCompat.Builder setOngoing(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setOnlyAlertOnce(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setPriority(int);
+ method public androidx.core.app.NotificationCompat.Builder setProgress(int, int, boolean);
+ method public androidx.core.app.NotificationCompat.Builder setPublicVersion(android.app.Notification?);
+ method public androidx.core.app.NotificationCompat.Builder setRemoteInputHistory(CharSequence![]?);
+ method public androidx.core.app.NotificationCompat.Builder setSettingsText(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setShortcutId(String?);
+ method public androidx.core.app.NotificationCompat.Builder setShortcutInfo(androidx.core.content.pm.ShortcutInfoCompat?);
+ method public androidx.core.app.NotificationCompat.Builder setShowWhen(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setSilent(boolean);
+ method @RequiresApi(23) public androidx.core.app.NotificationCompat.Builder setSmallIcon(androidx.core.graphics.drawable.IconCompat);
+ method public androidx.core.app.NotificationCompat.Builder setSmallIcon(int);
+ method public androidx.core.app.NotificationCompat.Builder setSmallIcon(int, int);
+ method public androidx.core.app.NotificationCompat.Builder setSortKey(String?);
+ method public androidx.core.app.NotificationCompat.Builder setSound(android.net.Uri?);
+ method public androidx.core.app.NotificationCompat.Builder setSound(android.net.Uri?, int);
+ method public androidx.core.app.NotificationCompat.Builder setStyle(androidx.core.app.NotificationCompat.Style?);
+ method public androidx.core.app.NotificationCompat.Builder setSubText(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setTicker(CharSequence?);
+ method @Deprecated public androidx.core.app.NotificationCompat.Builder setTicker(CharSequence?, android.widget.RemoteViews?);
+ method public androidx.core.app.NotificationCompat.Builder setTimeoutAfter(long);
+ method public androidx.core.app.NotificationCompat.Builder setUsesChronometer(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setVibrate(long[]?);
+ method public androidx.core.app.NotificationCompat.Builder setVisibility(int);
+ method public androidx.core.app.NotificationCompat.Builder setWhen(long);
+ field @Deprecated public java.util.ArrayList<java.lang.String!>! mPeople;
+ }
+
+ public static class NotificationCompat.CallStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.CallStyle();
+ ctor public NotificationCompat.CallStyle(androidx.core.app.NotificationCompat.Builder?);
+ method public static androidx.core.app.NotificationCompat.CallStyle forIncomingCall(androidx.core.app.Person, android.app.PendingIntent, android.app.PendingIntent);
+ method public static androidx.core.app.NotificationCompat.CallStyle forOngoingCall(androidx.core.app.Person, android.app.PendingIntent);
+ method public static androidx.core.app.NotificationCompat.CallStyle forScreeningCall(androidx.core.app.Person, android.app.PendingIntent, android.app.PendingIntent);
+ method public androidx.core.app.NotificationCompat.CallStyle setAnswerButtonColorHint(@ColorInt int);
+ method public androidx.core.app.NotificationCompat.CallStyle setDeclineButtonColorHint(@ColorInt int);
+ method public androidx.core.app.NotificationCompat.CallStyle setIsVideo(boolean);
+ method public androidx.core.app.NotificationCompat.CallStyle setVerificationIcon(android.graphics.Bitmap?);
+ method @RequiresApi(23) public androidx.core.app.NotificationCompat.CallStyle setVerificationIcon(android.graphics.drawable.Icon?);
+ method public androidx.core.app.NotificationCompat.CallStyle setVerificationText(CharSequence?);
+ field public static final int CALL_TYPE_INCOMING = 1; // 0x1
+ field public static final int CALL_TYPE_ONGOING = 2; // 0x2
+ field public static final int CALL_TYPE_SCREENING = 3; // 0x3
+ field public static final int CALL_TYPE_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class NotificationCompat.CarExtender implements androidx.core.app.NotificationCompat.Extender {
+ ctor public NotificationCompat.CarExtender();
+ ctor public NotificationCompat.CarExtender(android.app.Notification);
+ method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
+ method @ColorInt public int getColor();
+ method public android.graphics.Bitmap? getLargeIcon();
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation? getUnreadConversation();
+ method public androidx.core.app.NotificationCompat.CarExtender setColor(@ColorInt int);
+ method public androidx.core.app.NotificationCompat.CarExtender setLargeIcon(android.graphics.Bitmap?);
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender setUnreadConversation(androidx.core.app.NotificationCompat.CarExtender.UnreadConversation?);
+ }
+
+ @Deprecated public static class NotificationCompat.CarExtender.UnreadConversation {
+ method @Deprecated public long getLatestTimestamp();
+ method @Deprecated public String![]? getMessages();
+ method @Deprecated public String? getParticipant();
+ method @Deprecated public String![]? getParticipants();
+ method @Deprecated public android.app.PendingIntent? getReadPendingIntent();
+ method @Deprecated public androidx.core.app.RemoteInput? getRemoteInput();
+ method @Deprecated public android.app.PendingIntent? getReplyPendingIntent();
+ }
+
+ @Deprecated public static class NotificationCompat.CarExtender.UnreadConversation.Builder {
+ ctor @Deprecated public NotificationCompat.CarExtender.UnreadConversation.Builder(String);
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder addMessage(String?);
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation build();
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder setLatestTimestamp(long);
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder setReadPendingIntent(android.app.PendingIntent?);
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder setReplyAction(android.app.PendingIntent?, androidx.core.app.RemoteInput?);
+ }
+
+ public static class NotificationCompat.DecoratedCustomViewStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.DecoratedCustomViewStyle();
+ }
+
+ public static interface NotificationCompat.Extender {
+ method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
+ }
+
+ public static class NotificationCompat.InboxStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.InboxStyle();
+ ctor public NotificationCompat.InboxStyle(androidx.core.app.NotificationCompat.Builder?);
+ method public androidx.core.app.NotificationCompat.InboxStyle addLine(CharSequence?);
+ method public androidx.core.app.NotificationCompat.InboxStyle setBigContentTitle(CharSequence?);
+ method public androidx.core.app.NotificationCompat.InboxStyle setSummaryText(CharSequence?);
+ }
+
+ public static class NotificationCompat.MessagingStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MessagingStyle(androidx.core.app.Person);
+ ctor @Deprecated public NotificationCompat.MessagingStyle(CharSequence);
+ method public void addCompatExtras(android.os.Bundle);
+ method public androidx.core.app.NotificationCompat.MessagingStyle addHistoricMessage(androidx.core.app.NotificationCompat.MessagingStyle.Message?);
+ method public androidx.core.app.NotificationCompat.MessagingStyle addMessage(androidx.core.app.NotificationCompat.MessagingStyle.Message?);
+ method public androidx.core.app.NotificationCompat.MessagingStyle addMessage(CharSequence?, long, androidx.core.app.Person?);
+ method @Deprecated public androidx.core.app.NotificationCompat.MessagingStyle addMessage(CharSequence?, long, CharSequence?);
+ method public static androidx.core.app.NotificationCompat.MessagingStyle? extractMessagingStyleFromNotification(android.app.Notification);
+ method public CharSequence? getConversationTitle();
+ method public java.util.List<androidx.core.app.NotificationCompat.MessagingStyle.Message!> getHistoricMessages();
+ method public java.util.List<androidx.core.app.NotificationCompat.MessagingStyle.Message!> getMessages();
+ method public androidx.core.app.Person getUser();
+ method @Deprecated public CharSequence? getUserDisplayName();
+ method public boolean isGroupConversation();
+ method public androidx.core.app.NotificationCompat.MessagingStyle setConversationTitle(CharSequence?);
+ method public androidx.core.app.NotificationCompat.MessagingStyle setGroupConversation(boolean);
+ field public static final int MAXIMUM_RETAINED_MESSAGES = 25; // 0x19
+ }
+
+ public static final class NotificationCompat.MessagingStyle.Message {
+ ctor public NotificationCompat.MessagingStyle.Message(CharSequence?, long, androidx.core.app.Person?);
+ ctor @Deprecated public NotificationCompat.MessagingStyle.Message(CharSequence?, long, CharSequence?);
+ method public String? getDataMimeType();
+ method public android.net.Uri? getDataUri();
+ method public android.os.Bundle getExtras();
+ method public androidx.core.app.Person? getPerson();
+ method @Deprecated public CharSequence? getSender();
+ method public CharSequence? getText();
+ method public long getTimestamp();
+ method public androidx.core.app.NotificationCompat.MessagingStyle.Message setData(String?, android.net.Uri?);
+ }
+
+ public abstract static class NotificationCompat.Style {
+ ctor public NotificationCompat.Style();
+ method public android.app.Notification? build();
+ method public void setBuilder(androidx.core.app.NotificationCompat.Builder?);
+ }
+
+ public static final class NotificationCompat.TvExtender implements androidx.core.app.NotificationCompat.Extender {
+ ctor public NotificationCompat.TvExtender();
+ ctor public NotificationCompat.TvExtender(android.app.Notification);
+ method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
+ method public String? getChannelId();
+ method public android.app.PendingIntent? getContentIntent();
+ method public android.app.PendingIntent? getDeleteIntent();
+ method public boolean isAvailableOnTv();
+ method public boolean isSuppressShowOverApps();
+ method public androidx.core.app.NotificationCompat.TvExtender setChannelId(String?);
+ method public androidx.core.app.NotificationCompat.TvExtender setContentIntent(android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.TvExtender setDeleteIntent(android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.TvExtender setSuppressShowOverApps(boolean);
+ }
+
+ public static final class NotificationCompat.WearableExtender implements androidx.core.app.NotificationCompat.Extender {
+ ctor public NotificationCompat.WearableExtender();
+ ctor public NotificationCompat.WearableExtender(android.app.Notification);
+ method public androidx.core.app.NotificationCompat.WearableExtender addAction(androidx.core.app.NotificationCompat.Action);
+ method public androidx.core.app.NotificationCompat.WearableExtender addActions(java.util.List<androidx.core.app.NotificationCompat.Action!>);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender addPage(android.app.Notification);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender addPages(java.util.List<android.app.Notification!>);
+ method public androidx.core.app.NotificationCompat.WearableExtender clearActions();
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender clearPages();
+ method public androidx.core.app.NotificationCompat.WearableExtender clone();
+ method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
+ method public java.util.List<androidx.core.app.NotificationCompat.Action!> getActions();
+ method @Deprecated public android.graphics.Bitmap? getBackground();
+ method public String? getBridgeTag();
+ method public int getContentAction();
+ method @Deprecated public int getContentIcon();
+ method @Deprecated public int getContentIconGravity();
+ method public boolean getContentIntentAvailableOffline();
+ method @Deprecated public int getCustomContentHeight();
+ method @Deprecated public int getCustomSizePreset();
+ method public String? getDismissalId();
+ method @Deprecated public android.app.PendingIntent? getDisplayIntent();
+ method @Deprecated public int getGravity();
+ method @Deprecated public boolean getHintAmbientBigPicture();
+ method @Deprecated public boolean getHintAvoidBackgroundClipping();
+ method public boolean getHintContentIntentLaunchesActivity();
+ method @Deprecated public boolean getHintHideIcon();
+ method @Deprecated public int getHintScreenTimeout();
+ method @Deprecated public boolean getHintShowBackgroundOnly();
+ method @Deprecated public java.util.List<android.app.Notification!> getPages();
+ method public boolean getStartScrollBottom();
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setBackground(android.graphics.Bitmap?);
+ method public androidx.core.app.NotificationCompat.WearableExtender setBridgeTag(String?);
+ method public androidx.core.app.NotificationCompat.WearableExtender setContentAction(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setContentIcon(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setContentIconGravity(int);
+ method public androidx.core.app.NotificationCompat.WearableExtender setContentIntentAvailableOffline(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setCustomContentHeight(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setCustomSizePreset(int);
+ method public androidx.core.app.NotificationCompat.WearableExtender setDismissalId(String?);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setDisplayIntent(android.app.PendingIntent?);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setGravity(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setHintAmbientBigPicture(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setHintAvoidBackgroundClipping(boolean);
+ method public androidx.core.app.NotificationCompat.WearableExtender setHintContentIntentLaunchesActivity(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setHintHideIcon(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setHintScreenTimeout(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setHintShowBackgroundOnly(boolean);
+ method public androidx.core.app.NotificationCompat.WearableExtender setStartScrollBottom(boolean);
+ field @Deprecated public static final int SCREEN_TIMEOUT_LONG = -1; // 0xffffffff
+ field @Deprecated public static final int SCREEN_TIMEOUT_SHORT = 0; // 0x0
+ field @Deprecated public static final int SIZE_DEFAULT = 0; // 0x0
+ field @Deprecated public static final int SIZE_FULL_SCREEN = 5; // 0x5
+ field @Deprecated public static final int SIZE_LARGE = 4; // 0x4
+ field @Deprecated public static final int SIZE_MEDIUM = 3; // 0x3
+ field @Deprecated public static final int SIZE_SMALL = 2; // 0x2
+ field @Deprecated public static final int SIZE_XSMALL = 1; // 0x1
+ field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff
+ }
+
+ public final class NotificationCompatExtras {
+ field public static final String EXTRA_ACTION_EXTRAS = "android.support.actionExtras";
+ field public static final String EXTRA_GROUP_KEY = "android.support.groupKey";
+ field public static final String EXTRA_GROUP_SUMMARY = "android.support.isGroupSummary";
+ field public static final String EXTRA_LOCAL_ONLY = "android.support.localOnly";
+ field public static final String EXTRA_REMOTE_INPUTS = "android.support.remoteInputs";
+ field public static final String EXTRA_SORT_KEY = "android.support.sortKey";
+ }
+
+ public abstract class NotificationCompatSideChannelService extends android.app.Service {
+ ctor public NotificationCompatSideChannelService();
+ method public abstract void cancel(String!, int, String!);
+ method public abstract void cancelAll(String!);
+ method public abstract void notify(String!, int, String!, android.app.Notification!);
+ method @DeprecatedSinceApi(api=19, message="SDKs past 19 have no need for side channeling.") public android.os.IBinder! onBind(android.content.Intent!);
+ }
+
+ public final class NotificationManagerCompat {
+ method public boolean areNotificationsEnabled();
+ method public boolean canUseFullScreenIntent();
+ method public void cancel(int);
+ method public void cancel(String?, int);
+ method public void cancelAll();
+ method public void createNotificationChannel(android.app.NotificationChannel);
+ method public void createNotificationChannel(androidx.core.app.NotificationChannelCompat);
+ method public void createNotificationChannelGroup(android.app.NotificationChannelGroup);
+ method public void createNotificationChannelGroup(androidx.core.app.NotificationChannelGroupCompat);
+ method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup!>);
+ method public void createNotificationChannelGroupsCompat(java.util.List<androidx.core.app.NotificationChannelGroupCompat!>);
+ method public void createNotificationChannels(java.util.List<android.app.NotificationChannel!>);
+ method public void createNotificationChannelsCompat(java.util.List<androidx.core.app.NotificationChannelCompat!>);
+ method public void deleteNotificationChannel(String);
+ method public void deleteNotificationChannelGroup(String);
+ method public void deleteUnlistedNotificationChannels(java.util.Collection<java.lang.String!>);
+ method public static androidx.core.app.NotificationManagerCompat from(android.content.Context);
+ method public java.util.List<android.service.notification.StatusBarNotification!> getActiveNotifications();
+ method public int getCurrentInterruptionFilter();
+ method public static java.util.Set<java.lang.String!> getEnabledListenerPackages(android.content.Context);
+ method public int getImportance();
+ method public android.app.NotificationChannel? getNotificationChannel(String);
+ method public android.app.NotificationChannel? getNotificationChannel(String, String);
+ method public androidx.core.app.NotificationChannelCompat? getNotificationChannelCompat(String);
+ method public androidx.core.app.NotificationChannelCompat? getNotificationChannelCompat(String, String);
+ method public android.app.NotificationChannelGroup? getNotificationChannelGroup(String);
+ method public androidx.core.app.NotificationChannelGroupCompat? getNotificationChannelGroupCompat(String);
+ method public java.util.List<android.app.NotificationChannelGroup!> getNotificationChannelGroups();
+ method public java.util.List<androidx.core.app.NotificationChannelGroupCompat!> getNotificationChannelGroupsCompat();
+ method public java.util.List<android.app.NotificationChannel!> getNotificationChannels();
+ method public java.util.List<androidx.core.app.NotificationChannelCompat!> getNotificationChannelsCompat();
+ method @RequiresPermission(android.Manifest.permission.POST_NOTIFICATIONS) public void notify(int, android.app.Notification);
+ method @RequiresPermission(android.Manifest.permission.POST_NOTIFICATIONS) public void notify(String?, int, android.app.Notification);
+ method @RequiresPermission(android.Manifest.permission.POST_NOTIFICATIONS) public void notify(java.util.List<androidx.core.app.NotificationManagerCompat.NotificationWithIdAndTag!>);
+ field public static final String ACTION_BIND_SIDE_CHANNEL = "android.support.BIND_NOTIFICATION_SIDE_CHANNEL";
+ field public static final String EXTRA_USE_SIDE_CHANNEL = "android.support.useSideChannel";
+ field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
+ field public static final int IMPORTANCE_HIGH = 4; // 0x4
+ field public static final int IMPORTANCE_LOW = 2; // 0x2
+ field public static final int IMPORTANCE_MAX = 5; // 0x5
+ field public static final int IMPORTANCE_MIN = 1; // 0x1
+ field public static final int IMPORTANCE_NONE = 0; // 0x0
+ field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
+ field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
+ field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
+ field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
+ field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
+ field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+ }
+
+ public static class NotificationManagerCompat.NotificationWithIdAndTag {
+ ctor public NotificationManagerCompat.NotificationWithIdAndTag(int, android.app.Notification);
+ ctor public NotificationManagerCompat.NotificationWithIdAndTag(String?, int, android.app.Notification);
+ }
+
+ public interface OnMultiWindowModeChangedProvider {
+ method public void addOnMultiWindowModeChangedListener(androidx.core.util.Consumer<androidx.core.app.MultiWindowModeChangedInfo> listener);
+ method public void removeOnMultiWindowModeChangedListener(androidx.core.util.Consumer<androidx.core.app.MultiWindowModeChangedInfo> listener);
+ }
+
+ public interface OnNewIntentProvider {
+ method public void addOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
+ method public void removeOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
+ }
+
+ public interface OnPictureInPictureModeChangedProvider {
+ method public void addOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
+ method public void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
+ }
+
+ public interface OnUserLeaveHintProvider {
+ method public void addOnUserLeaveHintListener(Runnable listener);
+ method public void removeOnUserLeaveHintListener(Runnable listener);
+ }
+
+ public final class PendingIntentCompat {
+ method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, android.os.Bundle?, boolean);
+ method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, boolean);
+ method public static android.app.PendingIntent? getActivity(android.content.Context, int, android.content.Intent, int, android.os.Bundle?, boolean);
+ method public static android.app.PendingIntent? getActivity(android.content.Context, int, android.content.Intent, int, boolean);
+ method public static android.app.PendingIntent? getBroadcast(android.content.Context, int, android.content.Intent, int, boolean);
+ method @RequiresApi(26) public static android.app.PendingIntent getForegroundService(android.content.Context, int, android.content.Intent, int, boolean);
+ method public static android.app.PendingIntent? getService(android.content.Context, int, android.content.Intent, int, boolean);
+ method public static void send(android.app.PendingIntent, android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished?, android.os.Handler?) throws android.app.PendingIntent.CanceledException;
+ method public static void send(android.app.PendingIntent, android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished?, android.os.Handler?, String?, android.os.Bundle?) throws android.app.PendingIntent.CanceledException;
+ method public static void send(android.app.PendingIntent, int, android.app.PendingIntent.OnFinished?, android.os.Handler?) throws android.app.PendingIntent.CanceledException;
+ }
+
+ public class Person {
+ method public static androidx.core.app.Person fromBundle(android.os.Bundle);
+ method public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method public String? getKey();
+ method public CharSequence? getName();
+ method public String? getUri();
+ method public boolean isBot();
+ method public boolean isImportant();
+ method public androidx.core.app.Person.Builder toBuilder();
+ method public android.os.Bundle toBundle();
+ }
+
+ public static class Person.Builder {
+ ctor public Person.Builder();
+ method public androidx.core.app.Person build();
+ method public androidx.core.app.Person.Builder setBot(boolean);
+ method public androidx.core.app.Person.Builder setIcon(androidx.core.graphics.drawable.IconCompat?);
+ method public androidx.core.app.Person.Builder setImportant(boolean);
+ method public androidx.core.app.Person.Builder setKey(String?);
+ method public androidx.core.app.Person.Builder setName(CharSequence?);
+ method public androidx.core.app.Person.Builder setUri(String?);
+ }
+
+ public final class PictureInPictureModeChangedInfo {
+ ctor public PictureInPictureModeChangedInfo(boolean isInPictureInPictureMode);
+ ctor @RequiresApi(26) public PictureInPictureModeChangedInfo(boolean isInPictureInPictureMode, android.content.res.Configuration newConfig);
+ method @RequiresApi(26) public android.content.res.Configuration getNewConfig();
+ method public boolean isInPictureInPictureMode();
+ property public final boolean isInPictureInPictureMode;
+ property @RequiresApi(26) public final android.content.res.Configuration newConfig;
+ }
+
+ public final class RemoteActionCompat implements androidx.versionedparcelable.VersionedParcelable {
+ ctor public RemoteActionCompat(androidx.core.app.RemoteActionCompat);
+ ctor public RemoteActionCompat(androidx.core.graphics.drawable.IconCompat, CharSequence, CharSequence, android.app.PendingIntent);
+ method @RequiresApi(26) public static androidx.core.app.RemoteActionCompat createFromRemoteAction(android.app.RemoteAction);
+ method public android.app.PendingIntent getActionIntent();
+ method public CharSequence getContentDescription();
+ method public androidx.core.graphics.drawable.IconCompat getIcon();
+ method public CharSequence getTitle();
+ method public boolean isEnabled();
+ method public void setEnabled(boolean);
+ method public void setShouldShowIcon(boolean);
+ method public boolean shouldShowIcon();
+ method @RequiresApi(26) public android.app.RemoteAction toRemoteAction();
+ }
+
+ public final class RemoteInput {
+ method public static void addDataResultToIntent(androidx.core.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String!,android.net.Uri!>);
+ method public static void addResultsToIntent(androidx.core.app.RemoteInput![], android.content.Intent, android.os.Bundle);
+ method public boolean getAllowFreeFormInput();
+ method public java.util.Set<java.lang.String!>? getAllowedDataTypes();
+ method public CharSequence![]? getChoices();
+ method public static java.util.Map<java.lang.String!,android.net.Uri!>? getDataResultsFromIntent(android.content.Intent, String);
+ method public int getEditChoicesBeforeSending();
+ method public android.os.Bundle getExtras();
+ method public CharSequence? getLabel();
+ method public String getResultKey();
+ method public static android.os.Bundle? getResultsFromIntent(android.content.Intent);
+ method public static int getResultsSource(android.content.Intent);
+ method public boolean isDataOnly();
+ method public static void setResultsSource(android.content.Intent, int);
+ field public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0; // 0x0
+ field public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1; // 0x1
+ field public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2; // 0x2
+ field public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
+ field public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results";
+ field public static final int SOURCE_CHOICE = 1; // 0x1
+ field public static final int SOURCE_FREE_FORM_INPUT = 0; // 0x0
+ }
+
+ public static final class RemoteInput.Builder {
+ ctor public RemoteInput.Builder(String);
+ method public androidx.core.app.RemoteInput.Builder addExtras(android.os.Bundle);
+ method public androidx.core.app.RemoteInput build();
+ method public android.os.Bundle getExtras();
+ method public androidx.core.app.RemoteInput.Builder setAllowDataType(String, boolean);
+ method public androidx.core.app.RemoteInput.Builder setAllowFreeFormInput(boolean);
+ method public androidx.core.app.RemoteInput.Builder setChoices(CharSequence![]?);
+ method public androidx.core.app.RemoteInput.Builder setEditChoicesBeforeSending(int);
+ method public androidx.core.app.RemoteInput.Builder setLabel(CharSequence?);
+ }
+
+ public final class ServiceCompat {
+ method public static void startForeground(android.app.Service, int, android.app.Notification, int);
+ method public static void stopForeground(android.app.Service, int);
+ field public static final int START_STICKY = 1; // 0x1
+ field public static final int STOP_FOREGROUND_DETACH = 2; // 0x2
+ field public static final int STOP_FOREGROUND_REMOVE = 1; // 0x1
+ }
+
+ public final class ShareCompat {
+ method @Deprecated public static void configureMenuItem(android.view.Menu, @IdRes int, androidx.core.app.ShareCompat.IntentBuilder);
+ method @Deprecated public static void configureMenuItem(android.view.MenuItem, androidx.core.app.ShareCompat.IntentBuilder);
+ method public static android.content.ComponentName? getCallingActivity(android.app.Activity);
+ method public static String? getCallingPackage(android.app.Activity);
+ field public static final String EXTRA_CALLING_ACTIVITY = "androidx.core.app.EXTRA_CALLING_ACTIVITY";
+ field public static final String EXTRA_CALLING_ACTIVITY_INTEROP = "android.support.v4.app.EXTRA_CALLING_ACTIVITY";
+ field public static final String EXTRA_CALLING_PACKAGE = "androidx.core.app.EXTRA_CALLING_PACKAGE";
+ field public static final String EXTRA_CALLING_PACKAGE_INTEROP = "android.support.v4.app.EXTRA_CALLING_PACKAGE";
+ }
+
+ public static class ShareCompat.IntentBuilder {
+ ctor public ShareCompat.IntentBuilder(android.content.Context);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailBcc(String);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailBcc(String![]);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailCc(String);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailCc(String![]);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailTo(String);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailTo(String![]);
+ method public androidx.core.app.ShareCompat.IntentBuilder addStream(android.net.Uri);
+ method public android.content.Intent createChooserIntent();
+ method @Deprecated public static androidx.core.app.ShareCompat.IntentBuilder from(android.app.Activity);
+ method public android.content.Intent getIntent();
+ method public androidx.core.app.ShareCompat.IntentBuilder setChooserTitle(@StringRes int);
+ method public androidx.core.app.ShareCompat.IntentBuilder setChooserTitle(CharSequence?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setEmailBcc(String![]?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setEmailCc(String![]?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setEmailTo(String![]?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setHtmlText(String?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setStream(android.net.Uri?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setSubject(String?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setText(CharSequence?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setType(String?);
+ method public void startChooser();
+ }
+
+ public static class ShareCompat.IntentReader {
+ ctor public ShareCompat.IntentReader(android.app.Activity);
+ ctor public ShareCompat.IntentReader(android.content.Context, android.content.Intent);
+ method @Deprecated public static androidx.core.app.ShareCompat.IntentReader from(android.app.Activity);
+ method public android.content.ComponentName? getCallingActivity();
+ method public android.graphics.drawable.Drawable? getCallingActivityIcon();
+ method public android.graphics.drawable.Drawable? getCallingApplicationIcon();
+ method public CharSequence? getCallingApplicationLabel();
+ method public String? getCallingPackage();
+ method public String![]? getEmailBcc();
+ method public String![]? getEmailCc();
+ method public String![]? getEmailTo();
+ method public String? getHtmlText();
+ method public android.net.Uri? getStream();
+ method public android.net.Uri? getStream(int);
+ method public int getStreamCount();
+ method public String? getSubject();
+ method public CharSequence? getText();
+ method public String? getType();
+ method public boolean isMultipleShare();
+ method public boolean isShareIntent();
+ method public boolean isSingleShare();
+ }
+
+ public abstract class SharedElementCallback {
+ ctor public SharedElementCallback();
+ method public android.os.Parcelable! onCaptureSharedElementSnapshot(android.view.View!, android.graphics.Matrix!, android.graphics.RectF!);
+ method public android.view.View! onCreateSnapshotView(android.content.Context!, android.os.Parcelable!);
+ method public void onMapSharedElements(java.util.List<java.lang.String!>!, java.util.Map<java.lang.String!,android.view.View!>!);
+ method public void onRejectSharedElements(java.util.List<android.view.View!>!);
+ method public void onSharedElementEnd(java.util.List<java.lang.String!>!, java.util.List<android.view.View!>!, java.util.List<android.view.View!>!);
+ method public void onSharedElementStart(java.util.List<java.lang.String!>!, java.util.List<android.view.View!>!, java.util.List<android.view.View!>!);
+ method public void onSharedElementsArrived(java.util.List<java.lang.String!>!, java.util.List<android.view.View!>!, androidx.core.app.SharedElementCallback.OnSharedElementsReadyListener!);
+ }
+
+ public static interface SharedElementCallback.OnSharedElementsReadyListener {
+ method public void onSharedElementsReady();
+ }
+
+ public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent> {
+ method public androidx.core.app.TaskStackBuilder addNextIntent(android.content.Intent);
+ method public androidx.core.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
+ method public androidx.core.app.TaskStackBuilder addParentStack(android.app.Activity);
+ method public androidx.core.app.TaskStackBuilder addParentStack(android.content.ComponentName);
+ method public androidx.core.app.TaskStackBuilder addParentStack(Class<?>);
+ method public static androidx.core.app.TaskStackBuilder create(android.content.Context);
+ method public android.content.Intent? editIntentAt(int);
+ method @Deprecated public static androidx.core.app.TaskStackBuilder! from(android.content.Context!);
+ method @Deprecated public android.content.Intent! getIntent(int);
+ method public int getIntentCount();
+ method public android.content.Intent![] getIntents();
+ method public android.app.PendingIntent? getPendingIntent(int, int);
+ method public android.app.PendingIntent? getPendingIntent(int, int, android.os.Bundle?);
+ method @Deprecated public java.util.Iterator<android.content.Intent!> iterator();
+ method public void startActivities();
+ method public void startActivities(android.os.Bundle?);
+ }
+
+ public static interface TaskStackBuilder.SupportParentable {
+ method public android.content.Intent? getSupportParentActivityIntent();
+ }
+
+}
+
+package androidx.core.content {
+
+ public final class ContentProviderCompat {
+ method public static android.content.Context requireContext(android.content.ContentProvider);
+ }
+
+ public final class ContentResolverCompat {
+ method public static android.database.Cursor? query(android.content.ContentResolver, android.net.Uri, String![]?, String?, String![]?, String?, android.os.CancellationSignal?);
+ method @Deprecated public static android.database.Cursor? query(android.content.ContentResolver, android.net.Uri, String![]?, String?, String![]?, String?, androidx.core.os.CancellationSignal?);
+ }
+
+ public class ContextCompat {
+ ctor protected ContextCompat();
+ method public static int checkSelfPermission(android.content.Context, String);
+ method public static android.content.Context createAttributionContext(android.content.Context, String?);
+ method public static android.content.Context? createDeviceProtectedStorageContext(android.content.Context);
+ method public static String? getAttributionTag(android.content.Context);
+ method public static java.io.File getCodeCacheDir(android.content.Context);
+ method @ColorInt public static int getColor(android.content.Context, @ColorRes int);
+ method public static android.content.res.ColorStateList? getColorStateList(android.content.Context, @ColorRes int);
+ method public static android.content.Context getContextForLanguage(android.content.Context);
+ method public static java.io.File? getDataDir(android.content.Context);
+ method public static android.view.Display getDisplayOrDefault(@DisplayContext android.content.Context);
+ method public static android.graphics.drawable.Drawable? getDrawable(android.content.Context, @DrawableRes int);
+ method public static java.io.File![] getExternalCacheDirs(android.content.Context);
+ method public static java.io.File![] getExternalFilesDirs(android.content.Context, String?);
+ method public static java.util.concurrent.Executor getMainExecutor(android.content.Context);
+ method public static java.io.File? getNoBackupFilesDir(android.content.Context);
+ method public static java.io.File![] getObbDirs(android.content.Context);
+ method public static String getString(android.content.Context, int);
+ method public static <T> T? getSystemService(android.content.Context, Class<T!>);
+ method public static String? getSystemServiceName(android.content.Context, Class<?>);
+ method public static boolean isDeviceProtectedStorage(android.content.Context);
+ method public static android.content.Intent? registerReceiver(android.content.Context, android.content.BroadcastReceiver?, android.content.IntentFilter, int);
+ method public static android.content.Intent? registerReceiver(android.content.Context, android.content.BroadcastReceiver?, android.content.IntentFilter, String?, android.os.Handler?, int);
+ method public static boolean startActivities(android.content.Context, android.content.Intent![]);
+ method public static boolean startActivities(android.content.Context, android.content.Intent![], android.os.Bundle?);
+ method public static void startActivity(android.content.Context, android.content.Intent, android.os.Bundle?);
+ method public static void startForegroundService(android.content.Context, android.content.Intent);
+ field public static final int RECEIVER_EXPORTED = 2; // 0x2
+ field public static final int RECEIVER_NOT_EXPORTED = 4; // 0x4
+ field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
+ }
+
+ public class FileProvider extends android.content.ContentProvider {
+ ctor public FileProvider();
+ ctor protected FileProvider(@XmlRes int);
+ method public int delete(android.net.Uri, String?, String![]?);
+ method public String? getType(android.net.Uri);
+ method public static android.net.Uri! getUriForFile(android.content.Context, String, java.io.File);
+ method public static android.net.Uri getUriForFile(android.content.Context, String, java.io.File, String);
+ method public android.net.Uri! insert(android.net.Uri, android.content.ContentValues);
+ method public boolean onCreate();
+ method public android.database.Cursor query(android.net.Uri, String![]?, String?, String![]?, String?);
+ method public int update(android.net.Uri, android.content.ContentValues, String?, String![]?);
+ }
+
+ public final class IntentCompat {
+ method public static android.content.Intent createManageUnusedAppRestrictionsIntent(android.content.Context, String);
+ method public static android.os.Parcelable![]? getParcelableArrayExtra(android.content.Intent, String?, Class<? extends android.os.Parcelable!>);
+ method public static <T> java.util.ArrayList<T!>? getParcelableArrayListExtra(android.content.Intent, String?, Class<? extends T!>);
+ method public static <T> T? getParcelableExtra(android.content.Intent, String?, Class<T!>);
+ method public static <T extends java.io.Serializable> T? getSerializableExtra(android.content.Intent, String?, Class<T!>);
+ method public static android.content.Intent makeMainSelectorActivity(String, String);
+ field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
+ field public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
+ field public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
+ field public static final String EXTRA_START_PLAYBACK = "android.intent.extra.START_PLAYBACK";
+ field public static final String EXTRA_TIME = "android.intent.extra.TIME";
+ }
+
+ public class IntentSanitizer {
+ method public android.content.Intent sanitize(android.content.Intent, androidx.core.util.Consumer<java.lang.String!>);
+ method public android.content.Intent sanitizeByFiltering(android.content.Intent);
+ method public android.content.Intent sanitizeByThrowing(android.content.Intent);
+ }
+
+ public static final class IntentSanitizer.Builder {
+ ctor public IntentSanitizer.Builder();
+ method public androidx.core.content.IntentSanitizer.Builder allowAction(androidx.core.util.Predicate<java.lang.String!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowAction(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowAnyComponent();
+ method public androidx.core.content.IntentSanitizer.Builder allowCategory(androidx.core.util.Predicate<java.lang.String!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowCategory(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowClipData(androidx.core.util.Predicate<android.content.ClipData!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowClipDataText();
+ method public androidx.core.content.IntentSanitizer.Builder allowClipDataUri(androidx.core.util.Predicate<android.net.Uri!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowClipDataUriWithAuthority(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowComponent(android.content.ComponentName);
+ method public androidx.core.content.IntentSanitizer.Builder allowComponent(androidx.core.util.Predicate<android.content.ComponentName!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowComponentWithPackage(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowData(androidx.core.util.Predicate<android.net.Uri!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowDataWithAuthority(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtra(String, androidx.core.util.Predicate<java.lang.Object!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtra(String, Class<?>);
+ method public <T> androidx.core.content.IntentSanitizer.Builder allowExtra(String, Class<T!>, androidx.core.util.Predicate<T!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtraOutput(androidx.core.util.Predicate<android.net.Uri!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtraOutput(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtraStream(androidx.core.util.Predicate<android.net.Uri!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtraStreamUriWithAuthority(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowFlags(int);
+ method public androidx.core.content.IntentSanitizer.Builder allowHistoryStackFlags();
+ method public androidx.core.content.IntentSanitizer.Builder allowIdentifier();
+ method public androidx.core.content.IntentSanitizer.Builder allowPackage(androidx.core.util.Predicate<java.lang.String!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowPackage(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowReceiverFlags();
+ method public androidx.core.content.IntentSanitizer.Builder allowSelector();
+ method public androidx.core.content.IntentSanitizer.Builder allowSourceBounds();
+ method public androidx.core.content.IntentSanitizer.Builder allowType(androidx.core.util.Predicate<java.lang.String!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowType(String);
+ method public androidx.core.content.IntentSanitizer build();
+ }
+
+ public final class LocusIdCompat {
+ ctor public LocusIdCompat(String);
+ method public String getId();
+ method @RequiresApi(29) public android.content.LocusId toLocusId();
+ method @RequiresApi(29) public static androidx.core.content.LocusIdCompat toLocusIdCompat(android.content.LocusId);
+ }
+
+ public final class MimeTypeFilter {
+ method public static boolean matches(String?, String);
+ method public static String? matches(String?, String![]);
+ method public static String? matches(String![]?, String);
+ method public static String![] matchesMany(String![]?, String);
+ }
+
+ public interface OnConfigurationChangedProvider {
+ method public void addOnConfigurationChangedListener(androidx.core.util.Consumer<android.content.res.Configuration> listener);
+ method public void removeOnConfigurationChangedListener(androidx.core.util.Consumer<android.content.res.Configuration> listener);
+ }
+
+ public interface OnTrimMemoryProvider {
+ method public void addOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
+ method public void removeOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
+ }
+
+ public final class PackageManagerCompat {
+ method public static com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> getUnusedAppRestrictionsStatus(android.content.Context);
+ field public static final String ACTION_PERMISSION_REVOCATION_SETTINGS = "android.intent.action.AUTO_REVOKE_PERMISSIONS";
+ }
+
+ public final class PermissionChecker {
+ method public static int checkCallingOrSelfPermission(android.content.Context, String);
+ method public static int checkCallingPermission(android.content.Context, String, String?);
+ method public static int checkPermission(android.content.Context, String, int, int, String?);
+ method public static int checkSelfPermission(android.content.Context, String);
+ field public static final int PERMISSION_DENIED = -1; // 0xffffffff
+ field public static final int PERMISSION_DENIED_APP_OP = -2; // 0xfffffffe
+ field public static final int PERMISSION_GRANTED = 0; // 0x0
+ }
+
+ @Deprecated public final class SharedPreferencesCompat {
+ }
+
+ @Deprecated public static final class SharedPreferencesCompat.EditorCompat {
+ method @Deprecated public void apply(android.content.SharedPreferences.Editor);
+ method @Deprecated public static androidx.core.content.SharedPreferencesCompat.EditorCompat! getInstance();
+ }
+
+ public class UnusedAppRestrictionsBackportCallback {
+ method public void onResult(boolean, boolean) throws android.os.RemoteException;
+ }
+
+ public abstract class UnusedAppRestrictionsBackportService extends android.app.Service {
+ ctor public UnusedAppRestrictionsBackportService();
+ method protected abstract void isPermissionRevocationEnabled(androidx.core.content.UnusedAppRestrictionsBackportCallback);
+ method public android.os.IBinder? onBind(android.content.Intent?);
+ field public static final String ACTION_UNUSED_APP_RESTRICTIONS_BACKPORT_CONNECTION = "android.support.unusedapprestrictions.action.CustomUnusedAppRestrictionsBackportService";
+ }
+
+ public final class UnusedAppRestrictionsConstants {
+ field public static final int API_30 = 4; // 0x4
+ field public static final int API_30_BACKPORT = 3; // 0x3
+ field public static final int API_31 = 5; // 0x5
+ field public static final int DISABLED = 2; // 0x2
+ field public static final int ERROR = 0; // 0x0
+ field public static final int FEATURE_NOT_AVAILABLE = 1; // 0x1
+ }
+
+ public class UriMatcherCompat {
+ method public static androidx.core.util.Predicate<android.net.Uri!> asPredicate(android.content.UriMatcher);
+ }
+
+}
+
+package androidx.core.content.pm {
+
+ @Deprecated public final class ActivityInfoCompat {
+ field @Deprecated public static final int CONFIG_UI_MODE = 512; // 0x200
+ }
+
+ public final class PackageInfoCompat {
+ method public static long getLongVersionCode(android.content.pm.PackageInfo);
+ method public static java.util.List<android.content.pm.Signature!> getSignatures(android.content.pm.PackageManager, String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static boolean hasSignatures(android.content.pm.PackageManager, String, @Size(min=1) java.util.Map<byte[]!,java.lang.Integer!>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+ }
+
+ public final class PermissionInfoCompat {
+ method public static int getProtection(android.content.pm.PermissionInfo);
+ method public static int getProtectionFlags(android.content.pm.PermissionInfo);
+ }
+
+ public class ShortcutInfoCompat {
+ method public android.content.ComponentName? getActivity();
+ method public java.util.Set<java.lang.String!>? getCategories();
+ method public CharSequence? getDisabledMessage();
+ method public int getDisabledReason();
+ method public int getExcludedFromSurfaces();
+ method public android.os.PersistableBundle? getExtras();
+ method public String getId();
+ method public android.content.Intent getIntent();
+ method public android.content.Intent![] getIntents();
+ method public long getLastChangedTimestamp();
+ method public androidx.core.content.LocusIdCompat? getLocusId();
+ method public CharSequence? getLongLabel();
+ method public String getPackage();
+ method public int getRank();
+ method public CharSequence getShortLabel();
+ method public android.os.UserHandle? getUserHandle();
+ method public boolean hasKeyFieldsOnly();
+ method public boolean isCached();
+ method public boolean isDeclaredInManifest();
+ method public boolean isDynamic();
+ method public boolean isEnabled();
+ method public boolean isExcludedFromSurfaces(int);
+ method public boolean isImmutable();
+ method public boolean isPinned();
+ method @RequiresApi(25) public android.content.pm.ShortcutInfo! toShortcutInfo();
+ field public static final int SURFACE_LAUNCHER = 1; // 0x1
+ }
+
+ public static class ShortcutInfoCompat.Builder {
+ ctor public ShortcutInfoCompat.Builder(android.content.Context, String);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder addCapabilityBinding(String);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder addCapabilityBinding(String, String, java.util.List<java.lang.String!>);
+ method public androidx.core.content.pm.ShortcutInfoCompat build();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setActivity(android.content.ComponentName);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setAlwaysBadged();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setCategories(java.util.Set<java.lang.String!>);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setDisabledMessage(CharSequence);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setExcludedFromSurfaces(int);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setExtras(android.os.PersistableBundle);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIcon(androidx.core.graphics.drawable.IconCompat!);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent![]);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIsConversation();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLocusId(androidx.core.content.LocusIdCompat?);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
+ method @Deprecated public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived(boolean);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person![]);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setRank(int);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setShortLabel(CharSequence);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setSliceUri(android.net.Uri);
+ }
+
+ public class ShortcutManagerCompat {
+ method public static boolean addDynamicShortcuts(android.content.Context, java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>);
+ method public static android.content.Intent createShortcutResultIntent(android.content.Context, androidx.core.content.pm.ShortcutInfoCompat);
+ method public static void disableShortcuts(android.content.Context, java.util.List<java.lang.String!>, CharSequence?);
+ method public static void enableShortcuts(android.content.Context, java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>);
+ method public static java.util.List<androidx.core.content.pm.ShortcutInfoCompat!> getDynamicShortcuts(android.content.Context);
+ method public static int getIconMaxHeight(android.content.Context);
+ method public static int getIconMaxWidth(android.content.Context);
+ method public static int getMaxShortcutCountPerActivity(android.content.Context);
+ method public static java.util.List<androidx.core.content.pm.ShortcutInfoCompat!> getShortcuts(android.content.Context, int);
+ method public static boolean isRateLimitingActive(android.content.Context);
+ method public static boolean isRequestPinShortcutSupported(android.content.Context);
+ method public static boolean pushDynamicShortcut(android.content.Context, androidx.core.content.pm.ShortcutInfoCompat);
+ method public static void removeAllDynamicShortcuts(android.content.Context);
+ method public static void removeDynamicShortcuts(android.content.Context, java.util.List<java.lang.String!>);
+ method public static void removeLongLivedShortcuts(android.content.Context, java.util.List<java.lang.String!>);
+ method public static void reportShortcutUsed(android.content.Context, String);
+ method public static boolean requestPinShortcut(android.content.Context, androidx.core.content.pm.ShortcutInfoCompat, android.content.IntentSender?);
+ method public static boolean setDynamicShortcuts(android.content.Context, java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>);
+ method public static boolean updateShortcuts(android.content.Context, java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>);
+ field public static final String EXTRA_SHORTCUT_ID = "android.intent.extra.shortcut.ID";
+ field public static final int FLAG_MATCH_CACHED = 8; // 0x8
+ field public static final int FLAG_MATCH_DYNAMIC = 2; // 0x2
+ field public static final int FLAG_MATCH_MANIFEST = 1; // 0x1
+ field public static final int FLAG_MATCH_PINNED = 4; // 0x4
+ }
+
+}
+
+package androidx.core.content.res {
+
+ public final class ConfigurationHelper {
+ method public static int getDensityDpi(android.content.res.Resources);
+ }
+
+ public final class ResourcesCompat {
+ method public static void clearCachesForTheme(android.content.res.Resources.Theme);
+ method public static android.graphics.Typeface? getCachedFont(android.content.Context, @FontRes int) throws android.content.res.Resources.NotFoundException;
+ method @ColorInt public static int getColor(android.content.res.Resources, @ColorRes int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static android.content.res.ColorStateList? getColorStateList(android.content.res.Resources, @ColorRes int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static android.graphics.drawable.Drawable? getDrawable(android.content.res.Resources, @DrawableRes int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static android.graphics.drawable.Drawable? getDrawableForDensity(android.content.res.Resources, @DrawableRes int, int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static float getFloat(android.content.res.Resources, @DimenRes int);
+ method public static android.graphics.Typeface? getFont(android.content.Context, @FontRes int) throws android.content.res.Resources.NotFoundException;
+ method public static void getFont(android.content.Context, @FontRes int, androidx.core.content.res.ResourcesCompat.FontCallback, android.os.Handler?) throws android.content.res.Resources.NotFoundException;
+ field @AnyRes public static final int ID_NULL = 0; // 0x0
+ }
+
+ public abstract static class ResourcesCompat.FontCallback {
+ ctor public ResourcesCompat.FontCallback();
+ method public abstract void onFontRetrievalFailed(int);
+ method public abstract void onFontRetrieved(android.graphics.Typeface);
+ }
+
+ public static final class ResourcesCompat.ThemeCompat {
+ method public static void rebase(android.content.res.Resources.Theme);
+ }
+
+}
+
+package androidx.core.database {
+
+ public final class CursorWindowCompat {
+ method public static android.database.CursorWindow create(String?, long);
+ }
+
+ @Deprecated public final class DatabaseUtilsCompat {
+ method @Deprecated public static String![]! appendSelectionArgs(String![]!, String![]!);
+ method @Deprecated public static String! concatenateWhere(String!, String!);
+ }
+
+}
+
+package androidx.core.database.sqlite {
+
+ public final class SQLiteCursorCompat {
+ method public static void setFillWindowForwardOnly(android.database.sqlite.SQLiteCursor, boolean);
+ }
+
+}
+
+package androidx.core.graphics {
+
+ public final class BitmapCompat {
+ method public static android.graphics.Bitmap createScaledBitmap(android.graphics.Bitmap, int, int, android.graphics.Rect?, boolean);
+ method public static int getAllocationByteCount(android.graphics.Bitmap);
+ method public static boolean hasMipMap(android.graphics.Bitmap);
+ method public static void setHasMipMap(android.graphics.Bitmap, boolean);
+ }
+
+ public class BlendModeColorFilterCompat {
+ method public static android.graphics.ColorFilter? createBlendModeColorFilterCompat(int, androidx.core.graphics.BlendModeCompat);
+ }
+
+ public enum BlendModeCompat {
+ enum_constant public static final androidx.core.graphics.BlendModeCompat CLEAR;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat COLOR;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat COLOR_BURN;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat COLOR_DODGE;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DARKEN;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat DIFFERENCE;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DST;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DST_ATOP;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DST_IN;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DST_OUT;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DST_OVER;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat EXCLUSION;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat HARD_LIGHT;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat HUE;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat LIGHTEN;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat LUMINOSITY;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat MODULATE;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat MULTIPLY;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat OVERLAY;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat PLUS;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat SATURATION;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SCREEN;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat SOFT_LIGHT;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SRC;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SRC_ATOP;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SRC_IN;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SRC_OUT;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SRC_OVER;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat XOR;
+ }
+
+ public final class ColorUtils {
+ method @ColorInt public static int HSLToColor(float[]);
+ method @ColorInt public static int LABToColor(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double);
+ method public static void LABToXYZ(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double, double[]);
+ method @ColorInt public static int M3HCTToColor(@FloatRange(from=0.0, to=360, toInclusive=false) float, @FloatRange(from=0.0, to=java.lang.Double.POSITIVE_INFINITY, toInclusive=false) float, @FloatRange(from=0.0, to=100) float);
+ method public static void RGBToHSL(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, float[]);
+ method public static void RGBToLAB(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
+ method public static void RGBToXYZ(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
+ method @ColorInt public static int XYZToColor(@FloatRange(from=0.0f, to=95.047) double, @FloatRange(from=0.0f, to=0x64) double, @FloatRange(from=0.0f, to=108.883) double);
+ method public static void XYZToLAB(@FloatRange(from=0.0f, to=95.047) double, @FloatRange(from=0.0f, to=0x64) double, @FloatRange(from=0.0f, to=108.883) double, double[]);
+ method @ColorInt public static int blendARGB(@ColorInt int, @ColorInt int, @FloatRange(from=0.0, to=1.0) float);
+ method public static void blendHSL(float[], float[], @FloatRange(from=0.0, to=1.0) float, float[]);
+ method public static void blendLAB(double[], double[], @FloatRange(from=0.0, to=1.0) double, double[]);
+ method public static double calculateContrast(@ColorInt int, @ColorInt int);
+ method @FloatRange(from=0.0, to=1.0) public static double calculateLuminance(@ColorInt int);
+ method public static int calculateMinimumAlpha(@ColorInt int, @ColorInt int, float);
+ method public static void colorToHSL(@ColorInt int, float[]);
+ method public static void colorToLAB(@ColorInt int, double[]);
+ method public static void colorToM3HCT(@ColorInt int, @Size(3) float[]);
+ method public static void colorToXYZ(@ColorInt int, double[]);
+ method @RequiresApi(26) public static android.graphics.Color compositeColors(android.graphics.Color, android.graphics.Color);
+ method public static int compositeColors(@ColorInt int, @ColorInt int);
+ method public static double distanceEuclidean(double[], double[]);
+ method @ColorInt public static int setAlphaComponent(@ColorInt int, @IntRange(from=0, to=255) int);
+ }
+
+ public final class Insets {
+ method public static androidx.core.graphics.Insets add(androidx.core.graphics.Insets, androidx.core.graphics.Insets);
+ method public static androidx.core.graphics.Insets max(androidx.core.graphics.Insets, androidx.core.graphics.Insets);
+ method public static androidx.core.graphics.Insets min(androidx.core.graphics.Insets, androidx.core.graphics.Insets);
+ method public static androidx.core.graphics.Insets of(android.graphics.Rect);
+ method public static androidx.core.graphics.Insets of(int, int, int, int);
+ method public static androidx.core.graphics.Insets subtract(androidx.core.graphics.Insets, androidx.core.graphics.Insets);
+ method @RequiresApi(api=29) public static androidx.core.graphics.Insets toCompatInsets(android.graphics.Insets);
+ method @RequiresApi(29) public android.graphics.Insets toPlatformInsets();
+ field public static final androidx.core.graphics.Insets NONE;
+ field public final int bottom;
+ field public final int left;
+ field public final int right;
+ field public final int top;
+ }
+
+ public final class PaintCompat {
+ method public static boolean hasGlyph(android.graphics.Paint, String);
+ method public static boolean setBlendMode(android.graphics.Paint, androidx.core.graphics.BlendModeCompat?);
+ }
+
+ public class PathParser {
+ method public static boolean canMorph(androidx.core.graphics.PathParser.PathDataNode![]?, androidx.core.graphics.PathParser.PathDataNode![]?);
+ method public static androidx.core.graphics.PathParser.PathDataNode![] createNodesFromPathData(String);
+ method public static android.graphics.Path createPathFromPathData(String);
+ method public static androidx.core.graphics.PathParser.PathDataNode![] deepCopyNodes(androidx.core.graphics.PathParser.PathDataNode![]);
+ method public static boolean interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], float);
+ method public static void updateNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![]);
+ }
+
+ public static class PathParser.PathDataNode {
+ method public float[] getParams();
+ method public char getType();
+ method public void interpolatePathDataNode(androidx.core.graphics.PathParser.PathDataNode, androidx.core.graphics.PathParser.PathDataNode, float);
+ method public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
+ }
+
+ public final class PathSegment {
+ ctor public PathSegment(android.graphics.PointF, float, android.graphics.PointF, float);
+ method public android.graphics.PointF getEnd();
+ method public float getEndFraction();
+ method public android.graphics.PointF getStart();
+ method public float getStartFraction();
+ }
+
+ public final class PathUtils {
+ method @RequiresApi(26) public static java.util.Collection<androidx.core.graphics.PathSegment!> flatten(android.graphics.Path);
+ method @RequiresApi(26) public static java.util.Collection<androidx.core.graphics.PathSegment!> flatten(android.graphics.Path, @FloatRange(from=0) float);
+ }
+
+ public class TypefaceCompat {
+ method public static android.graphics.Typeface create(android.content.Context, android.graphics.Typeface?, int);
+ method public static android.graphics.Typeface create(android.content.Context, android.graphics.Typeface?, @IntRange(from=1, to=1000) int, boolean);
+ }
+
+}
+
+package androidx.core.graphics.drawable {
+
+ public final class DrawableCompat {
+ method public static void applyTheme(android.graphics.drawable.Drawable, android.content.res.Resources.Theme);
+ method public static boolean canApplyTheme(android.graphics.drawable.Drawable);
+ method public static void clearColorFilter(android.graphics.drawable.Drawable);
+ method public static int getAlpha(android.graphics.drawable.Drawable);
+ method public static android.graphics.ColorFilter? getColorFilter(android.graphics.drawable.Drawable);
+ method public static int getLayoutDirection(android.graphics.drawable.Drawable);
+ method public static void inflate(android.graphics.drawable.Drawable, android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static boolean isAutoMirrored(android.graphics.drawable.Drawable);
+ method @Deprecated public static void jumpToCurrentState(android.graphics.drawable.Drawable);
+ method public static void setAutoMirrored(android.graphics.drawable.Drawable, boolean);
+ method public static void setHotspot(android.graphics.drawable.Drawable, float, float);
+ method public static void setHotspotBounds(android.graphics.drawable.Drawable, int, int, int, int);
+ method public static boolean setLayoutDirection(android.graphics.drawable.Drawable, int);
+ method public static void setTint(android.graphics.drawable.Drawable, @ColorInt int);
+ method public static void setTintList(android.graphics.drawable.Drawable, android.content.res.ColorStateList?);
+ method public static void setTintMode(android.graphics.drawable.Drawable, android.graphics.PorterDuff.Mode?);
+ method public static <T extends android.graphics.drawable.Drawable> T! unwrap(android.graphics.drawable.Drawable);
+ method public static android.graphics.drawable.Drawable wrap(android.graphics.drawable.Drawable);
+ }
+
+ public class IconCompat implements androidx.versionedparcelable.VersionedParcelable {
+ method public static androidx.core.graphics.drawable.IconCompat? createFromBundle(android.os.Bundle);
+ method @RequiresApi(23) public static androidx.core.graphics.drawable.IconCompat? createFromIcon(android.content.Context, android.graphics.drawable.Icon);
+ method public static androidx.core.graphics.drawable.IconCompat createWithAdaptiveBitmap(android.graphics.Bitmap);
+ method public static androidx.core.graphics.drawable.IconCompat createWithAdaptiveBitmapContentUri(android.net.Uri);
+ method public static androidx.core.graphics.drawable.IconCompat createWithAdaptiveBitmapContentUri(String);
+ method public static androidx.core.graphics.drawable.IconCompat createWithBitmap(android.graphics.Bitmap);
+ method public static androidx.core.graphics.drawable.IconCompat createWithContentUri(android.net.Uri);
+ method public static androidx.core.graphics.drawable.IconCompat createWithContentUri(String);
+ method public static androidx.core.graphics.drawable.IconCompat createWithData(byte[], int, int);
+ method public static androidx.core.graphics.drawable.IconCompat createWithResource(android.content.Context, @DrawableRes int);
+ method @DrawableRes public int getResId();
+ method public String getResPackage();
+ method public int getType();
+ method public android.net.Uri getUri();
+ method public android.graphics.drawable.Drawable? loadDrawable(android.content.Context);
+ method public void onPostParceling();
+ method public void onPreParceling(boolean);
+ method public androidx.core.graphics.drawable.IconCompat setTint(@ColorInt int);
+ method public androidx.core.graphics.drawable.IconCompat setTintList(android.content.res.ColorStateList?);
+ method public androidx.core.graphics.drawable.IconCompat setTintMode(android.graphics.PorterDuff.Mode?);
+ method public android.os.Bundle toBundle();
+ method @Deprecated @RequiresApi(23) public android.graphics.drawable.Icon toIcon();
+ method @RequiresApi(23) public android.graphics.drawable.Icon toIcon(android.content.Context?);
+ field public static final int TYPE_ADAPTIVE_BITMAP = 5; // 0x5
+ field public static final int TYPE_BITMAP = 1; // 0x1
+ field public static final int TYPE_DATA = 3; // 0x3
+ field public static final int TYPE_RESOURCE = 2; // 0x2
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ field public static final int TYPE_URI = 4; // 0x4
+ field public static final int TYPE_URI_ADAPTIVE_BITMAP = 6; // 0x6
+ }
+
+ public abstract class RoundedBitmapDrawable extends android.graphics.drawable.Drawable {
+ method public void draw(android.graphics.Canvas);
+ method public final android.graphics.Bitmap? getBitmap();
+ method public float getCornerRadius();
+ method public int getGravity();
+ method public int getOpacity();
+ method public final android.graphics.Paint getPaint();
+ method public boolean hasAntiAlias();
+ method public boolean hasMipMap();
+ method public boolean isCircular();
+ method public void setAlpha(int);
+ method public void setAntiAlias(boolean);
+ method public void setCircular(boolean);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setCornerRadius(float);
+ method public void setDither(boolean);
+ method public void setGravity(int);
+ method public void setMipMap(boolean);
+ method public void setTargetDensity(android.graphics.Canvas);
+ method public void setTargetDensity(android.util.DisplayMetrics);
+ method public void setTargetDensity(int);
+ }
+
+ public final class RoundedBitmapDrawableFactory {
+ method public static androidx.core.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, android.graphics.Bitmap?);
+ method public static androidx.core.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, java.io.InputStream);
+ method public static androidx.core.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, String);
+ }
+
+}
+
+package androidx.core.hardware.display {
+
+ public final class DisplayManagerCompat {
+ method public android.view.Display? getDisplay(int);
+ method public android.view.Display![] getDisplays();
+ method public android.view.Display![] getDisplays(String?);
+ method public static androidx.core.hardware.display.DisplayManagerCompat getInstance(android.content.Context);
+ field public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
+ }
+
+}
+
+package androidx.core.hardware.fingerprint {
+
+ @Deprecated public class FingerprintManagerCompat {
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public void authenticate(androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject?, int, android.os.CancellationSignal?, androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback, android.os.Handler?);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public void authenticate(androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject?, int, androidx.core.os.CancellationSignal?, androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback, android.os.Handler?);
+ method @Deprecated public static androidx.core.hardware.fingerprint.FingerprintManagerCompat from(android.content.Context);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
+ }
+
+ @Deprecated public abstract static class FingerprintManagerCompat.AuthenticationCallback {
+ ctor @Deprecated public FingerprintManagerCompat.AuthenticationCallback();
+ method @Deprecated public void onAuthenticationError(int, CharSequence!);
+ method @Deprecated public void onAuthenticationFailed();
+ method @Deprecated public void onAuthenticationHelp(int, CharSequence!);
+ method @Deprecated public void onAuthenticationSucceeded(androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationResult!);
+ }
+
+ @Deprecated public static final class FingerprintManagerCompat.AuthenticationResult {
+ ctor @Deprecated public FingerprintManagerCompat.AuthenticationResult(androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject!);
+ method @Deprecated public androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject! getCryptoObject();
+ }
+
+ @Deprecated public static class FingerprintManagerCompat.CryptoObject {
+ ctor @Deprecated public FingerprintManagerCompat.CryptoObject(java.security.Signature);
+ ctor @Deprecated public FingerprintManagerCompat.CryptoObject(javax.crypto.Cipher);
+ ctor @Deprecated public FingerprintManagerCompat.CryptoObject(javax.crypto.Mac);
+ method @Deprecated public javax.crypto.Cipher? getCipher();
+ method @Deprecated public javax.crypto.Mac? getMac();
+ method @Deprecated public java.security.Signature? getSignature();
+ }
+
+}
+
+package androidx.core.location {
+
+ public abstract class GnssStatusCompat {
+ method @FloatRange(from=0, to=360) public abstract float getAzimuthDegrees(@IntRange(from=0) int);
+ method @FloatRange(from=0, to=63) public abstract float getBasebandCn0DbHz(@IntRange(from=0) int);
+ method @FloatRange(from=0) public abstract float getCarrierFrequencyHz(@IntRange(from=0) int);
+ method @FloatRange(from=0, to=63) public abstract float getCn0DbHz(@IntRange(from=0) int);
+ method public abstract int getConstellationType(@IntRange(from=0) int);
+ method @FloatRange(from=0xffffffa6, to=90) public abstract float getElevationDegrees(@IntRange(from=0) int);
+ method @IntRange(from=0) public abstract int getSatelliteCount();
+ method @IntRange(from=1, to=200) public abstract int getSvid(@IntRange(from=0) int);
+ method public abstract boolean hasAlmanacData(@IntRange(from=0) int);
+ method public abstract boolean hasBasebandCn0DbHz(@IntRange(from=0) int);
+ method public abstract boolean hasCarrierFrequencyHz(@IntRange(from=0) int);
+ method public abstract boolean hasEphemerisData(@IntRange(from=0) int);
+ method public abstract boolean usedInFix(@IntRange(from=0) int);
+ method @RequiresApi(android.os.Build.VERSION_CODES.N) public static androidx.core.location.GnssStatusCompat wrap(android.location.GnssStatus);
+ method public static androidx.core.location.GnssStatusCompat wrap(android.location.GpsStatus);
+ field public static final int CONSTELLATION_BEIDOU = 5; // 0x5
+ field public static final int CONSTELLATION_GALILEO = 6; // 0x6
+ field public static final int CONSTELLATION_GLONASS = 3; // 0x3
+ field public static final int CONSTELLATION_GPS = 1; // 0x1
+ field public static final int CONSTELLATION_IRNSS = 7; // 0x7
+ field public static final int CONSTELLATION_QZSS = 4; // 0x4
+ field public static final int CONSTELLATION_SBAS = 2; // 0x2
+ field public static final int CONSTELLATION_UNKNOWN = 0; // 0x0
+ }
+
+ public abstract static class GnssStatusCompat.Callback {
+ ctor public GnssStatusCompat.Callback();
+ method public void onFirstFix(@IntRange(from=0) int);
+ method public void onSatelliteStatusChanged(androidx.core.location.GnssStatusCompat);
+ method public void onStarted();
+ method public void onStopped();
+ }
+
+ public final class LocationCompat {
+ method public static float getBearingAccuracyDegrees(android.location.Location);
+ method public static long getElapsedRealtimeMillis(android.location.Location);
+ method public static long getElapsedRealtimeNanos(android.location.Location);
+ method @FloatRange(from=0.0) public static float getMslAltitudeAccuracyMeters(android.location.Location);
+ method public static double getMslAltitudeMeters(android.location.Location);
+ method public static float getSpeedAccuracyMetersPerSecond(android.location.Location);
+ method public static float getVerticalAccuracyMeters(android.location.Location);
+ method public static boolean hasBearingAccuracy(android.location.Location);
+ method public static boolean hasMslAltitude(android.location.Location);
+ method public static boolean hasMslAltitudeAccuracy(android.location.Location);
+ method public static boolean hasSpeedAccuracy(android.location.Location);
+ method public static boolean hasVerticalAccuracy(android.location.Location);
+ method public static boolean isMock(android.location.Location);
+ method public static void removeBearingAccuracy(android.location.Location);
+ method public static void removeMslAltitude(android.location.Location);
+ method public static void removeMslAltitudeAccuracy(android.location.Location);
+ method public static void removeSpeedAccuracy(android.location.Location);
+ method public static void removeVerticalAccuracy(android.location.Location);
+ method public static void setBearingAccuracyDegrees(android.location.Location, float);
+ method public static void setMock(android.location.Location, boolean);
+ method public static void setMslAltitudeAccuracyMeters(android.location.Location, @FloatRange(from=0.0) float);
+ method public static void setMslAltitudeMeters(android.location.Location, double);
+ method public static void setSpeedAccuracyMetersPerSecond(android.location.Location, float);
+ method public static void setVerticalAccuracyMeters(android.location.Location, float);
+ field public static final String EXTRA_BEARING_ACCURACY = "bearingAccuracy";
+ field public static final String EXTRA_IS_MOCK = "mockLocation";
+ field public static final String EXTRA_MSL_ALTITUDE = "androidx.core.location.extra.MSL_ALTITUDE";
+ field public static final String EXTRA_MSL_ALTITUDE_ACCURACY = "androidx.core.location.extra.MSL_ALTITUDE_ACCURACY";
+ field public static final String EXTRA_SPEED_ACCURACY = "speedAccuracy";
+ field public static final String EXTRA_VERTICAL_ACCURACY = "verticalAccuracy";
+ }
+
+ public interface LocationListenerCompat extends android.location.LocationListener {
+ method public default void onStatusChanged(String, int, android.os.Bundle?);
+ }
+
+ public final class LocationManagerCompat {
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static void getCurrentLocation(android.location.LocationManager, String, android.os.CancellationSignal?, java.util.concurrent.Executor, androidx.core.util.Consumer<android.location.Location!>);
+ method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static void getCurrentLocation(android.location.LocationManager, String, androidx.core.os.CancellationSignal?, java.util.concurrent.Executor, androidx.core.util.Consumer<android.location.Location!>);
+ method public static String? getGnssHardwareModelName(android.location.LocationManager);
+ method public static int getGnssYearOfHardware(android.location.LocationManager);
+ method public static boolean hasProvider(android.location.LocationManager, String);
+ method public static boolean isLocationEnabled(android.location.LocationManager);
+ method @RequiresApi(android.os.Build.VERSION_CODES.N) @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static boolean registerGnssMeasurementsCallback(android.location.LocationManager, android.location.GnssMeasurementsEvent.Callback, android.os.Handler);
+ method @RequiresApi(android.os.Build.VERSION_CODES.N) @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static boolean registerGnssMeasurementsCallback(android.location.LocationManager, java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static boolean registerGnssStatusCallback(android.location.LocationManager, androidx.core.location.GnssStatusCompat.Callback, android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static boolean registerGnssStatusCallback(android.location.LocationManager, java.util.concurrent.Executor, androidx.core.location.GnssStatusCompat.Callback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static void removeUpdates(android.location.LocationManager, androidx.core.location.LocationListenerCompat);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static void requestLocationUpdates(android.location.LocationManager, String, androidx.core.location.LocationRequestCompat, androidx.core.location.LocationListenerCompat, android.os.Looper);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static void requestLocationUpdates(android.location.LocationManager, String, androidx.core.location.LocationRequestCompat, java.util.concurrent.Executor, androidx.core.location.LocationListenerCompat);
+ method @RequiresApi(android.os.Build.VERSION_CODES.N) public static void unregisterGnssMeasurementsCallback(android.location.LocationManager, android.location.GnssMeasurementsEvent.Callback);
+ method public static void unregisterGnssStatusCallback(android.location.LocationManager, androidx.core.location.GnssStatusCompat.Callback);
+ }
+
+ public final class LocationRequestCompat {
+ method @IntRange(from=1) public long getDurationMillis();
+ method @IntRange(from=0) public long getIntervalMillis();
+ method @IntRange(from=0) public long getMaxUpdateDelayMillis();
+ method @IntRange(from=1, to=java.lang.Integer.MAX_VALUE) public int getMaxUpdates();
+ method @FloatRange(from=0, to=java.lang.Float.MAX_VALUE) public float getMinUpdateDistanceMeters();
+ method @IntRange(from=0) public long getMinUpdateIntervalMillis();
+ method public int getQuality();
+ method @RequiresApi(31) public android.location.LocationRequest toLocationRequest();
+ method public android.location.LocationRequest? toLocationRequest(String);
+ field public static final long PASSIVE_INTERVAL = 9223372036854775807L; // 0x7fffffffffffffffL
+ field public static final int QUALITY_BALANCED_POWER_ACCURACY = 102; // 0x66
+ field public static final int QUALITY_HIGH_ACCURACY = 100; // 0x64
+ field public static final int QUALITY_LOW_POWER = 104; // 0x68
+ }
+
+ public static final class LocationRequestCompat.Builder {
+ ctor public LocationRequestCompat.Builder(androidx.core.location.LocationRequestCompat);
+ ctor public LocationRequestCompat.Builder(long);
+ method public androidx.core.location.LocationRequestCompat build();
+ method public androidx.core.location.LocationRequestCompat.Builder clearMinUpdateIntervalMillis();
+ method public androidx.core.location.LocationRequestCompat.Builder setDurationMillis(@IntRange(from=1) long);
+ method public androidx.core.location.LocationRequestCompat.Builder setIntervalMillis(@IntRange(from=0) long);
+ method public androidx.core.location.LocationRequestCompat.Builder setMaxUpdateDelayMillis(@IntRange(from=0) long);
+ method public androidx.core.location.LocationRequestCompat.Builder setMaxUpdates(@IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int);
+ method public androidx.core.location.LocationRequestCompat.Builder setMinUpdateDistanceMeters(@FloatRange(from=0, to=java.lang.Float.MAX_VALUE) float);
+ method public androidx.core.location.LocationRequestCompat.Builder setMinUpdateIntervalMillis(@IntRange(from=0) long);
+ method public androidx.core.location.LocationRequestCompat.Builder setQuality(int);
+ }
+
+}
+
+package androidx.core.math {
+
+ public class MathUtils {
+ method public static int addExact(int, int);
+ method public static long addExact(long, long);
+ method public static double clamp(double, double, double);
+ method public static float clamp(float, float, float);
+ method public static int clamp(int, int, int);
+ method public static long clamp(long, long, long);
+ method public static int decrementExact(int);
+ method public static long decrementExact(long);
+ method public static int incrementExact(int);
+ method public static long incrementExact(long);
+ method public static int multiplyExact(int, int);
+ method public static long multiplyExact(long, long);
+ method public static int negateExact(int);
+ method public static long negateExact(long);
+ method public static int subtractExact(int, int);
+ method public static long subtractExact(long, long);
+ method public static int toIntExact(long);
+ }
+
+}
+
+package androidx.core.net {
+
+ public final class ConnectivityManagerCompat {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public static android.net.NetworkInfo? getNetworkInfoFromBroadcast(android.net.ConnectivityManager, android.content.Intent);
+ method public static int getRestrictBackgroundStatus(android.net.ConnectivityManager);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public static boolean isActiveNetworkMetered(android.net.ConnectivityManager);
+ field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+ field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+ field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
+ }
+
+ public final class MailTo {
+ method public String? getBcc();
+ method public String? getBody();
+ method public String? getCc();
+ method public java.util.Map<java.lang.String!,java.lang.String!>? getHeaders();
+ method public String? getSubject();
+ method public String? getTo();
+ method public static boolean isMailTo(android.net.Uri?);
+ method public static boolean isMailTo(String?);
+ method public static androidx.core.net.MailTo parse(android.net.Uri) throws androidx.core.net.ParseException;
+ method public static androidx.core.net.MailTo parse(String) throws androidx.core.net.ParseException;
+ field public static final String MAILTO_SCHEME = "mailto:";
+ }
+
+ public class ParseException extends java.lang.RuntimeException {
+ field public final String response;
+ }
+
+ public final class TrafficStatsCompat {
+ method @Deprecated public static void clearThreadStatsTag();
+ method @Deprecated public static int getThreadStatsTag();
+ method @Deprecated public static void incrementOperationCount(int);
+ method @Deprecated public static void incrementOperationCount(int, int);
+ method @Deprecated public static void setThreadStatsTag(int);
+ method public static void tagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
+ method @Deprecated public static void tagSocket(java.net.Socket!) throws java.net.SocketException;
+ method public static void untagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
+ method @Deprecated public static void untagSocket(java.net.Socket!) throws java.net.SocketException;
+ }
+
+ public final class UriCompat {
+ method public static String toSafeString(android.net.Uri);
+ }
+
+}
+
+package androidx.core.os {
+
+ public final class BuildCompat {
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.N) public static boolean isAtLeastN();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.N_MR1) public static boolean isAtLeastNMR1();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.O) public static boolean isAtLeastO();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.O_MR1) public static boolean isAtLeastOMR1();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.P) public static boolean isAtLeastP();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
+ method @Deprecated @ChecksSdkIntAtLeast(api=31, codename="S") public static boolean isAtLeastS();
+ method @Deprecated @ChecksSdkIntAtLeast(api=32, codename="Sv2") public static boolean isAtLeastSv2();
+ method @Deprecated @ChecksSdkIntAtLeast(api=33, codename="Tiramisu") public static boolean isAtLeastT();
+ method @Deprecated @ChecksSdkIntAtLeast(api=34, codename="UpsideDownCake") public static boolean isAtLeastU();
+ method @SuppressCompatibility @ChecksSdkIntAtLeast(codename="VanillaIceCream") @androidx.core.os.BuildCompat.PrereleaseSdkCheck public static boolean isAtLeastV();
+ field @ChecksSdkIntAtLeast(extension=android.os.ext.SdkExtensions.AD_SERVICES) public static final int AD_SERVICES_EXTENSION_INT;
+ field public static final androidx.core.os.BuildCompat INSTANCE;
+ field @ChecksSdkIntAtLeast(extension=android.os.Build.VERSION_CODES.R) public static final int R_EXTENSION_INT;
+ field @ChecksSdkIntAtLeast(extension=android.os.Build.VERSION_CODES.S) public static final int S_EXTENSION_INT;
+ field @ChecksSdkIntAtLeast(extension=android.os.Build.VERSION_CODES.TIRAMISU) public static final int T_EXTENSION_INT;
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public static @interface BuildCompat.PrereleaseSdkCheck {
+ }
+
+ public final class BundleCompat {
+ method public static android.os.IBinder? getBinder(android.os.Bundle, String?);
+ method public static <T> T? getParcelable(android.os.Bundle, String?, Class<T!>);
+ method public static android.os.Parcelable![]? getParcelableArray(android.os.Bundle, String?, Class<? extends android.os.Parcelable!>);
+ method public static <T> java.util.ArrayList<T!>? getParcelableArrayList(android.os.Bundle, String?, Class<? extends T!>);
+ method public static <T extends java.io.Serializable> T? getSerializable(android.os.Bundle, String?, Class<T!>);
+ method public static <T> android.util.SparseArray<T!>? getSparseParcelableArray(android.os.Bundle, String?, Class<? extends T!>);
+ method public static void putBinder(android.os.Bundle, String?, android.os.IBinder?);
+ }
+
+ @Deprecated public final class CancellationSignal {
+ ctor @Deprecated public CancellationSignal();
+ method @Deprecated public void cancel();
+ method @Deprecated public Object? getCancellationSignalObject();
+ method @Deprecated public boolean isCanceled();
+ method @Deprecated public void setOnCancelListener(androidx.core.os.CancellationSignal.OnCancelListener?);
+ method @Deprecated public void throwIfCanceled();
+ }
+
+ @Deprecated public static interface CancellationSignal.OnCancelListener {
+ method @Deprecated public void onCancel();
+ }
+
+ public final class ConfigurationCompat {
+ method public static androidx.core.os.LocaleListCompat getLocales(android.content.res.Configuration);
+ method public static void setLocales(android.content.res.Configuration, androidx.core.os.LocaleListCompat);
+ }
+
+ public final class EnvironmentCompat {
+ method public static String getStorageState(java.io.File);
+ field public static final String MEDIA_UNKNOWN = "unknown";
+ }
+
+ public final class ExecutorCompat {
+ method public static java.util.concurrent.Executor create(android.os.Handler);
+ }
+
+ public final class HandlerCompat {
+ method public static android.os.Handler createAsync(android.os.Looper);
+ method public static android.os.Handler createAsync(android.os.Looper, android.os.Handler.Callback);
+ method public static boolean hasCallbacks(android.os.Handler, Runnable);
+ method public static boolean postDelayed(android.os.Handler, Runnable, Object?, long);
+ }
+
+ public final class LocaleListCompat {
+ method public static androidx.core.os.LocaleListCompat create(java.util.Locale!...);
+ method public static androidx.core.os.LocaleListCompat forLanguageTags(String?);
+ method public java.util.Locale? get(int);
+ method @Size(min=1) public static androidx.core.os.LocaleListCompat getAdjustedDefault();
+ method @Size(min=1) public static androidx.core.os.LocaleListCompat getDefault();
+ method public static androidx.core.os.LocaleListCompat getEmptyLocaleList();
+ method public java.util.Locale? getFirstMatch(String![]);
+ method @IntRange(from=0xffffffff) public int indexOf(java.util.Locale?);
+ method public boolean isEmpty();
+ method @RequiresApi(21) public static boolean matchesLanguageAndScript(java.util.Locale, java.util.Locale);
+ method @IntRange(from=0) public int size();
+ method public String toLanguageTags();
+ method public Object? unwrap();
+ method @RequiresApi(24) public static androidx.core.os.LocaleListCompat wrap(android.os.LocaleList);
+ method @Deprecated @RequiresApi(24) public static androidx.core.os.LocaleListCompat! wrap(Object!);
+ }
+
+ public final class MessageCompat {
+ method public static boolean isAsynchronous(android.os.Message);
+ method public static void setAsynchronous(android.os.Message, boolean);
+ }
+
+ public class OperationCanceledException extends java.lang.RuntimeException {
+ ctor public OperationCanceledException();
+ ctor public OperationCanceledException(String?);
+ }
+
+ public final class ParcelCompat {
+ method public static <T> Object![]? readArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> java.util.ArrayList<T!>? readArrayList(android.os.Parcel, ClassLoader?, Class<? extends T!>);
+ method public static boolean readBoolean(android.os.Parcel);
+ method public static <K, V> java.util.HashMap<K!,V!>? readHashMap(android.os.Parcel, ClassLoader?, Class<? extends K!>, Class<? extends V!>);
+ method public static <T> void readList(android.os.Parcel, java.util.List<? super T!>, ClassLoader?, Class<T!>);
+ method public static <K, V> void readMap(android.os.Parcel, java.util.Map<? super K!,? super V!>, ClassLoader?, Class<K!>, Class<V!>);
+ method public static <T extends android.os.Parcelable> T? readParcelable(android.os.Parcel, ClassLoader?, Class<T!>);
+ method @Deprecated public static <T> T![]? readParcelableArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> android.os.Parcelable![]? readParcelableArrayTyped(android.os.Parcel, ClassLoader?, Class<T!>);
+ method @RequiresApi(30) public static <T> android.os.Parcelable.Creator<T!>? readParcelableCreator(android.os.Parcel, ClassLoader?, Class<T!>);
+ method @RequiresApi(api=android.os.Build.VERSION_CODES.Q) public static <T> java.util.List<T!> readParcelableList(android.os.Parcel, java.util.List<T!>, ClassLoader?, Class<T!>);
+ method public static <T extends java.io.Serializable> T? readSerializable(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> android.util.SparseArray<T!>? readSparseArray(android.os.Parcel, ClassLoader?, Class<? extends T!>);
+ method public static void writeBoolean(android.os.Parcel, boolean);
+ }
+
+ @Deprecated public final class ParcelableCompat {
+ method @Deprecated public static <T> android.os.Parcelable.Creator<T!>! newCreator(androidx.core.os.ParcelableCompatCreatorCallbacks<T!>!);
+ }
+
+ @Deprecated public interface ParcelableCompatCreatorCallbacks<T> {
+ method @Deprecated public T! createFromParcel(android.os.Parcel!, ClassLoader!);
+ method @Deprecated public T![]! newArray(int);
+ }
+
+ public final class ProcessCompat {
+ method public static boolean isApplicationUid(int);
+ }
+
+ @Deprecated public final class TraceCompat {
+ method @Deprecated public static void beginAsyncSection(String, int);
+ method @Deprecated public static void beginSection(String);
+ method @Deprecated public static void endAsyncSection(String, int);
+ method @Deprecated public static void endSection();
+ method @Deprecated public static boolean isEnabled();
+ method @Deprecated public static void setCounter(String, int);
+ }
+
+ public class UserHandleCompat {
+ method public static android.os.UserHandle getUserHandleForUid(int);
+ }
+
+ public class UserManagerCompat {
+ method public static boolean isUserUnlocked(android.content.Context);
+ }
+
+}
+
+package androidx.core.provider {
+
+ public final class DocumentsContractCompat {
+ method public static android.net.Uri? buildChildDocumentsUri(String, String?);
+ method public static android.net.Uri? buildChildDocumentsUriUsingTree(android.net.Uri, String);
+ method public static android.net.Uri? buildDocumentUri(String, String);
+ method public static android.net.Uri? buildDocumentUriUsingTree(android.net.Uri, String);
+ method public static android.net.Uri? buildTreeDocumentUri(String, String);
+ method public static android.net.Uri? createDocument(android.content.ContentResolver, android.net.Uri, String, String) throws java.io.FileNotFoundException;
+ method public static String? getDocumentId(android.net.Uri);
+ method public static String? getTreeDocumentId(android.net.Uri);
+ method public static boolean isDocumentUri(android.content.Context, android.net.Uri?);
+ method public static boolean isTreeUri(android.net.Uri);
+ method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+ method public static android.net.Uri? renameDocument(android.content.ContentResolver, android.net.Uri, String) throws java.io.FileNotFoundException;
+ }
+
+ public static final class DocumentsContractCompat.DocumentCompat {
+ field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200
+ }
+
+ public final class FontRequest {
+ ctor public FontRequest(String, String, String, @ArrayRes int);
+ ctor public FontRequest(String, String, String, java.util.List<java.util.List<byte[]!>!>);
+ method public java.util.List<java.util.List<byte[]!>!>? getCertificates();
+ method @ArrayRes public int getCertificatesArrayResId();
+ method public String getProviderAuthority();
+ method public String getProviderPackage();
+ method public String getQuery();
+ }
+
+ public class FontsContractCompat {
+ method public static android.graphics.Typeface? buildTypeface(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo![]);
+ method public static androidx.core.provider.FontsContractCompat.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static void requestFont(android.content.Context, androidx.core.provider.FontRequest, androidx.core.provider.FontsContractCompat.FontRequestCallback, android.os.Handler);
+ }
+
+ public static final class FontsContractCompat.Columns implements android.provider.BaseColumns {
+ ctor public FontsContractCompat.Columns();
+ field public static final String FILE_ID = "file_id";
+ field public static final String ITALIC = "font_italic";
+ field public static final String RESULT_CODE = "result_code";
+ field public static final int RESULT_CODE_FONT_NOT_FOUND = 1; // 0x1
+ field public static final int RESULT_CODE_FONT_UNAVAILABLE = 2; // 0x2
+ field public static final int RESULT_CODE_MALFORMED_QUERY = 3; // 0x3
+ field public static final int RESULT_CODE_OK = 0; // 0x0
+ field public static final String TTC_INDEX = "font_ttc_index";
+ field public static final String VARIATION_SETTINGS = "font_variation_settings";
+ field public static final String WEIGHT = "font_weight";
+ }
+
+ public static class FontsContractCompat.FontFamilyResult {
+ method public androidx.core.provider.FontsContractCompat.FontInfo![]! getFonts();
+ method public int getStatusCode();
+ field public static final int STATUS_OK = 0; // 0x0
+ field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
+ field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
+ }
+
+ public static class FontsContractCompat.FontInfo {
+ method public int getResultCode();
+ method @IntRange(from=0) public int getTtcIndex();
+ method public android.net.Uri getUri();
+ method @IntRange(from=1, to=1000) public int getWeight();
+ method public boolean isItalic();
+ }
+
+ public static class FontsContractCompat.FontRequestCallback {
+ ctor public FontsContractCompat.FontRequestCallback();
+ method public void onTypefaceRequestFailed(int);
+ method public void onTypefaceRetrieved(android.graphics.Typeface!);
+ field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
+ field public static final int FAIL_REASON_FONT_NOT_FOUND = 1; // 0x1
+ field public static final int FAIL_REASON_FONT_UNAVAILABLE = 2; // 0x2
+ field public static final int FAIL_REASON_MALFORMED_QUERY = 3; // 0x3
+ field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
+ field public static final int FAIL_REASON_SECURITY_VIOLATION = -4; // 0xfffffffc
+ field public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
+ }
+
+}
+
+package androidx.core.service.quicksettings {
+
+ public class PendingIntentActivityWrapper {
+ ctor public PendingIntentActivityWrapper(android.content.Context, int, android.content.Intent, int, android.os.Bundle?, boolean);
+ ctor public PendingIntentActivityWrapper(android.content.Context, int, android.content.Intent, int, boolean);
+ method public android.content.Context getContext();
+ method public int getFlags();
+ method public android.content.Intent getIntent();
+ method public android.os.Bundle getOptions();
+ method public android.app.PendingIntent? getPendingIntent();
+ method public int getRequestCode();
+ method public boolean isMutable();
+ }
+
+ public class TileServiceCompat {
+ method public static void startActivityAndCollapse(android.service.quicksettings.TileService, androidx.core.service.quicksettings.PendingIntentActivityWrapper);
+ }
+
+}
+
+package androidx.core.telephony {
+
+ @RequiresApi(22) public class SubscriptionManagerCompat {
+ method public static int getSlotIndex(int);
+ }
+
+ public class TelephonyManagerCompat {
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static String? getImei(android.telephony.TelephonyManager);
+ method public static int getSubscriptionId(android.telephony.TelephonyManager);
+ }
+
+}
+
+package androidx.core.telephony.mbms {
+
+ public final class MbmsHelper {
+ method public static CharSequence? getBestNameForService(android.content.Context, android.telephony.mbms.ServiceInfo);
+ }
+
+}
+
+package androidx.core.text {
+
+ public final class BidiFormatter {
+ method public static androidx.core.text.BidiFormatter! getInstance();
+ method public static androidx.core.text.BidiFormatter! getInstance(boolean);
+ method public static androidx.core.text.BidiFormatter! getInstance(java.util.Locale!);
+ method public boolean getStereoReset();
+ method public boolean isRtl(CharSequence!);
+ method public boolean isRtl(String!);
+ method public boolean isRtlContext();
+ method public CharSequence! unicodeWrap(CharSequence!);
+ method public CharSequence! unicodeWrap(CharSequence!, androidx.core.text.TextDirectionHeuristicCompat!);
+ method public CharSequence! unicodeWrap(CharSequence!, androidx.core.text.TextDirectionHeuristicCompat!, boolean);
+ method public CharSequence! unicodeWrap(CharSequence!, boolean);
+ method public String! unicodeWrap(String!);
+ method public String! unicodeWrap(String!, androidx.core.text.TextDirectionHeuristicCompat!);
+ method public String! unicodeWrap(String!, androidx.core.text.TextDirectionHeuristicCompat!, boolean);
+ method public String! unicodeWrap(String!, boolean);
+ }
+
+ public static final class BidiFormatter.Builder {
+ ctor public BidiFormatter.Builder();
+ ctor public BidiFormatter.Builder(boolean);
+ ctor public BidiFormatter.Builder(java.util.Locale!);
+ method public androidx.core.text.BidiFormatter! build();
+ method public androidx.core.text.BidiFormatter.Builder! setTextDirectionHeuristic(androidx.core.text.TextDirectionHeuristicCompat!);
+ method public androidx.core.text.BidiFormatter.Builder! stereoReset(boolean);
+ }
+
+ public final class HtmlCompat {
+ method public static android.text.Spanned fromHtml(String, int);
+ method public static android.text.Spanned fromHtml(String, int, android.text.Html.ImageGetter?, android.text.Html.TagHandler?);
+ method public static String toHtml(android.text.Spanned, int);
+ field public static final int FROM_HTML_MODE_COMPACT = 63; // 0x3f
+ field public static final int FROM_HTML_MODE_LEGACY = 0; // 0x0
+ field public static final int FROM_HTML_OPTION_USE_CSS_COLORS = 256; // 0x100
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE = 32; // 0x20
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_DIV = 16; // 0x10
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_HEADING = 2; // 0x2
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST = 8; // 0x8
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM = 4; // 0x4
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH = 1; // 0x1
+ field public static final int TO_HTML_PARAGRAPH_LINES_CONSECUTIVE = 0; // 0x0
+ field public static final int TO_HTML_PARAGRAPH_LINES_INDIVIDUAL = 1; // 0x1
+ }
+
+ public final class ICUCompat {
+ method public static String? maximizeAndGetScript(java.util.Locale);
+ }
+
+ public class PrecomputedTextCompat implements android.text.Spannable {
+ method public char charAt(int);
+ method public static androidx.core.text.PrecomputedTextCompat! create(CharSequence, androidx.core.text.PrecomputedTextCompat.Params);
+ method @IntRange(from=0) public int getParagraphCount();
+ method @IntRange(from=0) public int getParagraphEnd(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getParagraphStart(@IntRange(from=0) int);
+ method public androidx.core.text.PrecomputedTextCompat.Params getParams();
+ method public int getSpanEnd(Object!);
+ method public int getSpanFlags(Object!);
+ method public int getSpanStart(Object!);
+ method public <T> T![]! getSpans(int, int, Class<T!>!);
+ method @UiThread public static java.util.concurrent.Future<androidx.core.text.PrecomputedTextCompat!>! getTextFuture(CharSequence, androidx.core.text.PrecomputedTextCompat.Params, java.util.concurrent.Executor?);
+ method public int length();
+ method public int nextSpanTransition(int, int, Class!);
+ method public void removeSpan(Object!);
+ method public void setSpan(Object!, int, int, int);
+ method public CharSequence! subSequence(int, int);
+ }
+
+ public static final class PrecomputedTextCompat.Params {
+ ctor @RequiresApi(28) public PrecomputedTextCompat.Params(android.text.PrecomputedText.Params);
+ method @RequiresApi(23) public int getBreakStrategy();
+ method @RequiresApi(23) public int getHyphenationFrequency();
+ method public android.text.TextDirectionHeuristic? getTextDirection();
+ method public android.text.TextPaint getTextPaint();
+ }
+
+ public static class PrecomputedTextCompat.Params.Builder {
+ ctor public PrecomputedTextCompat.Params.Builder(android.text.TextPaint);
+ method public androidx.core.text.PrecomputedTextCompat.Params build();
+ method @RequiresApi(23) public androidx.core.text.PrecomputedTextCompat.Params.Builder! setBreakStrategy(int);
+ method @RequiresApi(23) public androidx.core.text.PrecomputedTextCompat.Params.Builder! setHyphenationFrequency(int);
+ method public androidx.core.text.PrecomputedTextCompat.Params.Builder! setTextDirection(android.text.TextDirectionHeuristic);
+ }
+
+ public interface TextDirectionHeuristicCompat {
+ method public boolean isRtl(char[]!, int, int);
+ method public boolean isRtl(CharSequence!, int, int);
+ }
+
+ public final class TextDirectionHeuristicsCompat {
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! ANYRTL_LTR;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! FIRSTSTRONG_LTR;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! FIRSTSTRONG_RTL;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! LOCALE;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! LTR;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! RTL;
+ }
+
+ public final class TextUtilsCompat {
+ method public static int getLayoutDirectionFromLocale(java.util.Locale?);
+ method public static String htmlEncode(String);
+ }
+
+}
+
+package androidx.core.text.method {
+
+ public class LinkMovementMethodCompat extends android.text.method.LinkMovementMethod {
+ method public static androidx.core.text.method.LinkMovementMethodCompat getInstance();
+ }
+
+}
+
+package androidx.core.text.util {
+
+ public final class LinkifyCompat {
+ method public static boolean addLinks(android.text.Spannable, int);
+ method public static boolean addLinks(android.text.Spannable, java.util.regex.Pattern, String?);
+ method public static boolean addLinks(android.text.Spannable, java.util.regex.Pattern, String?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ method public static boolean addLinks(android.text.Spannable, java.util.regex.Pattern, String?, String![]?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ method public static boolean addLinks(android.widget.TextView, int);
+ method public static void addLinks(android.widget.TextView, java.util.regex.Pattern, String?);
+ method public static void addLinks(android.widget.TextView, java.util.regex.Pattern, String?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ method public static void addLinks(android.widget.TextView, java.util.regex.Pattern, String?, String![]?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public final class LocalePreferences {
+ method public static String getCalendarType();
+ method public static String getCalendarType(boolean);
+ method public static String getCalendarType(java.util.Locale);
+ method public static String getCalendarType(java.util.Locale, boolean);
+ method public static String getFirstDayOfWeek();
+ method public static String getFirstDayOfWeek(boolean);
+ method public static String getFirstDayOfWeek(java.util.Locale);
+ method public static String getFirstDayOfWeek(java.util.Locale, boolean);
+ method public static String getHourCycle();
+ method public static String getHourCycle(boolean);
+ method public static String getHourCycle(java.util.Locale);
+ method public static String getHourCycle(java.util.Locale, boolean);
+ method public static String getTemperatureUnit();
+ method public static String getTemperatureUnit(boolean);
+ method public static String getTemperatureUnit(java.util.Locale);
+ method public static String getTemperatureUnit(java.util.Locale, boolean);
+ }
+
+ public static class LocalePreferences.CalendarType {
+ field public static final String CHINESE = "chinese";
+ field public static final String DANGI = "dangi";
+ field public static final String DEFAULT = "";
+ field public static final String GREGORIAN = "gregorian";
+ field public static final String HEBREW = "hebrew";
+ field public static final String INDIAN = "indian";
+ field public static final String ISLAMIC = "islamic";
+ field public static final String ISLAMIC_CIVIL = "islamic-civil";
+ field public static final String ISLAMIC_RGSA = "islamic-rgsa";
+ field public static final String ISLAMIC_TBLA = "islamic-tbla";
+ field public static final String ISLAMIC_UMALQURA = "islamic-umalqura";
+ field public static final String PERSIAN = "persian";
+ }
+
+ public static class LocalePreferences.FirstDayOfWeek {
+ field public static final String DEFAULT = "";
+ field public static final String FRIDAY = "fri";
+ field public static final String MONDAY = "mon";
+ field public static final String SATURDAY = "sat";
+ field public static final String SUNDAY = "sun";
+ field public static final String THURSDAY = "thu";
+ field public static final String TUESDAY = "tue";
+ field public static final String WEDNESDAY = "wed";
+ }
+
+ public static class LocalePreferences.HourCycle {
+ field public static final String DEFAULT = "";
+ field public static final String H11 = "h11";
+ field public static final String H12 = "h12";
+ field public static final String H23 = "h23";
+ field public static final String H24 = "h24";
+ }
+
+ public static class LocalePreferences.TemperatureUnit {
+ field public static final String CELSIUS = "celsius";
+ field public static final String DEFAULT = "";
+ field public static final String FAHRENHEIT = "fahrenhe";
+ field public static final String KELVIN = "kelvin";
+ }
+
+}
+
+package androidx.core.util {
+
+ public class AtomicFile {
+ ctor public AtomicFile(java.io.File);
+ method public void delete();
+ method public void failWrite(java.io.FileOutputStream?);
+ method public void finishWrite(java.io.FileOutputStream?);
+ method public java.io.File getBaseFile();
+ method public java.io.FileInputStream openRead() throws java.io.FileNotFoundException;
+ method public byte[] readFully() throws java.io.IOException;
+ method public java.io.FileOutputStream startWrite() throws java.io.IOException;
+ }
+
+ public fun interface Consumer<T> {
+ method public void accept(T value);
+ }
+
+ public fun interface Function<T, R> {
+ method public R apply(T value);
+ }
+
+ public class ObjectsCompat {
+ method public static boolean equals(Object?, Object?);
+ method public static int hash(java.lang.Object!...?);
+ method public static int hashCode(Object?);
+ method public static <T> T requireNonNull(T?);
+ method public static <T> T requireNonNull(T?, String);
+ method public static String? toString(Object?, String?);
+ }
+
+ public class Pair<F, S> {
+ ctor public Pair(F!, S!);
+ method public static <A, B> androidx.core.util.Pair<A!,B!> create(A!, B!);
+ field public final F! first;
+ field public final S! second;
+ }
+
+ public final class PatternsCompat {
+ field public static final java.util.regex.Pattern DOMAIN_NAME;
+ field public static final java.util.regex.Pattern EMAIL_ADDRESS;
+ field public static final java.util.regex.Pattern IP_ADDRESS;
+ field public static final java.util.regex.Pattern WEB_URL;
+ }
+
+ public final class Pools {
+ }
+
+ public static interface Pools.Pool<T> {
+ method public T? acquire();
+ method public boolean release(T instance);
+ }
+
+ public static class Pools.SimplePool<T> implements androidx.core.util.Pools.Pool<T> {
+ ctor public Pools.SimplePool(@IntRange(from=1L) int maxPoolSize);
+ method public T? acquire();
+ method public boolean release(T instance);
+ }
+
+ public static class Pools.SynchronizedPool<T> extends androidx.core.util.Pools.SimplePool<T> {
+ ctor public Pools.SynchronizedPool(int maxPoolSize);
+ }
+
+ public interface Predicate<T> {
+ method public default androidx.core.util.Predicate<T!>! and(androidx.core.util.Predicate<? super T!>!);
+ method public static <T> androidx.core.util.Predicate<T!>! isEqual(Object!);
+ method public default androidx.core.util.Predicate<T!>! negate();
+ method public static <T> androidx.core.util.Predicate<T!>! not(androidx.core.util.Predicate<? super T!>!);
+ method public default androidx.core.util.Predicate<T!>! or(androidx.core.util.Predicate<? super T!>!);
+ method public boolean test(T!);
+ }
+
+ public final class SizeFCompat {
+ ctor public SizeFCompat(float, float);
+ method public float getHeight();
+ method public float getWidth();
+ method @RequiresApi(21) public android.util.SizeF toSizeF();
+ method @RequiresApi(21) public static androidx.core.util.SizeFCompat toSizeFCompat(android.util.SizeF);
+ }
+
+ public fun interface Supplier<T> {
+ method public T get();
+ }
+
+ public class TypedValueCompat {
+ method public static float deriveDimension(int, float, android.util.DisplayMetrics);
+ method public static float dpToPx(float, android.util.DisplayMetrics);
+ method public static int getUnitFromComplexDimension(int);
+ method public static float pxToDp(float, android.util.DisplayMetrics);
+ method public static float pxToSp(float, android.util.DisplayMetrics);
+ method public static float spToPx(float, android.util.DisplayMetrics);
+ }
+
+}
+
+package androidx.core.view {
+
+ public class AccessibilityDelegateCompat {
+ ctor public AccessibilityDelegateCompat();
+ method public boolean dispatchPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public androidx.core.view.accessibility.AccessibilityNodeProviderCompat? getAccessibilityNodeProvider(android.view.View);
+ method public void onInitializeAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityNodeInfo(android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat);
+ method public void onPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public boolean onRequestSendAccessibilityEvent(android.view.ViewGroup, android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public boolean performAccessibilityAction(android.view.View, int, android.os.Bundle?);
+ method public void sendAccessibilityEvent(android.view.View, int);
+ method public void sendAccessibilityEventUnchecked(android.view.View, android.view.accessibility.AccessibilityEvent);
+ }
+
+ public abstract class ActionProvider {
+ ctor public ActionProvider(android.content.Context);
+ method public android.content.Context getContext();
+ method public boolean hasSubMenu();
+ method public boolean isVisible();
+ method public abstract android.view.View onCreateActionView();
+ method public android.view.View onCreateActionView(android.view.MenuItem);
+ method public boolean onPerformDefaultAction();
+ method public void onPrepareSubMenu(android.view.SubMenu);
+ method public boolean overridesItemVisibility();
+ method public void refreshVisibility();
+ method public void setVisibilityListener(androidx.core.view.ActionProvider.VisibilityListener?);
+ }
+
+ public static interface ActionProvider.VisibilityListener {
+ method public void onActionProviderVisibilityChanged(boolean);
+ }
+
+ public final class ContentInfoCompat {
+ method public android.content.ClipData getClip();
+ method public android.os.Bundle? getExtras();
+ method public int getFlags();
+ method public android.net.Uri? getLinkUri();
+ method public int getSource();
+ method @RequiresApi(31) public static android.util.Pair<android.view.ContentInfo!,android.view.ContentInfo!> partition(android.view.ContentInfo, java.util.function.Predicate<android.content.ClipData.Item!>);
+ method public android.util.Pair<androidx.core.view.ContentInfoCompat!,androidx.core.view.ContentInfoCompat!> partition(androidx.core.util.Predicate<android.content.ClipData.Item!>);
+ method @RequiresApi(31) public android.view.ContentInfo toContentInfo();
+ method @RequiresApi(31) public static androidx.core.view.ContentInfoCompat toContentInfoCompat(android.view.ContentInfo);
+ field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
+ field public static final int SOURCE_APP = 0; // 0x0
+ field public static final int SOURCE_AUTOFILL = 4; // 0x4
+ field public static final int SOURCE_CLIPBOARD = 1; // 0x1
+ field public static final int SOURCE_DRAG_AND_DROP = 3; // 0x3
+ field public static final int SOURCE_INPUT_METHOD = 2; // 0x2
+ field public static final int SOURCE_PROCESS_TEXT = 5; // 0x5
+ }
+
+ public static final class ContentInfoCompat.Builder {
+ ctor public ContentInfoCompat.Builder(android.content.ClipData, int);
+ ctor public ContentInfoCompat.Builder(androidx.core.view.ContentInfoCompat);
+ method public androidx.core.view.ContentInfoCompat build();
+ method public androidx.core.view.ContentInfoCompat.Builder setClip(android.content.ClipData);
+ method public androidx.core.view.ContentInfoCompat.Builder setExtras(android.os.Bundle?);
+ method public androidx.core.view.ContentInfoCompat.Builder setFlags(int);
+ method public androidx.core.view.ContentInfoCompat.Builder setLinkUri(android.net.Uri?);
+ method public androidx.core.view.ContentInfoCompat.Builder setSource(int);
+ }
+
+ public class DifferentialMotionFlingController {
+ ctor public DifferentialMotionFlingController(android.content.Context, androidx.core.view.DifferentialMotionFlingTarget);
+ method public void onMotionEvent(android.view.MotionEvent, int);
+ }
+
+ public interface DifferentialMotionFlingTarget {
+ method public float getScaledScrollFactor();
+ method public boolean startDifferentialMotionFling(float);
+ method public void stopDifferentialMotionFling();
+ }
+
+ public final class DisplayCompat {
+ method public static androidx.core.view.DisplayCompat.ModeCompat getMode(android.content.Context, android.view.Display);
+ method public static androidx.core.view.DisplayCompat.ModeCompat![] getSupportedModes(android.content.Context, android.view.Display);
+ }
+
+ public static final class DisplayCompat.ModeCompat {
+ method public int getPhysicalHeight();
+ method public int getPhysicalWidth();
+ method @Deprecated public boolean isNative();
+ method @RequiresApi(android.os.Build.VERSION_CODES.M) public android.view.Display.Mode? toMode();
+ }
+
+ public final class DisplayCutoutCompat {
+ ctor public DisplayCutoutCompat(android.graphics.Rect?, java.util.List<android.graphics.Rect!>?);
+ ctor public DisplayCutoutCompat(androidx.core.graphics.Insets, android.graphics.Rect?, android.graphics.Rect?, android.graphics.Rect?, android.graphics.Rect?, androidx.core.graphics.Insets);
+ method public java.util.List<android.graphics.Rect!> getBoundingRects();
+ method public int getSafeInsetBottom();
+ method public int getSafeInsetLeft();
+ method public int getSafeInsetRight();
+ method public int getSafeInsetTop();
+ method public androidx.core.graphics.Insets getWaterfallInsets();
+ }
+
+ public final class DragAndDropPermissionsCompat {
+ method public void release();
+ }
+
+ public class DragStartHelper {
+ ctor public DragStartHelper(android.view.View, androidx.core.view.DragStartHelper.OnDragStartListener);
+ method public void attach();
+ method public void detach();
+ method public void getTouchPosition(android.graphics.Point);
+ method public boolean onLongClick(android.view.View);
+ method public boolean onTouch(android.view.View, android.view.MotionEvent);
+ }
+
+ public static interface DragStartHelper.OnDragStartListener {
+ method public boolean onDragStart(android.view.View, androidx.core.view.DragStartHelper);
+ }
+
+ public final class GestureDetectorCompat {
+ ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener);
+ ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener, android.os.Handler?);
+ method public boolean isLongpressEnabled();
+ method public boolean onTouchEvent(android.view.MotionEvent);
+ method public void setIsLongpressEnabled(boolean);
+ method public void setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener?);
+ }
+
+ public final class GravityCompat {
+ method public static void apply(int, int, int, android.graphics.Rect, android.graphics.Rect, int);
+ method public static void apply(int, int, int, android.graphics.Rect, int, int, android.graphics.Rect, int);
+ method public static void applyDisplay(int, android.graphics.Rect, android.graphics.Rect, int);
+ method public static int getAbsoluteGravity(int, int);
+ field public static final int END = 8388613; // 0x800005
+ field public static final int RELATIVE_HORIZONTAL_GRAVITY_MASK = 8388615; // 0x800007
+ field public static final int RELATIVE_LAYOUT_DIRECTION = 8388608; // 0x800000
+ field public static final int START = 8388611; // 0x800003
+ }
+
+ public final class HapticFeedbackConstantsCompat {
+ field public static final int CLOCK_TICK = 4; // 0x4
+ field public static final int CONFIRM = 16; // 0x10
+ field public static final int CONTEXT_CLICK = 6; // 0x6
+ field public static final int DRAG_START = 25; // 0x19
+ field public static final int FLAG_IGNORE_VIEW_SETTING = 1; // 0x1
+ field public static final int GESTURE_END = 13; // 0xd
+ field public static final int GESTURE_START = 12; // 0xc
+ field public static final int GESTURE_THRESHOLD_ACTIVATE = 23; // 0x17
+ field public static final int GESTURE_THRESHOLD_DEACTIVATE = 24; // 0x18
+ field public static final int KEYBOARD_PRESS = 3; // 0x3
+ field public static final int KEYBOARD_RELEASE = 7; // 0x7
+ field public static final int KEYBOARD_TAP = 3; // 0x3
+ field public static final int LONG_PRESS = 0; // 0x0
+ field public static final int NO_HAPTICS = -1; // 0xffffffff
+ field public static final int REJECT = 17; // 0x11
+ field public static final int SEGMENT_FREQUENT_TICK = 27; // 0x1b
+ field public static final int SEGMENT_TICK = 26; // 0x1a
+ field public static final int TEXT_HANDLE_MOVE = 9; // 0x9
+ field public static final int TOGGLE_OFF = 22; // 0x16
+ field public static final int TOGGLE_ON = 21; // 0x15
+ field public static final int VIRTUAL_KEY = 1; // 0x1
+ field public static final int VIRTUAL_KEY_RELEASE = 8; // 0x8
+ }
+
+ public final class InputDeviceCompat {
+ field public static final int SOURCE_ANY = -256; // 0xffffff00
+ field public static final int SOURCE_CLASS_BUTTON = 1; // 0x1
+ field public static final int SOURCE_CLASS_JOYSTICK = 16; // 0x10
+ field public static final int SOURCE_CLASS_MASK = 255; // 0xff
+ field public static final int SOURCE_CLASS_NONE = 0; // 0x0
+ field public static final int SOURCE_CLASS_POINTER = 2; // 0x2
+ field public static final int SOURCE_CLASS_POSITION = 8; // 0x8
+ field public static final int SOURCE_CLASS_TRACKBALL = 4; // 0x4
+ field public static final int SOURCE_DPAD = 513; // 0x201
+ field public static final int SOURCE_GAMEPAD = 1025; // 0x401
+ field public static final int SOURCE_HDMI = 33554433; // 0x2000001
+ field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010
+ field public static final int SOURCE_KEYBOARD = 257; // 0x101
+ field public static final int SOURCE_MOUSE = 8194; // 0x2002
+ field public static final int SOURCE_ROTARY_ENCODER = 4194304; // 0x400000
+ field public static final int SOURCE_STYLUS = 16386; // 0x4002
+ field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
+ field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
+ field public static final int SOURCE_TOUCH_NAVIGATION = 2097152; // 0x200000
+ field public static final int SOURCE_TRACKBALL = 65540; // 0x10004
+ field public static final int SOURCE_UNKNOWN = 0; // 0x0
+ }
+
+ public final class LayoutInflaterCompat {
+ method @Deprecated public static androidx.core.view.LayoutInflaterFactory! getFactory(android.view.LayoutInflater!);
+ method @Deprecated public static void setFactory(android.view.LayoutInflater, androidx.core.view.LayoutInflaterFactory);
+ method public static void setFactory2(android.view.LayoutInflater, android.view.LayoutInflater.Factory2);
+ }
+
+ @Deprecated public interface LayoutInflaterFactory {
+ method @Deprecated public android.view.View! onCreateView(android.view.View!, String!, android.content.Context!, android.util.AttributeSet!);
+ }
+
+ public final class MarginLayoutParamsCompat {
+ method public static int getLayoutDirection(android.view.ViewGroup.MarginLayoutParams);
+ method public static int getMarginEnd(android.view.ViewGroup.MarginLayoutParams);
+ method public static int getMarginStart(android.view.ViewGroup.MarginLayoutParams);
+ method public static boolean isMarginRelative(android.view.ViewGroup.MarginLayoutParams);
+ method public static void resolveLayoutDirection(android.view.ViewGroup.MarginLayoutParams, int);
+ method public static void setLayoutDirection(android.view.ViewGroup.MarginLayoutParams, int);
+ method public static void setMarginEnd(android.view.ViewGroup.MarginLayoutParams, int);
+ method public static void setMarginStart(android.view.ViewGroup.MarginLayoutParams, int);
+ }
+
+ public final class MenuCompat {
+ method public static void setGroupDividerEnabled(android.view.Menu, boolean);
+ method @Deprecated public static void setShowAsAction(android.view.MenuItem!, int);
+ }
+
+ public interface MenuHost {
+ method public void addMenuProvider(androidx.core.view.MenuProvider);
+ method public void addMenuProvider(androidx.core.view.MenuProvider, androidx.lifecycle.LifecycleOwner);
+ method public void addMenuProvider(androidx.core.view.MenuProvider, androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State);
+ method public void invalidateMenu();
+ method public void removeMenuProvider(androidx.core.view.MenuProvider);
+ }
+
+ public class MenuHostHelper {
+ ctor public MenuHostHelper(Runnable);
+ method public void addMenuProvider(androidx.core.view.MenuProvider);
+ method public void addMenuProvider(androidx.core.view.MenuProvider, androidx.lifecycle.LifecycleOwner);
+ method public void addMenuProvider(androidx.core.view.MenuProvider, androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State);
+ method public void onCreateMenu(android.view.Menu, android.view.MenuInflater);
+ method public void onMenuClosed(android.view.Menu);
+ method public boolean onMenuItemSelected(android.view.MenuItem);
+ method public void onPrepareMenu(android.view.Menu);
+ method public void removeMenuProvider(androidx.core.view.MenuProvider);
+ }
+
+ public final class MenuItemCompat {
+ method @Deprecated public static boolean collapseActionView(android.view.MenuItem!);
+ method @Deprecated public static boolean expandActionView(android.view.MenuItem!);
+ method public static androidx.core.view.ActionProvider? getActionProvider(android.view.MenuItem);
+ method @Deprecated public static android.view.View! getActionView(android.view.MenuItem!);
+ method public static int getAlphabeticModifiers(android.view.MenuItem);
+ method public static CharSequence? getContentDescription(android.view.MenuItem);
+ method public static android.content.res.ColorStateList? getIconTintList(android.view.MenuItem);
+ method public static android.graphics.PorterDuff.Mode? getIconTintMode(android.view.MenuItem);
+ method public static int getNumericModifiers(android.view.MenuItem);
+ method public static CharSequence? getTooltipText(android.view.MenuItem);
+ method @Deprecated public static boolean isActionViewExpanded(android.view.MenuItem!);
+ method public static android.view.MenuItem? setActionProvider(android.view.MenuItem, androidx.core.view.ActionProvider?);
+ method @Deprecated public static android.view.MenuItem! setActionView(android.view.MenuItem!, android.view.View!);
+ method @Deprecated public static android.view.MenuItem! setActionView(android.view.MenuItem!, int);
+ method public static void setAlphabeticShortcut(android.view.MenuItem, char, int);
+ method public static void setContentDescription(android.view.MenuItem, CharSequence?);
+ method public static void setIconTintList(android.view.MenuItem, android.content.res.ColorStateList?);
+ method public static void setIconTintMode(android.view.MenuItem, android.graphics.PorterDuff.Mode?);
+ method public static void setNumericShortcut(android.view.MenuItem, char, int);
+ method @Deprecated public static android.view.MenuItem! setOnActionExpandListener(android.view.MenuItem!, androidx.core.view.MenuItemCompat.OnActionExpandListener!);
+ method public static void setShortcut(android.view.MenuItem, char, char, int, int);
+ method @Deprecated public static void setShowAsAction(android.view.MenuItem!, int);
+ method public static void setTooltipText(android.view.MenuItem, CharSequence?);
+ field @Deprecated public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
+ field @Deprecated public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
+ field @Deprecated public static final int SHOW_AS_ACTION_IF_ROOM = 1; // 0x1
+ field @Deprecated public static final int SHOW_AS_ACTION_NEVER = 0; // 0x0
+ field @Deprecated public static final int SHOW_AS_ACTION_WITH_TEXT = 4; // 0x4
+ }
+
+ @Deprecated public static interface MenuItemCompat.OnActionExpandListener {
+ method @Deprecated public boolean onMenuItemActionCollapse(android.view.MenuItem!);
+ method @Deprecated public boolean onMenuItemActionExpand(android.view.MenuItem!);
+ }
+
+ public interface MenuProvider {
+ method public void onCreateMenu(android.view.Menu, android.view.MenuInflater);
+ method public default void onMenuClosed(android.view.Menu);
+ method public boolean onMenuItemSelected(android.view.MenuItem);
+ method public default void onPrepareMenu(android.view.Menu);
+ }
+
+ public final class MotionEventCompat {
+ method @Deprecated public static int findPointerIndex(android.view.MotionEvent!, int);
+ method @Deprecated public static int getActionIndex(android.view.MotionEvent!);
+ method @Deprecated public static int getActionMasked(android.view.MotionEvent!);
+ method @Deprecated public static float getAxisValue(android.view.MotionEvent!, int);
+ method @Deprecated public static float getAxisValue(android.view.MotionEvent!, int, int);
+ method @Deprecated public static int getButtonState(android.view.MotionEvent!);
+ method @Deprecated public static int getPointerCount(android.view.MotionEvent!);
+ method @Deprecated public static int getPointerId(android.view.MotionEvent!, int);
+ method @Deprecated public static int getSource(android.view.MotionEvent!);
+ method @Deprecated public static float getX(android.view.MotionEvent!, int);
+ method @Deprecated public static float getY(android.view.MotionEvent!, int);
+ method public static boolean isFromSource(android.view.MotionEvent, int);
+ field @Deprecated public static final int ACTION_HOVER_ENTER = 9; // 0x9
+ field @Deprecated public static final int ACTION_HOVER_EXIT = 10; // 0xa
+ field @Deprecated public static final int ACTION_HOVER_MOVE = 7; // 0x7
+ field @Deprecated public static final int ACTION_MASK = 255; // 0xff
+ field @Deprecated public static final int ACTION_POINTER_DOWN = 5; // 0x5
+ field @Deprecated public static final int ACTION_POINTER_INDEX_MASK = 65280; // 0xff00
+ field @Deprecated public static final int ACTION_POINTER_INDEX_SHIFT = 8; // 0x8
+ field @Deprecated public static final int ACTION_POINTER_UP = 6; // 0x6
+ field @Deprecated public static final int ACTION_SCROLL = 8; // 0x8
+ field @Deprecated public static final int AXIS_BRAKE = 23; // 0x17
+ field @Deprecated public static final int AXIS_DISTANCE = 24; // 0x18
+ field @Deprecated public static final int AXIS_GAS = 22; // 0x16
+ field @Deprecated public static final int AXIS_GENERIC_1 = 32; // 0x20
+ field @Deprecated public static final int AXIS_GENERIC_10 = 41; // 0x29
+ field @Deprecated public static final int AXIS_GENERIC_11 = 42; // 0x2a
+ field @Deprecated public static final int AXIS_GENERIC_12 = 43; // 0x2b
+ field @Deprecated public static final int AXIS_GENERIC_13 = 44; // 0x2c
+ field @Deprecated public static final int AXIS_GENERIC_14 = 45; // 0x2d
+ field @Deprecated public static final int AXIS_GENERIC_15 = 46; // 0x2e
+ field @Deprecated public static final int AXIS_GENERIC_16 = 47; // 0x2f
+ field @Deprecated public static final int AXIS_GENERIC_2 = 33; // 0x21
+ field @Deprecated public static final int AXIS_GENERIC_3 = 34; // 0x22
+ field @Deprecated public static final int AXIS_GENERIC_4 = 35; // 0x23
+ field @Deprecated public static final int AXIS_GENERIC_5 = 36; // 0x24
+ field @Deprecated public static final int AXIS_GENERIC_6 = 37; // 0x25
+ field @Deprecated public static final int AXIS_GENERIC_7 = 38; // 0x26
+ field @Deprecated public static final int AXIS_GENERIC_8 = 39; // 0x27
+ field @Deprecated public static final int AXIS_GENERIC_9 = 40; // 0x28
+ field @Deprecated public static final int AXIS_HAT_X = 15; // 0xf
+ field @Deprecated public static final int AXIS_HAT_Y = 16; // 0x10
+ field @Deprecated public static final int AXIS_HSCROLL = 10; // 0xa
+ field @Deprecated public static final int AXIS_LTRIGGER = 17; // 0x11
+ field @Deprecated public static final int AXIS_ORIENTATION = 8; // 0x8
+ field @Deprecated public static final int AXIS_PRESSURE = 2; // 0x2
+ field public static final int AXIS_RELATIVE_X = 27; // 0x1b
+ field public static final int AXIS_RELATIVE_Y = 28; // 0x1c
+ field @Deprecated public static final int AXIS_RTRIGGER = 18; // 0x12
+ field @Deprecated public static final int AXIS_RUDDER = 20; // 0x14
+ field @Deprecated public static final int AXIS_RX = 12; // 0xc
+ field @Deprecated public static final int AXIS_RY = 13; // 0xd
+ field @Deprecated public static final int AXIS_RZ = 14; // 0xe
+ field public static final int AXIS_SCROLL = 26; // 0x1a
+ field @Deprecated public static final int AXIS_SIZE = 3; // 0x3
+ field @Deprecated public static final int AXIS_THROTTLE = 19; // 0x13
+ field @Deprecated public static final int AXIS_TILT = 25; // 0x19
+ field @Deprecated public static final int AXIS_TOOL_MAJOR = 6; // 0x6
+ field @Deprecated public static final int AXIS_TOOL_MINOR = 7; // 0x7
+ field @Deprecated public static final int AXIS_TOUCH_MAJOR = 4; // 0x4
+ field @Deprecated public static final int AXIS_TOUCH_MINOR = 5; // 0x5
+ field @Deprecated public static final int AXIS_VSCROLL = 9; // 0x9
+ field @Deprecated public static final int AXIS_WHEEL = 21; // 0x15
+ field @Deprecated public static final int AXIS_X = 0; // 0x0
+ field @Deprecated public static final int AXIS_Y = 1; // 0x1
+ field @Deprecated public static final int AXIS_Z = 11; // 0xb
+ field @Deprecated public static final int BUTTON_PRIMARY = 1; // 0x1
+ }
+
+ public interface NestedScrollingChild {
+ method public boolean dispatchNestedFling(float, float, boolean);
+ method public boolean dispatchNestedPreFling(float, float);
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?);
+ method public boolean hasNestedScrollingParent();
+ method public boolean isNestedScrollingEnabled();
+ method public void setNestedScrollingEnabled(boolean);
+ method public boolean startNestedScroll(int);
+ method public void stopNestedScroll();
+ }
+
+ public interface NestedScrollingChild2 extends androidx.core.view.NestedScrollingChild {
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?, int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?, int);
+ method public boolean hasNestedScrollingParent(int);
+ method public boolean startNestedScroll(int, int);
+ method public void stopNestedScroll(int);
+ }
+
+ public interface NestedScrollingChild3 extends androidx.core.view.NestedScrollingChild2 {
+ method public void dispatchNestedScroll(int, int, int, int, int[]?, int, int[]);
+ }
+
+ public class NestedScrollingChildHelper {
+ ctor public NestedScrollingChildHelper(android.view.View);
+ method public boolean dispatchNestedFling(float, float, boolean);
+ method public boolean dispatchNestedPreFling(float, float);
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?);
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?, int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?, int);
+ method public void dispatchNestedScroll(int, int, int, int, int[]?, int, int[]?);
+ method public boolean hasNestedScrollingParent();
+ method public boolean hasNestedScrollingParent(int);
+ method public boolean isNestedScrollingEnabled();
+ method public void onDetachedFromWindow();
+ method public void onStopNestedScroll(android.view.View);
+ method public void setNestedScrollingEnabled(boolean);
+ method public boolean startNestedScroll(int);
+ method public boolean startNestedScroll(int, int);
+ method public void stopNestedScroll();
+ method public void stopNestedScroll(int);
+ }
+
+ public interface NestedScrollingParent {
+ method public int getNestedScrollAxes();
+ method public boolean onNestedFling(android.view.View, float, float, boolean);
+ method public boolean onNestedPreFling(android.view.View, float, float);
+ method public void onNestedPreScroll(android.view.View, int, int, int[]);
+ method public void onNestedScroll(android.view.View, int, int, int, int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int);
+ method public void onStopNestedScroll(android.view.View);
+ }
+
+ public interface NestedScrollingParent2 extends androidx.core.view.NestedScrollingParent {
+ method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View, int);
+ }
+
+ public interface NestedScrollingParent3 extends androidx.core.view.NestedScrollingParent2 {
+ method public void onNestedScroll(android.view.View, int, int, int, int, int, int[]);
+ }
+
+ public class NestedScrollingParentHelper {
+ ctor public NestedScrollingParentHelper(android.view.ViewGroup);
+ method public int getNestedScrollAxes();
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View);
+ method public void onStopNestedScroll(android.view.View, int);
+ }
+
+ public interface OnApplyWindowInsetsListener {
+ method public androidx.core.view.WindowInsetsCompat onApplyWindowInsets(android.view.View, androidx.core.view.WindowInsetsCompat);
+ }
+
+ public interface OnReceiveContentListener {
+ method public androidx.core.view.ContentInfoCompat? onReceiveContent(android.view.View, androidx.core.view.ContentInfoCompat);
+ }
+
+ public interface OnReceiveContentViewBehavior {
+ method public androidx.core.view.ContentInfoCompat? onReceiveContent(androidx.core.view.ContentInfoCompat);
+ }
+
+ public final class OneShotPreDrawListener implements android.view.View.OnAttachStateChangeListener android.view.ViewTreeObserver.OnPreDrawListener {
+ method public static androidx.core.view.OneShotPreDrawListener add(android.view.View, Runnable);
+ method public boolean onPreDraw();
+ method public void onViewAttachedToWindow(android.view.View);
+ method public void onViewDetachedFromWindow(android.view.View);
+ method public void removeListener();
+ }
+
+ public final class PointerIconCompat {
+ method public static androidx.core.view.PointerIconCompat create(android.graphics.Bitmap, float, float);
+ method public static androidx.core.view.PointerIconCompat getSystemIcon(android.content.Context, int);
+ method public static androidx.core.view.PointerIconCompat load(android.content.res.Resources, int);
+ field public static final int TYPE_ALIAS = 1010; // 0x3f2
+ field public static final int TYPE_ALL_SCROLL = 1013; // 0x3f5
+ field public static final int TYPE_ARROW = 1000; // 0x3e8
+ field public static final int TYPE_CELL = 1006; // 0x3ee
+ field public static final int TYPE_CONTEXT_MENU = 1001; // 0x3e9
+ field public static final int TYPE_COPY = 1011; // 0x3f3
+ field public static final int TYPE_CROSSHAIR = 1007; // 0x3ef
+ field public static final int TYPE_DEFAULT = 1000; // 0x3e8
+ field public static final int TYPE_GRAB = 1020; // 0x3fc
+ field public static final int TYPE_GRABBING = 1021; // 0x3fd
+ field public static final int TYPE_HAND = 1002; // 0x3ea
+ field public static final int TYPE_HELP = 1003; // 0x3eb
+ field public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = 1014; // 0x3f6
+ field public static final int TYPE_NO_DROP = 1012; // 0x3f4
+ field public static final int TYPE_NULL = 0; // 0x0
+ field public static final int TYPE_TEXT = 1008; // 0x3f0
+ field public static final int TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; // 0x3f9
+ field public static final int TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; // 0x3f8
+ field public static final int TYPE_VERTICAL_DOUBLE_ARROW = 1015; // 0x3f7
+ field public static final int TYPE_VERTICAL_TEXT = 1009; // 0x3f1
+ field public static final int TYPE_WAIT = 1004; // 0x3ec
+ field public static final int TYPE_ZOOM_IN = 1018; // 0x3fa
+ field public static final int TYPE_ZOOM_OUT = 1019; // 0x3fb
+ }
+
+ public final class ScaleGestureDetectorCompat {
+ method public static boolean isQuickScaleEnabled(android.view.ScaleGestureDetector);
+ method @Deprecated public static boolean isQuickScaleEnabled(Object!);
+ method public static void setQuickScaleEnabled(android.view.ScaleGestureDetector, boolean);
+ method @Deprecated public static void setQuickScaleEnabled(Object!, boolean);
+ }
+
+ public interface ScrollingView {
+ method public int computeHorizontalScrollExtent();
+ method public int computeHorizontalScrollOffset();
+ method public int computeHorizontalScrollRange();
+ method public int computeVerticalScrollExtent();
+ method public int computeVerticalScrollOffset();
+ method public int computeVerticalScrollRange();
+ }
+
+ public final class SoftwareKeyboardControllerCompat {
+ ctor public SoftwareKeyboardControllerCompat(android.view.View);
+ method public void hide();
+ method public void show();
+ }
+
+ public interface TintableBackgroundView {
+ method public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public final class VelocityTrackerCompat {
+ method public static void addMovement(android.view.VelocityTracker, android.view.MotionEvent);
+ method public static void clear(android.view.VelocityTracker);
+ method public static void computeCurrentVelocity(android.view.VelocityTracker, int);
+ method public static void computeCurrentVelocity(android.view.VelocityTracker, int, float);
+ method public static float getAxisVelocity(android.view.VelocityTracker, int);
+ method public static float getAxisVelocity(android.view.VelocityTracker, int, int);
+ method @Deprecated public static float getXVelocity(android.view.VelocityTracker!, int);
+ method @Deprecated public static float getYVelocity(android.view.VelocityTracker!, int);
+ method public static boolean isAxisSupported(android.view.VelocityTracker, int);
+ method public static void recycle(android.view.VelocityTracker);
+ }
+
+ public class ViewCompat {
+ ctor @Deprecated protected ViewCompat();
+ method public static int addAccessibilityAction(android.view.View, CharSequence, androidx.core.view.accessibility.AccessibilityViewCommand);
+ method public static void addKeyboardNavigationClusters(android.view.View, java.util.Collection<android.view.View!>, int);
+ method public static void addOnUnhandledKeyEventListener(android.view.View, androidx.core.view.ViewCompat.OnUnhandledKeyEventListenerCompat);
+ method @Deprecated public static androidx.core.view.ViewPropertyAnimatorCompat animate(android.view.View);
+ method @Deprecated public static boolean canScrollHorizontally(android.view.View!, int);
+ method @Deprecated public static boolean canScrollVertically(android.view.View!, int);
+ method public static void cancelDragAndDrop(android.view.View);
+ method @Deprecated public static int combineMeasuredStates(int, int);
+ method public static androidx.core.view.WindowInsetsCompat computeSystemWindowInsets(android.view.View, androidx.core.view.WindowInsetsCompat, android.graphics.Rect);
+ method public static androidx.core.view.WindowInsetsCompat dispatchApplyWindowInsets(android.view.View, androidx.core.view.WindowInsetsCompat);
+ method public static void dispatchFinishTemporaryDetach(android.view.View);
+ method public static boolean dispatchNestedFling(android.view.View, float, float, boolean);
+ method public static boolean dispatchNestedPreFling(android.view.View, float, float);
+ method public static boolean dispatchNestedPreScroll(android.view.View, int, int, int[]?, int[]?);
+ method public static boolean dispatchNestedPreScroll(android.view.View, int, int, int[]?, int[]?, int);
+ method public static boolean dispatchNestedScroll(android.view.View, int, int, int, int, int[]?);
+ method public static boolean dispatchNestedScroll(android.view.View, int, int, int, int, int[]?, int);
+ method public static void dispatchNestedScroll(android.view.View, int, int, int, int, int[]?, int, int[]);
+ method public static void dispatchStartTemporaryDetach(android.view.View);
+ method public static void enableAccessibleClickableSpanSupport(android.view.View);
+ method @Deprecated public static int generateViewId();
+ method public static androidx.core.view.AccessibilityDelegateCompat? getAccessibilityDelegate(android.view.View);
+ method @Deprecated public static int getAccessibilityLiveRegion(android.view.View);
+ method public static androidx.core.view.accessibility.AccessibilityNodeProviderCompat? getAccessibilityNodeProvider(android.view.View);
+ method @UiThread public static CharSequence? getAccessibilityPaneTitle(android.view.View);
+ method @Deprecated public static float getAlpha(android.view.View!);
+ method public static androidx.core.view.autofill.AutofillIdCompat? getAutofillId(android.view.View);
+ method public static android.content.res.ColorStateList? getBackgroundTintList(android.view.View);
+ method public static android.graphics.PorterDuff.Mode? getBackgroundTintMode(android.view.View);
+ method @Deprecated public static android.graphics.Rect? getClipBounds(android.view.View);
+ method public static androidx.core.view.contentcapture.ContentCaptureSessionCompat? getContentCaptureSession(android.view.View);
+ method @Deprecated public static android.view.Display? getDisplay(android.view.View);
+ method public static float getElevation(android.view.View);
+ method @Deprecated public static boolean getFitsSystemWindows(android.view.View);
+ method @Deprecated public static int getImportantForAccessibility(android.view.View);
+ method public static int getImportantForAutofill(android.view.View);
+ method public static int getImportantForContentCapture(android.view.View);
+ method @Deprecated public static int getLabelFor(android.view.View);
+ method @Deprecated public static int getLayerType(android.view.View!);
+ method @Deprecated public static int getLayoutDirection(android.view.View);
+ method @Deprecated public static android.graphics.Matrix? getMatrix(android.view.View!);
+ method @Deprecated public static int getMeasuredHeightAndState(android.view.View!);
+ method @Deprecated public static int getMeasuredState(android.view.View!);
+ method @Deprecated public static int getMeasuredWidthAndState(android.view.View!);
+ method @Deprecated public static int getMinimumHeight(android.view.View);
+ method @Deprecated public static int getMinimumWidth(android.view.View);
+ method public static int getNextClusterForwardId(android.view.View);
+ method public static String![]? getOnReceiveContentMimeTypes(android.view.View);
+ method @Deprecated public static int getOverScrollMode(android.view.View!);
+ method @Deprecated @Px public static int getPaddingEnd(android.view.View);
+ method @Deprecated @Px public static int getPaddingStart(android.view.View);
+ method @Deprecated public static android.view.ViewParent? getParentForAccessibility(android.view.View);
+ method @Deprecated public static float getPivotX(android.view.View!);
+ method @Deprecated public static float getPivotY(android.view.View!);
+ method public static androidx.core.view.WindowInsetsCompat? getRootWindowInsets(android.view.View);
+ method @Deprecated public static float getRotation(android.view.View!);
+ method @Deprecated public static float getRotationX(android.view.View!);
+ method @Deprecated public static float getRotationY(android.view.View!);
+ method @Deprecated public static float getScaleX(android.view.View!);
+ method @Deprecated public static float getScaleY(android.view.View!);
+ method public static int getScrollIndicators(android.view.View);
+ method @UiThread public static CharSequence? getStateDescription(android.view.View);
+ method public static java.util.List<android.graphics.Rect!> getSystemGestureExclusionRects(android.view.View);
+ method public static String? getTransitionName(android.view.View);
+ method @Deprecated public static float getTranslationX(android.view.View!);
+ method @Deprecated public static float getTranslationY(android.view.View!);
+ method public static float getTranslationZ(android.view.View);
+ method @Deprecated public static androidx.core.view.WindowInsetsControllerCompat? getWindowInsetsController(android.view.View);
+ method @Deprecated public static int getWindowSystemUiVisibility(android.view.View);
+ method @Deprecated public static float getX(android.view.View!);
+ method @Deprecated public static float getY(android.view.View!);
+ method public static float getZ(android.view.View);
+ method public static boolean hasAccessibilityDelegate(android.view.View);
+ method public static boolean hasExplicitFocusable(android.view.View);
+ method public static boolean hasNestedScrollingParent(android.view.View);
+ method public static boolean hasNestedScrollingParent(android.view.View, int);
+ method @Deprecated public static boolean hasOnClickListeners(android.view.View);
+ method @Deprecated public static boolean hasOverlappingRendering(android.view.View);
+ method @Deprecated public static boolean hasTransientState(android.view.View);
+ method @UiThread public static boolean isAccessibilityHeading(android.view.View);
+ method @Deprecated public static boolean isAttachedToWindow(android.view.View);
+ method public static boolean isFocusedByDefault(android.view.View);
+ method public static boolean isImportantForAccessibility(android.view.View);
+ method public static boolean isImportantForAutofill(android.view.View);
+ method public static boolean isImportantForContentCapture(android.view.View);
+ method @Deprecated public static boolean isInLayout(android.view.View);
+ method public static boolean isKeyboardNavigationCluster(android.view.View);
+ method @Deprecated public static boolean isLaidOut(android.view.View);
+ method @Deprecated public static boolean isLayoutDirectionResolved(android.view.View);
+ method public static boolean isNestedScrollingEnabled(android.view.View);
+ method @Deprecated public static boolean isOpaque(android.view.View!);
+ method @Deprecated public static boolean isPaddingRelative(android.view.View);
+ method @UiThread public static boolean isScreenReaderFocusable(android.view.View);
+ method @Deprecated public static void jumpDrawablesToCurrentState(android.view.View!);
+ method public static android.view.View? keyboardNavigationClusterSearch(android.view.View, android.view.View?, int);
+ method public static void offsetLeftAndRight(android.view.View, int);
+ method public static void offsetTopAndBottom(android.view.View, int);
+ method public static androidx.core.view.WindowInsetsCompat onApplyWindowInsets(android.view.View, androidx.core.view.WindowInsetsCompat);
+ method @Deprecated public static void onInitializeAccessibilityEvent(android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method @Deprecated public static void onInitializeAccessibilityNodeInfo(android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat);
+ method @Deprecated public static void onPopulateAccessibilityEvent(android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method @Deprecated public static boolean performAccessibilityAction(android.view.View, int, android.os.Bundle?);
+ method public static boolean performHapticFeedback(android.view.View, int);
+ method public static boolean performHapticFeedback(android.view.View, int, int);
+ method public static androidx.core.view.ContentInfoCompat? performReceiveContent(android.view.View, androidx.core.view.ContentInfoCompat);
+ method @Deprecated public static void postInvalidateOnAnimation(android.view.View);
+ method @Deprecated public static void postInvalidateOnAnimation(android.view.View, int, int, int, int);
+ method @Deprecated public static void postOnAnimation(android.view.View, Runnable);
+ method @Deprecated public static void postOnAnimationDelayed(android.view.View, Runnable, long);
+ method public static void removeAccessibilityAction(android.view.View, int);
+ method public static void removeOnUnhandledKeyEventListener(android.view.View, androidx.core.view.ViewCompat.OnUnhandledKeyEventListenerCompat);
+ method public static void replaceAccessibilityAction(android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat, CharSequence?, androidx.core.view.accessibility.AccessibilityViewCommand?);
+ method public static void requestApplyInsets(android.view.View);
+ method public static <T extends android.view.View> T requireViewById(android.view.View, @IdRes int);
+ method @Deprecated public static int resolveSizeAndState(int, int, int);
+ method public static boolean restoreDefaultFocus(android.view.View);
+ method public static void saveAttributeDataForStyleable(android.view.View, android.content.Context, int[], android.util.AttributeSet?, android.content.res.TypedArray, int, int);
+ method public static void setAccessibilityDelegate(android.view.View, androidx.core.view.AccessibilityDelegateCompat?);
+ method @UiThread public static void setAccessibilityHeading(android.view.View, boolean);
+ method @Deprecated public static void setAccessibilityLiveRegion(android.view.View, int);
+ method @UiThread public static void setAccessibilityPaneTitle(android.view.View, CharSequence?);
+ method @Deprecated public static void setActivated(android.view.View!, boolean);
+ method @Deprecated public static void setAlpha(android.view.View!, @FloatRange(from=0.0, to=1.0) float);
+ method public static void setAutofillHints(android.view.View, java.lang.String!...?);
+ method public static void setAutofillId(android.view.View, androidx.core.view.autofill.AutofillIdCompat?);
+ method @Deprecated public static void setBackground(android.view.View, android.graphics.drawable.Drawable?);
+ method public static void setBackgroundTintList(android.view.View, android.content.res.ColorStateList?);
+ method public static void setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode?);
+ method @Deprecated public static void setChildrenDrawingOrderEnabled(android.view.ViewGroup!, boolean);
+ method @Deprecated public static void setClipBounds(android.view.View, android.graphics.Rect?);
+ method public static void setContentCaptureSession(android.view.View, androidx.core.view.contentcapture.ContentCaptureSessionCompat?);
+ method public static void setElevation(android.view.View, float);
+ method @Deprecated public static void setFitsSystemWindows(android.view.View!, boolean);
+ method public static void setFocusedByDefault(android.view.View, boolean);
+ method @Deprecated public static void setHasTransientState(android.view.View, boolean);
+ method @Deprecated @UiThread public static void setImportantForAccessibility(android.view.View, int);
+ method public static void setImportantForAutofill(android.view.View, int);
+ method public static void setImportantForContentCapture(android.view.View, int);
+ method public static void setKeyboardNavigationCluster(android.view.View, boolean);
+ method @Deprecated public static void setLabelFor(android.view.View, @IdRes int);
+ method @Deprecated public static void setLayerPaint(android.view.View, android.graphics.Paint?);
+ method @Deprecated public static void setLayerType(android.view.View!, int, android.graphics.Paint!);
+ method @Deprecated public static void setLayoutDirection(android.view.View, int);
+ method public static void setNestedScrollingEnabled(android.view.View, boolean);
+ method public static void setNextClusterForwardId(android.view.View, int);
+ method public static void setOnApplyWindowInsetsListener(android.view.View, androidx.core.view.OnApplyWindowInsetsListener?);
+ method public static void setOnReceiveContentListener(android.view.View, String![]?, androidx.core.view.OnReceiveContentListener?);
+ method @Deprecated public static void setOverScrollMode(android.view.View!, int);
+ method @Deprecated public static void setPaddingRelative(android.view.View, @Px int, @Px int, @Px int, @Px int);
+ method @Deprecated public static void setPivotX(android.view.View!, float);
+ method @Deprecated public static void setPivotY(android.view.View!, float);
+ method public static void setPointerIcon(android.view.View, androidx.core.view.PointerIconCompat?);
+ method @Deprecated public static void setRotation(android.view.View!, float);
+ method @Deprecated public static void setRotationX(android.view.View!, float);
+ method @Deprecated public static void setRotationY(android.view.View!, float);
+ method @Deprecated public static void setSaveFromParentEnabled(android.view.View!, boolean);
+ method @Deprecated public static void setScaleX(android.view.View!, float);
+ method @Deprecated public static void setScaleY(android.view.View!, float);
+ method @UiThread public static void setScreenReaderFocusable(android.view.View, boolean);
+ method public static void setScrollIndicators(android.view.View, int);
+ method public static void setScrollIndicators(android.view.View, int, int);
+ method @UiThread public static void setStateDescription(android.view.View, CharSequence?);
+ method public static void setSystemGestureExclusionRects(android.view.View, java.util.List<android.graphics.Rect!>);
+ method public static void setTooltipText(android.view.View, CharSequence?);
+ method public static void setTransitionName(android.view.View, String?);
+ method @Deprecated public static void setTranslationX(android.view.View!, float);
+ method @Deprecated public static void setTranslationY(android.view.View!, float);
+ method public static void setTranslationZ(android.view.View, float);
+ method public static void setWindowInsetsAnimationCallback(android.view.View, androidx.core.view.WindowInsetsAnimationCompat.Callback?);
+ method @Deprecated public static void setX(android.view.View!, float);
+ method @Deprecated public static void setY(android.view.View!, float);
+ method public static void setZ(android.view.View, float);
+ method public static boolean startDragAndDrop(android.view.View, android.content.ClipData?, android.view.View.DragShadowBuilder, Object?, int);
+ method public static boolean startNestedScroll(android.view.View, int);
+ method public static boolean startNestedScroll(android.view.View, int, int);
+ method public static void stopNestedScroll(android.view.View);
+ method public static void stopNestedScroll(android.view.View, int);
+ method public static void updateDragShadow(android.view.View, android.view.View.DragShadowBuilder);
+ field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2
+ field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
+ field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
+ field @Deprecated public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0; // 0x0
+ field @Deprecated public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 2; // 0x2
+ field @Deprecated public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 4; // 0x4
+ field @Deprecated public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
+ field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_AUTO = 0; // 0x0
+ field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO = 2; // 0x2
+ field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS = 8; // 0x8
+ field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_YES = 1; // 0x1
+ field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS = 4; // 0x4
+ field @Deprecated public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
+ field @Deprecated public static final int LAYER_TYPE_NONE = 0; // 0x0
+ field @Deprecated public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
+ field @Deprecated public static final int LAYOUT_DIRECTION_INHERIT = 2; // 0x2
+ field @Deprecated public static final int LAYOUT_DIRECTION_LOCALE = 3; // 0x3
+ field @Deprecated public static final int LAYOUT_DIRECTION_LTR = 0; // 0x0
+ field @Deprecated public static final int LAYOUT_DIRECTION_RTL = 1; // 0x1
+ field @Deprecated public static final int MEASURED_HEIGHT_STATE_SHIFT = 16; // 0x10
+ field @Deprecated public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
+ field @Deprecated public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
+ field @Deprecated public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+ field @Deprecated public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
+ field @Deprecated public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
+ field @Deprecated public static final int OVER_SCROLL_NEVER = 2; // 0x2
+ field public static final int SCROLL_AXIS_HORIZONTAL = 1; // 0x1
+ field public static final int SCROLL_AXIS_NONE = 0; // 0x0
+ field public static final int SCROLL_AXIS_VERTICAL = 2; // 0x2
+ field public static final int SCROLL_INDICATOR_BOTTOM = 2; // 0x2
+ field public static final int SCROLL_INDICATOR_END = 32; // 0x20
+ field public static final int SCROLL_INDICATOR_LEFT = 4; // 0x4
+ field public static final int SCROLL_INDICATOR_RIGHT = 8; // 0x8
+ field public static final int SCROLL_INDICATOR_START = 16; // 0x10
+ field public static final int SCROLL_INDICATOR_TOP = 1; // 0x1
+ field public static final int TYPE_NON_TOUCH = 1; // 0x1
+ field public static final int TYPE_TOUCH = 0; // 0x0
+ }
+
+ public static interface ViewCompat.OnUnhandledKeyEventListenerCompat {
+ method public boolean onUnhandledKeyEvent(android.view.View, android.view.KeyEvent);
+ }
+
+ public final class ViewConfigurationCompat {
+ method public static float getScaledHorizontalScrollFactor(android.view.ViewConfiguration, android.content.Context);
+ method public static int getScaledHoverSlop(android.view.ViewConfiguration);
+ method public static int getScaledMaximumFlingVelocity(android.content.Context, android.view.ViewConfiguration, int, int, int);
+ method public static int getScaledMinimumFlingVelocity(android.content.Context, android.view.ViewConfiguration, int, int, int);
+ method @Deprecated public static int getScaledPagingTouchSlop(android.view.ViewConfiguration!);
+ method public static float getScaledVerticalScrollFactor(android.view.ViewConfiguration, android.content.Context);
+ method @Deprecated public static boolean hasPermanentMenuKey(android.view.ViewConfiguration!);
+ method public static boolean shouldShowMenuShortcutsWhenKeyboardPresent(android.view.ViewConfiguration, android.content.Context);
+ }
+
+ public final class ViewGroupCompat {
+ method public static int getLayoutMode(android.view.ViewGroup);
+ method public static int getNestedScrollAxes(android.view.ViewGroup);
+ method public static boolean isTransitionGroup(android.view.ViewGroup);
+ method @Deprecated public static boolean onRequestSendAccessibilityEvent(android.view.ViewGroup!, android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method public static void setLayoutMode(android.view.ViewGroup, int);
+ method @Deprecated public static void setMotionEventSplittingEnabled(android.view.ViewGroup!, boolean);
+ method public static void setTransitionGroup(android.view.ViewGroup, boolean);
+ field public static final int LAYOUT_MODE_CLIP_BOUNDS = 0; // 0x0
+ field public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1; // 0x1
+ }
+
+ public final class ViewParentCompat {
+ method public static void notifySubtreeAccessibilityStateChanged(android.view.ViewParent, android.view.View, android.view.View, int);
+ method public static boolean onNestedFling(android.view.ViewParent, android.view.View, float, float, boolean);
+ method public static boolean onNestedPreFling(android.view.ViewParent, android.view.View, float, float);
+ method public static void onNestedPreScroll(android.view.ViewParent, android.view.View, int, int, int[]);
+ method public static void onNestedPreScroll(android.view.ViewParent, android.view.View, int, int, int[], int);
+ method public static void onNestedScroll(android.view.ViewParent, android.view.View, int, int, int, int);
+ method public static void onNestedScroll(android.view.ViewParent, android.view.View, int, int, int, int, int);
+ method public static void onNestedScroll(android.view.ViewParent, android.view.View, int, int, int, int, int, int[]);
+ method public static void onNestedScrollAccepted(android.view.ViewParent, android.view.View, android.view.View, int);
+ method public static void onNestedScrollAccepted(android.view.ViewParent, android.view.View, android.view.View, int, int);
+ method public static boolean onStartNestedScroll(android.view.ViewParent, android.view.View, android.view.View, int);
+ method public static boolean onStartNestedScroll(android.view.ViewParent, android.view.View, android.view.View, int, int);
+ method public static void onStopNestedScroll(android.view.ViewParent, android.view.View);
+ method public static void onStopNestedScroll(android.view.ViewParent, android.view.View, int);
+ method @Deprecated public static boolean requestSendAccessibilityEvent(android.view.ViewParent!, android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ }
+
+ public final class ViewPropertyAnimatorCompat {
+ method public androidx.core.view.ViewPropertyAnimatorCompat alpha(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat alphaBy(float);
+ method public void cancel();
+ method public long getDuration();
+ method public android.view.animation.Interpolator? getInterpolator();
+ method public long getStartDelay();
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotation(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotationBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotationX(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotationXBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotationY(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotationYBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat scaleX(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat scaleXBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat scaleY(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat scaleYBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat setDuration(long);
+ method public androidx.core.view.ViewPropertyAnimatorCompat setInterpolator(android.view.animation.Interpolator?);
+ method public androidx.core.view.ViewPropertyAnimatorCompat setListener(androidx.core.view.ViewPropertyAnimatorListener?);
+ method public androidx.core.view.ViewPropertyAnimatorCompat setStartDelay(long);
+ method public androidx.core.view.ViewPropertyAnimatorCompat setUpdateListener(androidx.core.view.ViewPropertyAnimatorUpdateListener?);
+ method public void start();
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationX(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationXBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationY(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationYBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationZ(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationZBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat withEndAction(Runnable);
+ method public androidx.core.view.ViewPropertyAnimatorCompat withLayer();
+ method public androidx.core.view.ViewPropertyAnimatorCompat withStartAction(Runnable);
+ method public androidx.core.view.ViewPropertyAnimatorCompat x(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat xBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat y(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat yBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat z(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat zBy(float);
+ }
+
+ public interface ViewPropertyAnimatorListener {
+ method public void onAnimationCancel(android.view.View);
+ method public void onAnimationEnd(android.view.View);
+ method public void onAnimationStart(android.view.View);
+ }
+
+ public class ViewPropertyAnimatorListenerAdapter implements androidx.core.view.ViewPropertyAnimatorListener {
+ ctor public ViewPropertyAnimatorListenerAdapter();
+ method public void onAnimationCancel(android.view.View);
+ method public void onAnimationEnd(android.view.View);
+ method public void onAnimationStart(android.view.View);
+ }
+
+ public interface ViewPropertyAnimatorUpdateListener {
+ method public void onAnimationUpdate(android.view.View);
+ }
+
+ public class ViewStructureCompat {
+ method public void setClassName(String);
+ method public void setContentDescription(CharSequence);
+ method public void setDimens(int, int, int, int, int, int);
+ method public void setText(CharSequence);
+ method @RequiresApi(23) public android.view.ViewStructure toViewStructure();
+ method @RequiresApi(23) public static androidx.core.view.ViewStructureCompat toViewStructureCompat(android.view.ViewStructure);
+ }
+
+ public final class WindowCompat {
+ method public static androidx.core.view.WindowInsetsControllerCompat getInsetsController(android.view.Window, android.view.View);
+ method public static <T extends android.view.View> T requireViewById(android.view.Window, @IdRes int);
+ method public static void setDecorFitsSystemWindows(android.view.Window, boolean);
+ field public static final int FEATURE_ACTION_BAR = 8; // 0x8
+ field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9
+ field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
+ }
+
+ public final class WindowInsetsAnimationCompat {
+ ctor public WindowInsetsAnimationCompat(int, android.view.animation.Interpolator?, long);
+ method @FloatRange(from=0.0f, to=1.0f) public float getAlpha();
+ method public long getDurationMillis();
+ method @FloatRange(from=0.0f, to=1.0f) public float getFraction();
+ method public float getInterpolatedFraction();
+ method public android.view.animation.Interpolator? getInterpolator();
+ method public int getTypeMask();
+ method public void setAlpha(@FloatRange(from=0.0f, to=1.0f) float);
+ method public void setFraction(@FloatRange(from=0.0f, to=1.0f) float);
+ }
+
+ public static final class WindowInsetsAnimationCompat.BoundsCompat {
+ ctor public WindowInsetsAnimationCompat.BoundsCompat(androidx.core.graphics.Insets, androidx.core.graphics.Insets);
+ method public androidx.core.graphics.Insets getLowerBound();
+ method public androidx.core.graphics.Insets getUpperBound();
+ method public androidx.core.view.WindowInsetsAnimationCompat.BoundsCompat inset(androidx.core.graphics.Insets);
+ method @RequiresApi(30) public android.view.WindowInsetsAnimation.Bounds toBounds();
+ method @RequiresApi(30) public static androidx.core.view.WindowInsetsAnimationCompat.BoundsCompat toBoundsCompat(android.view.WindowInsetsAnimation.Bounds);
+ }
+
+ public abstract static class WindowInsetsAnimationCompat.Callback {
+ ctor public WindowInsetsAnimationCompat.Callback(int);
+ method public final int getDispatchMode();
+ method public void onEnd(androidx.core.view.WindowInsetsAnimationCompat);
+ method public void onPrepare(androidx.core.view.WindowInsetsAnimationCompat);
+ method public abstract androidx.core.view.WindowInsetsCompat onProgress(androidx.core.view.WindowInsetsCompat, java.util.List<androidx.core.view.WindowInsetsAnimationCompat!>);
+ method public androidx.core.view.WindowInsetsAnimationCompat.BoundsCompat onStart(androidx.core.view.WindowInsetsAnimationCompat, androidx.core.view.WindowInsetsAnimationCompat.BoundsCompat);
+ field public static final int DISPATCH_MODE_CONTINUE_ON_SUBTREE = 1; // 0x1
+ field public static final int DISPATCH_MODE_STOP = 0; // 0x0
+ }
+
+ public interface WindowInsetsAnimationControlListenerCompat {
+ method public void onCancelled(androidx.core.view.WindowInsetsAnimationControllerCompat?);
+ method public void onFinished(androidx.core.view.WindowInsetsAnimationControllerCompat);
+ method public void onReady(androidx.core.view.WindowInsetsAnimationControllerCompat, int);
+ }
+
+ public final class WindowInsetsAnimationControllerCompat {
+ method public void finish(boolean);
+ method public float getCurrentAlpha();
+ method @FloatRange(from=0.0f, to=1.0f) public float getCurrentFraction();
+ method public androidx.core.graphics.Insets getCurrentInsets();
+ method public androidx.core.graphics.Insets getHiddenStateInsets();
+ method public androidx.core.graphics.Insets getShownStateInsets();
+ method public int getTypes();
+ method public boolean isCancelled();
+ method public boolean isFinished();
+ method public boolean isReady();
+ method public void setInsetsAndAlpha(androidx.core.graphics.Insets?, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+ }
+
+ public class WindowInsetsCompat {
+ ctor public WindowInsetsCompat(androidx.core.view.WindowInsetsCompat?);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat consumeDisplayCutout();
+ method @Deprecated public androidx.core.view.WindowInsetsCompat consumeStableInsets();
+ method @Deprecated public androidx.core.view.WindowInsetsCompat consumeSystemWindowInsets();
+ method public androidx.core.view.DisplayCutoutCompat? getDisplayCutout();
+ method public androidx.core.graphics.Insets getInsets(int);
+ method public androidx.core.graphics.Insets getInsetsIgnoringVisibility(int);
+ method @Deprecated public androidx.core.graphics.Insets getMandatorySystemGestureInsets();
+ method @Deprecated public int getStableInsetBottom();
+ method @Deprecated public int getStableInsetLeft();
+ method @Deprecated public int getStableInsetRight();
+ method @Deprecated public int getStableInsetTop();
+ method @Deprecated public androidx.core.graphics.Insets getStableInsets();
+ method @Deprecated public androidx.core.graphics.Insets getSystemGestureInsets();
+ method @Deprecated public int getSystemWindowInsetBottom();
+ method @Deprecated public int getSystemWindowInsetLeft();
+ method @Deprecated public int getSystemWindowInsetRight();
+ method @Deprecated public int getSystemWindowInsetTop();
+ method @Deprecated public androidx.core.graphics.Insets getSystemWindowInsets();
+ method @Deprecated public androidx.core.graphics.Insets getTappableElementInsets();
+ method public boolean hasInsets();
+ method @Deprecated public boolean hasStableInsets();
+ method @Deprecated public boolean hasSystemWindowInsets();
+ method public androidx.core.view.WindowInsetsCompat inset(androidx.core.graphics.Insets);
+ method public androidx.core.view.WindowInsetsCompat inset(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int);
+ method public boolean isConsumed();
+ method public boolean isRound();
+ method public boolean isVisible(int);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat replaceSystemWindowInsets(android.graphics.Rect);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat replaceSystemWindowInsets(int, int, int, int);
+ method @RequiresApi(20) public android.view.WindowInsets? toWindowInsets();
+ method @RequiresApi(20) public static androidx.core.view.WindowInsetsCompat toWindowInsetsCompat(android.view.WindowInsets);
+ method @RequiresApi(20) public static androidx.core.view.WindowInsetsCompat toWindowInsetsCompat(android.view.WindowInsets, android.view.View?);
+ field public static final androidx.core.view.WindowInsetsCompat CONSUMED;
+ }
+
+ public static final class WindowInsetsCompat.Builder {
+ ctor public WindowInsetsCompat.Builder();
+ ctor public WindowInsetsCompat.Builder(androidx.core.view.WindowInsetsCompat);
+ method public androidx.core.view.WindowInsetsCompat build();
+ method public androidx.core.view.WindowInsetsCompat.Builder setDisplayCutout(androidx.core.view.DisplayCutoutCompat?);
+ method public androidx.core.view.WindowInsetsCompat.Builder setInsets(int, androidx.core.graphics.Insets);
+ method public androidx.core.view.WindowInsetsCompat.Builder setInsetsIgnoringVisibility(int, androidx.core.graphics.Insets);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat.Builder setMandatorySystemGestureInsets(androidx.core.graphics.Insets);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat.Builder setStableInsets(androidx.core.graphics.Insets);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat.Builder setSystemGestureInsets(androidx.core.graphics.Insets);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat.Builder setSystemWindowInsets(androidx.core.graphics.Insets);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat.Builder setTappableElementInsets(androidx.core.graphics.Insets);
+ method public androidx.core.view.WindowInsetsCompat.Builder setVisible(int, boolean);
+ }
+
+ public static final class WindowInsetsCompat.Type {
+ method public static int captionBar();
+ method public static int displayCutout();
+ method public static int ime();
+ method public static int mandatorySystemGestures();
+ method public static int navigationBars();
+ method public static int statusBars();
+ method public static int systemBars();
+ method public static int systemGestures();
+ method public static int tappableElement();
+ }
+
+ public final class WindowInsetsControllerCompat {
+ ctor public WindowInsetsControllerCompat(android.view.Window, android.view.View);
+ method public void addOnControllableInsetsChangedListener(androidx.core.view.WindowInsetsControllerCompat.OnControllableInsetsChangedListener);
+ method public void controlWindowInsetsAnimation(int, long, android.view.animation.Interpolator?, android.os.CancellationSignal?, androidx.core.view.WindowInsetsAnimationControlListenerCompat);
+ method public int getSystemBarsBehavior();
+ method public void hide(int);
+ method public boolean isAppearanceLightNavigationBars();
+ method public boolean isAppearanceLightStatusBars();
+ method public void removeOnControllableInsetsChangedListener(androidx.core.view.WindowInsetsControllerCompat.OnControllableInsetsChangedListener);
+ method public void setAppearanceLightNavigationBars(boolean);
+ method public void setAppearanceLightStatusBars(boolean);
+ method public void setSystemBarsBehavior(int);
+ method public void show(int);
+ method @Deprecated @RequiresApi(30) public static androidx.core.view.WindowInsetsControllerCompat toWindowInsetsControllerCompat(android.view.WindowInsetsController);
+ field public static final int BEHAVIOR_DEFAULT = 1; // 0x1
+ field @Deprecated public static final int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1; // 0x1
+ field @Deprecated public static final int BEHAVIOR_SHOW_BARS_BY_TOUCH = 0; // 0x0
+ field public static final int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2; // 0x2
+ }
+
+ public static interface WindowInsetsControllerCompat.OnControllableInsetsChangedListener {
+ method public void onControllableInsetsChanged(androidx.core.view.WindowInsetsControllerCompat, int);
+ }
+
+}
+
+package androidx.core.view.accessibility {
+
+ public final class AccessibilityClickableSpanCompat extends android.text.style.ClickableSpan {
+ method public void onClick(android.view.View);
+ }
+
+ public final class AccessibilityEventCompat {
+ method @Deprecated public static void appendRecord(android.view.accessibility.AccessibilityEvent!, androidx.core.view.accessibility.AccessibilityRecordCompat!);
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! asRecord(android.view.accessibility.AccessibilityEvent!);
+ method public static int getAction(android.view.accessibility.AccessibilityEvent);
+ method public static int getContentChangeTypes(android.view.accessibility.AccessibilityEvent);
+ method public static int getMovementGranularity(android.view.accessibility.AccessibilityEvent);
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! getRecord(android.view.accessibility.AccessibilityEvent!, int);
+ method @Deprecated public static int getRecordCount(android.view.accessibility.AccessibilityEvent!);
+ method public static boolean isAccessibilityDataSensitive(android.view.accessibility.AccessibilityEvent);
+ method public static void setAccessibilityDataSensitive(android.view.accessibility.AccessibilityEvent, boolean);
+ method public static void setAction(android.view.accessibility.AccessibilityEvent, int);
+ method public static void setContentChangeTypes(android.view.accessibility.AccessibilityEvent, int);
+ method public static void setMovementGranularity(android.view.accessibility.AccessibilityEvent, int);
+ field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+ field public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 1024; // 0x400
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
+ field public static final int CONTENT_CHANGE_TYPE_ENABLED = 4096; // 0x1000
+ field public static final int CONTENT_CHANGE_TYPE_ERROR = 2048; // 0x800
+ field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
+ field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
+ field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
+ field public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 64; // 0x40
+ field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1
+ field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2
+ field public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0; // 0x0
+ field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
+ field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
+ field public static final int TYPE_ASSIST_READING_CONTEXT = 16777216; // 0x1000000
+ field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000
+ field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000
+ field @Deprecated public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 1024; // 0x400
+ field @Deprecated public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 512; // 0x200
+ field public static final int TYPE_TOUCH_INTERACTION_END = 2097152; // 0x200000
+ field public static final int TYPE_TOUCH_INTERACTION_START = 1048576; // 0x100000
+ field public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 32768; // 0x8000
+ field public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 65536; // 0x10000
+ field public static final int TYPE_VIEW_CONTEXT_CLICKED = 8388608; // 0x800000
+ field @Deprecated public static final int TYPE_VIEW_HOVER_ENTER = 128; // 0x80
+ field @Deprecated public static final int TYPE_VIEW_HOVER_EXIT = 256; // 0x100
+ field @Deprecated public static final int TYPE_VIEW_SCROLLED = 4096; // 0x1000
+ field public static final int TYPE_VIEW_TARGETED_BY_SCROLL = 67108864; // 0x4000000
+ field @Deprecated public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192; // 0x2000
+ field public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 131072; // 0x20000
+ field public static final int TYPE_WINDOWS_CHANGED = 4194304; // 0x400000
+ field @Deprecated public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800
+ }
+
+ public final class AccessibilityManagerCompat {
+ method @Deprecated public static boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager!, androidx.core.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListener!);
+ method public static boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager, androidx.core.view.accessibility.AccessibilityManagerCompat.TouchExplorationStateChangeListener);
+ method @Deprecated public static java.util.List<android.accessibilityservice.AccessibilityServiceInfo!>! getEnabledAccessibilityServiceList(android.view.accessibility.AccessibilityManager!, int);
+ method @Deprecated public static java.util.List<android.accessibilityservice.AccessibilityServiceInfo!>! getInstalledAccessibilityServiceList(android.view.accessibility.AccessibilityManager!);
+ method public static boolean isRequestFromAccessibilityTool(android.view.accessibility.AccessibilityManager);
+ method @Deprecated public static boolean isTouchExplorationEnabled(android.view.accessibility.AccessibilityManager!);
+ method @Deprecated public static boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager!, androidx.core.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListener!);
+ method public static boolean removeTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager, androidx.core.view.accessibility.AccessibilityManagerCompat.TouchExplorationStateChangeListener);
+ }
+
+ @Deprecated public static interface AccessibilityManagerCompat.AccessibilityStateChangeListener {
+ method @Deprecated public void onAccessibilityStateChanged(boolean);
+ }
+
+ @Deprecated public abstract static class AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat implements androidx.core.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListener {
+ ctor @Deprecated public AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat();
+ }
+
+ public static interface AccessibilityManagerCompat.TouchExplorationStateChangeListener {
+ method public void onTouchExplorationStateChanged(boolean);
+ }
+
+ public class AccessibilityNodeInfoCompat {
+ ctor @Deprecated public AccessibilityNodeInfoCompat(Object!);
+ method public void addAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
+ method public void addAction(int);
+ method public void addChild(android.view.View!);
+ method public void addChild(android.view.View!, int);
+ method public boolean canOpenPopup();
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat!>! findAccessibilityNodeInfosByText(String!);
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat!>! findAccessibilityNodeInfosByViewId(String!);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! findFocus(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! focusSearch(int);
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!>! getActionList();
+ method @Deprecated public int getActions();
+ method public java.util.List<java.lang.String!> getAvailableExtraData();
+ method @Deprecated public void getBoundsInParent(android.graphics.Rect!);
+ method public void getBoundsInScreen(android.graphics.Rect!);
+ method public void getBoundsInWindow(android.graphics.Rect);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getChild(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getChild(int, int);
+ method public int getChildCount();
+ method public CharSequence! getClassName();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat! getCollectionInfo();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat! getCollectionItemInfo();
+ method public CharSequence? getContainerTitle();
+ method public CharSequence! getContentDescription();
+ method public int getDrawingOrder();
+ method public CharSequence! getError();
+ method public android.view.accessibility.AccessibilityNodeInfo.ExtraRenderingInfo? getExtraRenderingInfo();
+ method public android.os.Bundle! getExtras();
+ method public CharSequence? getHintText();
+ method @Deprecated public Object! getInfo();
+ method public int getInputType();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getLabelFor();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getLabeledBy();
+ method public int getLiveRegion();
+ method public int getMaxTextLength();
+ method public long getMinDurationBetweenContentChangesMillis();
+ method public int getMovementGranularities();
+ method public CharSequence! getPackageName();
+ method public CharSequence? getPaneTitle();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getParent();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getParent(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat! getRangeInfo();
+ method public CharSequence? getRoleDescription();
+ method public CharSequence? getStateDescription();
+ method public CharSequence! getText();
+ method public int getTextSelectionEnd();
+ method public int getTextSelectionStart();
+ method public CharSequence? getTooltipText();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.TouchDelegateInfoCompat? getTouchDelegateInfo();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getTraversalAfter();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getTraversalBefore();
+ method public String? getUniqueId();
+ method public String! getViewIdResourceName();
+ method public androidx.core.view.accessibility.AccessibilityWindowInfoCompat! getWindow();
+ method public int getWindowId();
+ method public boolean hasRequestInitialAccessibilityFocus();
+ method public boolean isAccessibilityDataSensitive();
+ method public boolean isAccessibilityFocused();
+ method public boolean isCheckable();
+ method public boolean isChecked();
+ method public boolean isClickable();
+ method public boolean isContentInvalid();
+ method public boolean isContextClickable();
+ method public boolean isDismissable();
+ method public boolean isEditable();
+ method public boolean isEnabled();
+ method public boolean isFocusable();
+ method public boolean isFocused();
+ method public boolean isGranularScrollingSupported();
+ method public boolean isHeading();
+ method public boolean isImportantForAccessibility();
+ method public boolean isLongClickable();
+ method public boolean isMultiLine();
+ method public boolean isPassword();
+ method public boolean isScreenReaderFocusable();
+ method public boolean isScrollable();
+ method public boolean isSelected();
+ method public boolean isShowingHintText();
+ method public boolean isTextEntryKey();
+ method public boolean isTextSelectable();
+ method public boolean isVisibleToUser();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain(android.view.View!);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain(android.view.View!, int);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain(androidx.core.view.accessibility.AccessibilityNodeInfoCompat!);
+ method public boolean performAction(int);
+ method public boolean performAction(int, android.os.Bundle!);
+ method @Deprecated public void recycle();
+ method public boolean refresh();
+ method public boolean removeAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
+ method public boolean removeChild(android.view.View!);
+ method public boolean removeChild(android.view.View!, int);
+ method public void setAccessibilityDataSensitive(boolean);
+ method public void setAccessibilityFocused(boolean);
+ method public void setAvailableExtraData(java.util.List<java.lang.String!>);
+ method @Deprecated public void setBoundsInParent(android.graphics.Rect!);
+ method public void setBoundsInScreen(android.graphics.Rect!);
+ method public void setBoundsInWindow(android.graphics.Rect);
+ method public void setCanOpenPopup(boolean);
+ method public void setCheckable(boolean);
+ method public void setChecked(boolean);
+ method public void setClassName(CharSequence!);
+ method public void setClickable(boolean);
+ method public void setCollectionInfo(Object!);
+ method public void setCollectionItemInfo(Object!);
+ method public void setContainerTitle(CharSequence?);
+ method public void setContentDescription(CharSequence!);
+ method public void setContentInvalid(boolean);
+ method public void setContextClickable(boolean);
+ method public void setDismissable(boolean);
+ method public void setDrawingOrder(int);
+ method public void setEditable(boolean);
+ method public void setEnabled(boolean);
+ method public void setError(CharSequence!);
+ method public void setFocusable(boolean);
+ method public void setFocused(boolean);
+ method public void setGranularScrollingSupported(boolean);
+ method public void setHeading(boolean);
+ method public void setHintText(CharSequence?);
+ method public void setImportantForAccessibility(boolean);
+ method public void setInputType(int);
+ method public void setLabelFor(android.view.View!);
+ method public void setLabelFor(android.view.View!, int);
+ method public void setLabeledBy(android.view.View!);
+ method public void setLabeledBy(android.view.View!, int);
+ method public void setLiveRegion(int);
+ method public void setLongClickable(boolean);
+ method public void setMaxTextLength(int);
+ method public void setMinDurationBetweenContentChangesMillis(long);
+ method public void setMovementGranularities(int);
+ method public void setMultiLine(boolean);
+ method public void setPackageName(CharSequence!);
+ method public void setPaneTitle(CharSequence?);
+ method public void setParent(android.view.View!);
+ method public void setParent(android.view.View!, int);
+ method public void setPassword(boolean);
+ method public void setQueryFromAppProcessEnabled(android.view.View, boolean);
+ method public void setRangeInfo(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat!);
+ method public void setRequestInitialAccessibilityFocus(boolean);
+ method public void setRoleDescription(CharSequence?);
+ method public void setScreenReaderFocusable(boolean);
+ method public void setScrollable(boolean);
+ method public void setSelected(boolean);
+ method public void setShowingHintText(boolean);
+ method public void setSource(android.view.View!);
+ method public void setSource(android.view.View!, int);
+ method public void setStateDescription(CharSequence?);
+ method public void setText(CharSequence!);
+ method public void setTextEntryKey(boolean);
+ method public void setTextSelectable(boolean);
+ method public void setTextSelection(int, int);
+ method public void setTooltipText(CharSequence?);
+ method public void setTouchDelegateInfo(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.TouchDelegateInfoCompat);
+ method public void setTraversalAfter(android.view.View!);
+ method public void setTraversalAfter(android.view.View!, int);
+ method public void setTraversalBefore(android.view.View!);
+ method public void setTraversalBefore(android.view.View!, int);
+ method public void setUniqueId(String?);
+ method public void setViewIdResourceName(String!);
+ method public void setVisibleToUser(boolean);
+ method public android.view.accessibility.AccessibilityNodeInfo! unwrap();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! wrap(android.view.accessibility.AccessibilityNodeInfo);
+ field public static final int ACTION_ACCESSIBILITY_FOCUS = 64; // 0x40
+ field public static final String ACTION_ARGUMENT_COLUMN_INT = "android.view.accessibility.action.ARGUMENT_COLUMN_INT";
+ field public static final String ACTION_ARGUMENT_DIRECTION_INT = "androidx.core.view.accessibility.action.ARGUMENT_DIRECTION_INT";
+ field public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN = "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
+ field public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING = "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+ field public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
+ field public static final String ACTION_ARGUMENT_MOVE_WINDOW_X = "ACTION_ARGUMENT_MOVE_WINDOW_X";
+ field public static final String ACTION_ARGUMENT_MOVE_WINDOW_Y = "ACTION_ARGUMENT_MOVE_WINDOW_Y";
+ field public static final String ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT = "android.view.accessibility.action.ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT";
+ field public static final String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
+ field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";
+ field public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "androidx.core.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
+ field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
+ field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
+ field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+ field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80
+ field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2
+ field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8
+ field public static final int ACTION_CLICK = 16; // 0x10
+ field public static final int ACTION_COLLAPSE = 524288; // 0x80000
+ field public static final int ACTION_COPY = 16384; // 0x4000
+ field public static final int ACTION_CUT = 65536; // 0x10000
+ field public static final int ACTION_DISMISS = 1048576; // 0x100000
+ field public static final int ACTION_EXPAND = 262144; // 0x40000
+ field public static final int ACTION_FOCUS = 1; // 0x1
+ field public static final int ACTION_LONG_CLICK = 32; // 0x20
+ field public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 256; // 0x100
+ field public static final int ACTION_NEXT_HTML_ELEMENT = 1024; // 0x400
+ field public static final int ACTION_PASTE = 32768; // 0x8000
+ field public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 512; // 0x200
+ field public static final int ACTION_PREVIOUS_HTML_ELEMENT = 2048; // 0x800
+ field public static final int ACTION_SCROLL_BACKWARD = 8192; // 0x2000
+ field public static final int ACTION_SCROLL_FORWARD = 4096; // 0x1000
+ field public static final int ACTION_SELECT = 4; // 0x4
+ field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
+ field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+ field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.core.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+ field public static final int EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH = 20000; // 0x4e20
+ field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.core.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+ field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.core.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
+ field public static final int FLAG_PREFETCH_ANCESTORS = 1; // 0x1
+ field public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 16; // 0x10
+ field public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 8; // 0x8
+ field public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 4; // 0x4
+ field public static final int FLAG_PREFETCH_SIBLINGS = 2; // 0x2
+ field public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 32; // 0x20
+ field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
+ field public static final int FOCUS_INPUT = 1; // 0x1
+ field public static final int MAX_NUMBER_OF_PREFETCHED_NODES = 50; // 0x32
+ field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
+ field public static final int MOVEMENT_GRANULARITY_LINE = 4; // 0x4
+ field public static final int MOVEMENT_GRANULARITY_PAGE = 16; // 0x10
+ field public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 8; // 0x8
+ field public static final int MOVEMENT_GRANULARITY_WORD = 2; // 0x2
+ }
+
+ public static class AccessibilityNodeInfoCompat.AccessibilityActionCompat {
+ ctor public AccessibilityNodeInfoCompat.AccessibilityActionCompat(int, CharSequence!);
+ method public int getId();
+ method public CharSequence! getLabel();
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_ACCESSIBILITY_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLEAR_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLEAR_SELECTION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLICK;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_COLLAPSE;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CONTEXT_CLICK;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_COPY;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CUT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_DISMISS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_DRAG_CANCEL;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_DRAG_DROP;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_DRAG_START;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_EXPAND;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_HIDE_TOOLTIP;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_IME_ENTER;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_LONG_CLICK;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_MOVE_WINDOW;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_NEXT_AT_MOVEMENT_GRANULARITY;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_NEXT_HTML_ELEMENT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PAGE_DOWN;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PAGE_LEFT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PAGE_RIGHT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PAGE_UP;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_PASTE;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PRESS_AND_HOLD;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_PREVIOUS_HTML_ELEMENT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_BACKWARD;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_DOWN;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_FORWARD;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_SCROLL_IN_DIRECTION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_LEFT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_RIGHT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_TO_POSITION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_UP;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SELECT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SET_PROGRESS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SET_SELECTION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SET_TEXT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SHOW_ON_SCREEN;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_SHOW_TEXT_SUGGESTIONS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SHOW_TOOLTIP;
+ }
+
+ public static class AccessibilityNodeInfoCompat.CollectionInfoCompat {
+ method public int getColumnCount();
+ method public int getRowCount();
+ method public int getSelectionMode();
+ method public boolean isHierarchical();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat! obtain(int, int, boolean);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat! obtain(int, int, boolean, int);
+ field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
+ field public static final int SELECTION_MODE_NONE = 0; // 0x0
+ field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
+ }
+
+ public static class AccessibilityNodeInfoCompat.CollectionItemInfoCompat {
+ method public int getColumnIndex();
+ method public int getColumnSpan();
+ method public String? getColumnTitle();
+ method public int getRowIndex();
+ method public int getRowSpan();
+ method public String? getRowTitle();
+ method @Deprecated public boolean isHeading();
+ method public boolean isSelected();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat! obtain(int, int, int, int, boolean);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat! obtain(int, int, int, int, boolean, boolean);
+ }
+
+ public static final class AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder {
+ ctor public AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat build();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setColumnIndex(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setColumnSpan(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setColumnTitle(String?);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setHeading(boolean);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setRowIndex(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setRowSpan(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setRowTitle(String?);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setSelected(boolean);
+ }
+
+ public static class AccessibilityNodeInfoCompat.RangeInfoCompat {
+ ctor public AccessibilityNodeInfoCompat.RangeInfoCompat(int, float, float, float);
+ method public float getCurrent();
+ method public float getMax();
+ method public float getMin();
+ method public int getType();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat! obtain(int, float, float, float);
+ field public static final int RANGE_TYPE_FLOAT = 1; // 0x1
+ field public static final int RANGE_TYPE_INT = 0; // 0x0
+ field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
+ }
+
+ public static final class AccessibilityNodeInfoCompat.TouchDelegateInfoCompat {
+ ctor public AccessibilityNodeInfoCompat.TouchDelegateInfoCompat(java.util.Map<android.graphics.Region!,android.view.View!>);
+ method public android.graphics.Region? getRegionAt(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getRegionCount();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getTargetForRegion(android.graphics.Region);
+ }
+
+ public class AccessibilityNodeProviderCompat {
+ ctor public AccessibilityNodeProviderCompat();
+ ctor public AccessibilityNodeProviderCompat(Object?);
+ method public void addExtraDataToAccessibilityNodeInfo(int, androidx.core.view.accessibility.AccessibilityNodeInfoCompat, String, android.os.Bundle?);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? createAccessibilityNodeInfo(int);
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat!>? findAccessibilityNodeInfosByText(String, int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? findFocus(int);
+ method public Object? getProvider();
+ method public boolean performAction(int, int, android.os.Bundle?);
+ field public static final int HOST_VIEW_ID = -1; // 0xffffffff
+ }
+
+ public class AccessibilityRecordCompat {
+ ctor @Deprecated public AccessibilityRecordCompat(Object!);
+ method @Deprecated public boolean equals(Object?);
+ method @Deprecated public int getAddedCount();
+ method @Deprecated public CharSequence! getBeforeText();
+ method @Deprecated public CharSequence! getClassName();
+ method @Deprecated public CharSequence! getContentDescription();
+ method @Deprecated public int getCurrentItemIndex();
+ method @Deprecated public int getFromIndex();
+ method @Deprecated public Object! getImpl();
+ method @Deprecated public int getItemCount();
+ method @Deprecated public int getMaxScrollX();
+ method public static int getMaxScrollX(android.view.accessibility.AccessibilityRecord);
+ method @Deprecated public int getMaxScrollY();
+ method public static int getMaxScrollY(android.view.accessibility.AccessibilityRecord);
+ method @Deprecated public android.os.Parcelable! getParcelableData();
+ method @Deprecated public int getRemovedCount();
+ method @Deprecated public int getScrollX();
+ method @Deprecated public int getScrollY();
+ method @Deprecated public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getSource();
+ method @Deprecated public java.util.List<java.lang.CharSequence!>! getText();
+ method @Deprecated public int getToIndex();
+ method @Deprecated public int getWindowId();
+ method @Deprecated public int hashCode();
+ method @Deprecated public boolean isChecked();
+ method @Deprecated public boolean isEnabled();
+ method @Deprecated public boolean isFullScreen();
+ method @Deprecated public boolean isPassword();
+ method @Deprecated public boolean isScrollable();
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! obtain();
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! obtain(androidx.core.view.accessibility.AccessibilityRecordCompat!);
+ method @Deprecated public void recycle();
+ method @Deprecated public void setAddedCount(int);
+ method @Deprecated public void setBeforeText(CharSequence!);
+ method @Deprecated public void setChecked(boolean);
+ method @Deprecated public void setClassName(CharSequence!);
+ method @Deprecated public void setContentDescription(CharSequence!);
+ method @Deprecated public void setCurrentItemIndex(int);
+ method @Deprecated public void setEnabled(boolean);
+ method @Deprecated public void setFromIndex(int);
+ method @Deprecated public void setFullScreen(boolean);
+ method @Deprecated public void setItemCount(int);
+ method public static void setMaxScrollX(android.view.accessibility.AccessibilityRecord, int);
+ method @Deprecated public void setMaxScrollX(int);
+ method public static void setMaxScrollY(android.view.accessibility.AccessibilityRecord, int);
+ method @Deprecated public void setMaxScrollY(int);
+ method @Deprecated public void setParcelableData(android.os.Parcelable!);
+ method @Deprecated public void setPassword(boolean);
+ method @Deprecated public void setRemovedCount(int);
+ method @Deprecated public void setScrollX(int);
+ method @Deprecated public void setScrollY(int);
+ method @Deprecated public void setScrollable(boolean);
+ method public static void setSource(android.view.accessibility.AccessibilityRecord, android.view.View?, int);
+ method @Deprecated public void setSource(android.view.View!);
+ method @Deprecated public void setSource(android.view.View!, int);
+ method @Deprecated public void setToIndex(int);
+ }
+
+ public interface AccessibilityViewCommand {
+ method public boolean perform(android.view.View, androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments?);
+ }
+
+ public abstract static class AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.CommandArguments();
+ }
+
+ public static final class AccessibilityViewCommand.MoveAtGranularityArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.MoveAtGranularityArguments();
+ method public boolean getExtendSelection();
+ method public int getGranularity();
+ }
+
+ public static final class AccessibilityViewCommand.MoveHtmlArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.MoveHtmlArguments();
+ method public String? getHTMLElement();
+ }
+
+ public static final class AccessibilityViewCommand.MoveWindowArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.MoveWindowArguments();
+ method public int getX();
+ method public int getY();
+ }
+
+ public static final class AccessibilityViewCommand.ScrollToPositionArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.ScrollToPositionArguments();
+ method public int getColumn();
+ method public int getRow();
+ }
+
+ public static final class AccessibilityViewCommand.SetProgressArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.SetProgressArguments();
+ method public float getProgress();
+ }
+
+ public static final class AccessibilityViewCommand.SetSelectionArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.SetSelectionArguments();
+ method public int getEnd();
+ method public int getStart();
+ }
+
+ public static final class AccessibilityViewCommand.SetTextArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.SetTextArguments();
+ method public CharSequence? getText();
+ }
+
+ public class AccessibilityWindowInfoCompat {
+ ctor public AccessibilityWindowInfoCompat();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getAnchor();
+ method public void getBoundsInScreen(android.graphics.Rect);
+ method public androidx.core.view.accessibility.AccessibilityWindowInfoCompat? getChild(int);
+ method public int getChildCount();
+ method public int getDisplayId();
+ method public int getId();
+ method public int getLayer();
+ method public androidx.core.os.LocaleListCompat getLocales();
+ method public androidx.core.view.accessibility.AccessibilityWindowInfoCompat? getParent();
+ method public void getRegionInScreen(android.graphics.Region);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getRoot();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getRoot(int);
+ method public CharSequence? getTitle();
+ method public long getTransitionTimeMillis();
+ method public int getType();
+ method public boolean isAccessibilityFocused();
+ method public boolean isActive();
+ method public boolean isFocused();
+ method public boolean isInPictureInPictureMode();
+ method public static androidx.core.view.accessibility.AccessibilityWindowInfoCompat? obtain();
+ method public static androidx.core.view.accessibility.AccessibilityWindowInfoCompat? obtain(androidx.core.view.accessibility.AccessibilityWindowInfoCompat?);
+ method @Deprecated public void recycle();
+ method public android.view.accessibility.AccessibilityWindowInfo? unwrap();
+ field public static final int TYPE_ACCESSIBILITY_OVERLAY = 4; // 0x4
+ field public static final int TYPE_APPLICATION = 1; // 0x1
+ field public static final int TYPE_INPUT_METHOD = 2; // 0x2
+ field public static final int TYPE_MAGNIFICATION_OVERLAY = 6; // 0x6
+ field public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5; // 0x5
+ field public static final int TYPE_SYSTEM = 3; // 0x3
+ }
+
+}
+
+package androidx.core.view.animation {
+
+ public final class PathInterpolatorCompat {
+ method public static android.view.animation.Interpolator create(android.graphics.Path);
+ method public static android.view.animation.Interpolator create(float, float);
+ method public static android.view.animation.Interpolator create(float, float, float, float);
+ }
+
+}
+
+package androidx.core.view.autofill {
+
+ public class AutofillIdCompat {
+ method @RequiresApi(26) public android.view.autofill.AutofillId toAutofillId();
+ method @RequiresApi(26) public static androidx.core.view.autofill.AutofillIdCompat toAutofillIdCompat(android.view.autofill.AutofillId);
+ }
+
+}
+
+package androidx.core.view.contentcapture {
+
+ public class ContentCaptureSessionCompat {
+ method public android.view.autofill.AutofillId? newAutofillId(long);
+ method public androidx.core.view.ViewStructureCompat? newVirtualViewStructure(android.view.autofill.AutofillId, long);
+ method public void notifyViewTextChanged(android.view.autofill.AutofillId, CharSequence?);
+ method public void notifyViewsAppeared(java.util.List<android.view.ViewStructure!>);
+ method public void notifyViewsDisappeared(long[]);
+ method @RequiresApi(29) public android.view.contentcapture.ContentCaptureSession toContentCaptureSession();
+ method @RequiresApi(29) public static androidx.core.view.contentcapture.ContentCaptureSessionCompat toContentCaptureSessionCompat(android.view.contentcapture.ContentCaptureSession, android.view.View);
+ }
+
+}
+
+package androidx.core.view.inputmethod {
+
+ public final class EditorInfoCompat {
+ ctor @Deprecated public EditorInfoCompat();
+ method public static String![] getContentMimeTypes(android.view.inputmethod.EditorInfo);
+ method public static CharSequence? getInitialSelectedText(android.view.inputmethod.EditorInfo, int);
+ method public static CharSequence? getInitialTextAfterCursor(android.view.inputmethod.EditorInfo, int, int);
+ method public static CharSequence? getInitialTextBeforeCursor(android.view.inputmethod.EditorInfo, int, int);
+ method public static boolean isStylusHandwritingEnabled(android.view.inputmethod.EditorInfo);
+ method public static void setContentMimeTypes(android.view.inputmethod.EditorInfo, String![]?);
+ method public static void setInitialSurroundingSubText(android.view.inputmethod.EditorInfo, CharSequence, int);
+ method public static void setInitialSurroundingText(android.view.inputmethod.EditorInfo, CharSequence);
+ method public static void setStylusHandwritingEnabled(android.view.inputmethod.EditorInfo, boolean);
+ field public static final int IME_FLAG_FORCE_ASCII = -2147483648; // 0x80000000
+ field public static final int IME_FLAG_NO_PERSONALIZED_LEARNING = 16777216; // 0x1000000
+ }
+
+ public final class InputConnectionCompat {
+ ctor @Deprecated public InputConnectionCompat();
+ method public static boolean commitContent(android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo, androidx.core.view.inputmethod.InputContentInfoCompat, int, android.os.Bundle?);
+ method @Deprecated public static android.view.inputmethod.InputConnection createWrapper(android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo, androidx.core.view.inputmethod.InputConnectionCompat.OnCommitContentListener);
+ method public static android.view.inputmethod.InputConnection createWrapper(android.view.View, android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo);
+ field public static final int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = 1; // 0x1
+ }
+
+ public static interface InputConnectionCompat.OnCommitContentListener {
+ method public boolean onCommitContent(androidx.core.view.inputmethod.InputContentInfoCompat, int, android.os.Bundle?);
+ }
+
+ public final class InputContentInfoCompat {
+ ctor public InputContentInfoCompat(android.net.Uri, android.content.ClipDescription, android.net.Uri?);
+ method public android.net.Uri getContentUri();
+ method public android.content.ClipDescription getDescription();
+ method public android.net.Uri? getLinkUri();
+ method public void releasePermission();
+ method public void requestPermission();
+ method public Object? unwrap();
+ method public static androidx.core.view.inputmethod.InputContentInfoCompat? wrap(Object?);
+ }
+
+}
+
+package androidx.core.widget {
+
+ public abstract class AutoScrollHelper implements android.view.View.OnTouchListener {
+ ctor public AutoScrollHelper(android.view.View);
+ method public abstract boolean canTargetScrollHorizontally(int);
+ method public abstract boolean canTargetScrollVertically(int);
+ method public boolean isEnabled();
+ method public boolean isExclusive();
+ method public boolean onTouch(android.view.View!, android.view.MotionEvent!);
+ method public abstract void scrollTargetBy(int, int);
+ method public androidx.core.widget.AutoScrollHelper setActivationDelay(int);
+ method public androidx.core.widget.AutoScrollHelper setEdgeType(int);
+ method public androidx.core.widget.AutoScrollHelper! setEnabled(boolean);
+ method public androidx.core.widget.AutoScrollHelper! setExclusive(boolean);
+ method public androidx.core.widget.AutoScrollHelper setMaximumEdges(float, float);
+ method public androidx.core.widget.AutoScrollHelper setMaximumVelocity(float, float);
+ method public androidx.core.widget.AutoScrollHelper setMinimumVelocity(float, float);
+ method public androidx.core.widget.AutoScrollHelper setRampDownDuration(int);
+ method public androidx.core.widget.AutoScrollHelper setRampUpDuration(int);
+ method public androidx.core.widget.AutoScrollHelper setRelativeEdges(float, float);
+ method public androidx.core.widget.AutoScrollHelper setRelativeVelocity(float, float);
+ field public static final int EDGE_TYPE_INSIDE = 0; // 0x0
+ field public static final int EDGE_TYPE_INSIDE_EXTEND = 1; // 0x1
+ field public static final int EDGE_TYPE_OUTSIDE = 2; // 0x2
+ field public static final float NO_MAX = 3.4028235E38f;
+ field public static final float NO_MIN = 0.0f;
+ field public static final float RELATIVE_UNSPECIFIED = 0.0f;
+ }
+
+ public final class CheckedTextViewCompat {
+ method public static android.graphics.drawable.Drawable? getCheckMarkDrawable(android.widget.CheckedTextView);
+ method public static android.content.res.ColorStateList? getCheckMarkTintList(android.widget.CheckedTextView);
+ method public static android.graphics.PorterDuff.Mode? getCheckMarkTintMode(android.widget.CheckedTextView);
+ method public static void setCheckMarkTintList(android.widget.CheckedTextView, android.content.res.ColorStateList?);
+ method public static void setCheckMarkTintMode(android.widget.CheckedTextView, android.graphics.PorterDuff.Mode?);
+ }
+
+ public final class CompoundButtonCompat {
+ method public static android.graphics.drawable.Drawable? getButtonDrawable(android.widget.CompoundButton);
+ method public static android.content.res.ColorStateList? getButtonTintList(android.widget.CompoundButton);
+ method public static android.graphics.PorterDuff.Mode? getButtonTintMode(android.widget.CompoundButton);
+ method public static void setButtonTintList(android.widget.CompoundButton, android.content.res.ColorStateList?);
+ method public static void setButtonTintMode(android.widget.CompoundButton, android.graphics.PorterDuff.Mode?);
+ }
+
+ public class ContentLoadingProgressBar extends android.widget.ProgressBar {
+ ctor public ContentLoadingProgressBar(android.content.Context);
+ ctor public ContentLoadingProgressBar(android.content.Context, android.util.AttributeSet?);
+ method public void hide();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void show();
+ }
+
+ public final class EdgeEffectCompat {
+ ctor @Deprecated public EdgeEffectCompat(android.content.Context!);
+ method public static android.widget.EdgeEffect create(android.content.Context, android.util.AttributeSet?);
+ method @Deprecated public boolean draw(android.graphics.Canvas!);
+ method @Deprecated public void finish();
+ method public static float getDistance(android.widget.EdgeEffect);
+ method @Deprecated public boolean isFinished();
+ method @Deprecated public boolean onAbsorb(int);
+ method public static void onPull(android.widget.EdgeEffect, float, float);
+ method @Deprecated public boolean onPull(float);
+ method @Deprecated public boolean onPull(float, float);
+ method public static float onPullDistance(android.widget.EdgeEffect, float, float);
+ method @Deprecated public boolean onRelease();
+ method @Deprecated public void setSize(int, int);
+ }
+
+ public class ImageViewCompat {
+ method public static android.content.res.ColorStateList? getImageTintList(android.widget.ImageView);
+ method public static android.graphics.PorterDuff.Mode? getImageTintMode(android.widget.ImageView);
+ method public static void setImageTintList(android.widget.ImageView, android.content.res.ColorStateList?);
+ method public static void setImageTintMode(android.widget.ImageView, android.graphics.PorterDuff.Mode?);
+ }
+
+ public final class ListPopupWindowCompat {
+ method public static android.view.View.OnTouchListener? createDragToOpenListener(android.widget.ListPopupWindow, android.view.View);
+ method @Deprecated public static android.view.View.OnTouchListener! createDragToOpenListener(Object!, android.view.View!);
+ }
+
+ public class ListViewAutoScrollHelper extends androidx.core.widget.AutoScrollHelper {
+ ctor public ListViewAutoScrollHelper(android.widget.ListView);
+ method public boolean canTargetScrollHorizontally(int);
+ method public boolean canTargetScrollVertically(int);
+ method public void scrollTargetBy(int, int);
+ }
+
+ @Deprecated public final class ListViewCompat {
+ method @Deprecated public static boolean canScrollList(android.widget.ListView, int);
+ method @Deprecated public static void scrollListBy(android.widget.ListView, int);
+ }
+
+ public class NestedScrollView extends android.widget.FrameLayout implements androidx.core.view.NestedScrollingChild3 androidx.core.view.NestedScrollingParent3 androidx.core.view.ScrollingView {
+ ctor public NestedScrollView(android.content.Context);
+ ctor public NestedScrollView(android.content.Context, android.util.AttributeSet?);
+ ctor public NestedScrollView(android.content.Context, android.util.AttributeSet?, int);
+ method public boolean arrowScroll(int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollExtent();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollOffset();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollRange();
+ method protected int computeScrollDeltaToGetChildRectOnScreen(android.graphics.Rect!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollExtent();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollOffset();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollRange();
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?, int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?, int);
+ method public void dispatchNestedScroll(int, int, int, int, int[]?, int, int[]);
+ method public boolean executeKeyEvent(android.view.KeyEvent);
+ method public void fling(int);
+ method public boolean fullScroll(int);
+ method public int getMaxScrollAmount();
+ method public boolean hasNestedScrollingParent(int);
+ method public boolean isFillViewport();
+ method public boolean isSmoothScrollingEnabled();
+ method public void onAttachedToWindow();
+ method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int, int[]);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View, int);
+ method public boolean pageScroll(int);
+ method public void setFillViewport(boolean);
+ method public void setOnScrollChangeListener(androidx.core.widget.NestedScrollView.OnScrollChangeListener?);
+ method public void setSmoothScrollingEnabled(boolean);
+ method public final void smoothScrollBy(int, int);
+ method public final void smoothScrollBy(int, int, int);
+ method public final void smoothScrollTo(int, int);
+ method public final void smoothScrollTo(int, int, int);
+ method public boolean startNestedScroll(int, int);
+ method public void stopNestedScroll(int);
+ }
+
+ public static interface NestedScrollView.OnScrollChangeListener {
+ method public void onScrollChange(androidx.core.widget.NestedScrollView, int, int, int, int);
+ }
+
+ public final class PopupMenuCompat {
+ method public static android.view.View.OnTouchListener? getDragToOpenListener(Object);
+ }
+
+ public final class PopupWindowCompat {
+ method public static boolean getOverlapAnchor(android.widget.PopupWindow);
+ method public static int getWindowLayoutType(android.widget.PopupWindow);
+ method public static void setOverlapAnchor(android.widget.PopupWindow, boolean);
+ method public static void setWindowLayoutType(android.widget.PopupWindow, int);
+ method public static void showAsDropDown(android.widget.PopupWindow, android.view.View, int, int, int);
+ }
+
+ @Deprecated public final class ScrollerCompat {
+ method @Deprecated public void abortAnimation();
+ method @Deprecated public boolean computeScrollOffset();
+ method @Deprecated public static androidx.core.widget.ScrollerCompat! create(android.content.Context!);
+ method @Deprecated public static androidx.core.widget.ScrollerCompat! create(android.content.Context!, android.view.animation.Interpolator!);
+ method @Deprecated public void fling(int, int, int, int, int, int, int, int);
+ method @Deprecated public void fling(int, int, int, int, int, int, int, int, int, int);
+ method @Deprecated public float getCurrVelocity();
+ method @Deprecated public int getCurrX();
+ method @Deprecated public int getCurrY();
+ method @Deprecated public int getFinalX();
+ method @Deprecated public int getFinalY();
+ method @Deprecated public boolean isFinished();
+ method @Deprecated public boolean isOverScrolled();
+ method @Deprecated public void notifyHorizontalEdgeReached(int, int, int);
+ method @Deprecated public void notifyVerticalEdgeReached(int, int, int);
+ method @Deprecated public boolean springBack(int, int, int, int, int, int);
+ method @Deprecated public void startScroll(int, int, int, int);
+ method @Deprecated public void startScroll(int, int, int, int, int);
+ }
+
+ public final class TextViewCompat {
+ method public static int getAutoSizeMaxTextSize(android.widget.TextView);
+ method public static int getAutoSizeMinTextSize(android.widget.TextView);
+ method public static int getAutoSizeStepGranularity(android.widget.TextView);
+ method public static int[] getAutoSizeTextAvailableSizes(android.widget.TextView);
+ method public static int getAutoSizeTextType(android.widget.TextView);
+ method public static android.content.res.ColorStateList? getCompoundDrawableTintList(android.widget.TextView);
+ method public static android.graphics.PorterDuff.Mode? getCompoundDrawableTintMode(android.widget.TextView);
+ method public static android.graphics.drawable.Drawable![] getCompoundDrawablesRelative(android.widget.TextView);
+ method public static int getFirstBaselineToTopHeight(android.widget.TextView);
+ method public static int getLastBaselineToBottomHeight(android.widget.TextView);
+ method public static int getMaxLines(android.widget.TextView);
+ method public static int getMinLines(android.widget.TextView);
+ method public static androidx.core.text.PrecomputedTextCompat.Params getTextMetricsParams(android.widget.TextView);
+ method public static void setAutoSizeTextTypeUniformWithConfiguration(android.widget.TextView, int, int, int, int) throws java.lang.IllegalArgumentException;
+ method public static void setAutoSizeTextTypeUniformWithPresetSizes(android.widget.TextView, int[], int) throws java.lang.IllegalArgumentException;
+ method public static void setAutoSizeTextTypeWithDefaults(android.widget.TextView, int);
+ method public static void setCompoundDrawableTintList(android.widget.TextView, android.content.res.ColorStateList?);
+ method public static void setCompoundDrawableTintMode(android.widget.TextView, android.graphics.PorterDuff.Mode?);
+ method public static void setCompoundDrawablesRelative(android.widget.TextView, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?);
+ method public static void setCompoundDrawablesRelativeWithIntrinsicBounds(android.widget.TextView, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?);
+ method public static void setCompoundDrawablesRelativeWithIntrinsicBounds(android.widget.TextView, @DrawableRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int);
+ method public static void setCustomSelectionActionModeCallback(android.widget.TextView, android.view.ActionMode.Callback);
+ method public static void setFirstBaselineToTopHeight(android.widget.TextView, @IntRange(from=0) @Px int);
+ method public static void setLastBaselineToBottomHeight(android.widget.TextView, @IntRange(from=0) @Px int);
+ method public static void setLineHeight(android.widget.TextView, @IntRange(from=0) @Px int);
+ method public static void setLineHeight(android.widget.TextView, int, @FloatRange(from=0) float);
+ method public static void setPrecomputedText(android.widget.TextView, androidx.core.text.PrecomputedTextCompat);
+ method public static void setTextAppearance(android.widget.TextView, @StyleRes int);
+ method public static void setTextMetricsParams(android.widget.TextView, androidx.core.text.PrecomputedTextCompat.Params);
+ field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
+ field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
+ }
+
+ public interface TintableCompoundButton {
+ method public android.content.res.ColorStateList? getSupportButtonTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportButtonTintMode();
+ method public void setSupportButtonTintList(android.content.res.ColorStateList?);
+ method public void setSupportButtonTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public interface TintableCompoundDrawablesView {
+ method public android.content.res.ColorStateList? getSupportCompoundDrawablesTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportCompoundDrawablesTintMode();
+ method public void setSupportCompoundDrawablesTintList(android.content.res.ColorStateList?);
+ method public void setSupportCompoundDrawablesTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+}
+
diff --git a/core/core/api/res-1.13.0-beta01.txt b/core/core/api/res-1.13.0-beta01.txt
new file mode 100644
index 0000000..dd913d3
--- /dev/null
+++ b/core/core/api/res-1.13.0-beta01.txt
@@ -0,0 +1,21 @@
+attr alpha
+attr font
+attr fontProviderAuthority
+attr fontProviderCerts
+attr fontProviderFetchStrategy
+attr fontProviderFetchTimeout
+attr fontProviderPackage
+attr fontProviderQuery
+attr fontProviderSystemFontFamily
+attr fontStyle
+attr fontVariationSettings
+attr fontWeight
+attr lStar
+attr queryPatterns
+attr shortcutMatchRequired
+attr ttcIndex
+style TextAppearance_Compat_Notification
+style TextAppearance_Compat_Notification_Info
+style TextAppearance_Compat_Notification_Line2
+style TextAppearance_Compat_Notification_Time
+style TextAppearance_Compat_Notification_Title
diff --git a/core/core/api/restricted_1.13.0-beta01.txt b/core/core/api/restricted_1.13.0-beta01.txt
new file mode 100644
index 0000000..cf1f7da
--- /dev/null
+++ b/core/core/api/restricted_1.13.0-beta01.txt
@@ -0,0 +1,4803 @@
+// Signature format: 4.0
+package android.support.v4.os {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ResultReceiver implements android.os.Parcelable {
+ ctor public ResultReceiver(android.os.Handler!);
+ method public int describeContents();
+ method protected void onReceiveResult(int, android.os.Bundle!);
+ method public void send(int, android.os.Bundle!);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.os.ResultReceiver!>! CREATOR;
+ }
+
+}
+
+package androidx.core.accessibilityservice {
+
+ public final class AccessibilityServiceInfoCompat {
+ method public static String capabilityToString(int);
+ method public static String feedbackTypeToString(int);
+ method public static String? flagToString(int);
+ method public static int getCapabilities(android.accessibilityservice.AccessibilityServiceInfo);
+ method public static String? loadDescription(android.accessibilityservice.AccessibilityServiceInfo, android.content.pm.PackageManager);
+ field public static final int CAPABILITY_CAN_FILTER_KEY_EVENTS = 8; // 0x8
+ field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
+ field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
+ field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1
+ field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
+ field public static final int FEEDBACK_BRAILLE = 32; // 0x20
+ field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
+ field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
+ field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
+ field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
+ field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
+ }
+
+}
+
+package androidx.core.app {
+
+ public class ActivityCompat extends androidx.core.content.ContextCompat {
+ ctor protected ActivityCompat();
+ method public static void finishAffinity(android.app.Activity);
+ method public static void finishAfterTransition(android.app.Activity);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.app.ActivityCompat.PermissionCompatDelegate? getPermissionCompatDelegate();
+ method public static android.net.Uri? getReferrer(android.app.Activity);
+ method @Deprecated public static boolean invalidateOptionsMenu(android.app.Activity!);
+ method public static boolean isLaunchedFromBubble(android.app.Activity);
+ method public static void postponeEnterTransition(android.app.Activity);
+ method public static void recreate(android.app.Activity);
+ method public static androidx.core.view.DragAndDropPermissionsCompat? requestDragAndDropPermissions(android.app.Activity, android.view.DragEvent);
+ method public static void requestPermissions(android.app.Activity, String![], @IntRange(from=0) int);
+ method public static <T extends android.view.View> T requireViewById(android.app.Activity, @IdRes int);
+ method public static void setEnterSharedElementCallback(android.app.Activity, androidx.core.app.SharedElementCallback?);
+ method public static void setExitSharedElementCallback(android.app.Activity, androidx.core.app.SharedElementCallback?);
+ method public static void setLocusContext(android.app.Activity, androidx.core.content.LocusIdCompat?, android.os.Bundle?);
+ method public static void setPermissionCompatDelegate(androidx.core.app.ActivityCompat.PermissionCompatDelegate?);
+ method public static boolean shouldShowRequestPermissionRationale(android.app.Activity, String);
+ method public static void startActivityForResult(android.app.Activity, android.content.Intent, int, android.os.Bundle?);
+ method public static void startIntentSenderForResult(android.app.Activity, android.content.IntentSender, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+ method public static void startPostponedEnterTransition(android.app.Activity);
+ }
+
+ public static interface ActivityCompat.OnRequestPermissionsResultCallback {
+ method public void onRequestPermissionsResult(int, String![], int[]);
+ }
+
+ public static interface ActivityCompat.PermissionCompatDelegate {
+ method public boolean onActivityResult(android.app.Activity, @IntRange(from=0) int, int, android.content.Intent?);
+ method public boolean requestPermissions(android.app.Activity, String![], @IntRange(from=0) int);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface ActivityCompat.RequestPermissionsRequestCodeValidator {
+ method public void validateRequestPermissionsRequestCode(int);
+ }
+
+ public final class ActivityManagerCompat {
+ method public static boolean isLowRamDevice(android.app.ActivityManager);
+ }
+
+ public class ActivityOptionsCompat {
+ ctor protected ActivityOptionsCompat();
+ method public android.graphics.Rect? getLaunchBounds();
+ method public static androidx.core.app.ActivityOptionsCompat makeBasic();
+ method public static androidx.core.app.ActivityOptionsCompat makeClipRevealAnimation(android.view.View, int, int, int, int);
+ method public static androidx.core.app.ActivityOptionsCompat makeCustomAnimation(android.content.Context, int, int);
+ method public static androidx.core.app.ActivityOptionsCompat makeScaleUpAnimation(android.view.View, int, int, int, int);
+ method public static androidx.core.app.ActivityOptionsCompat makeSceneTransitionAnimation(android.app.Activity, android.view.View, String);
+ method public static androidx.core.app.ActivityOptionsCompat makeSceneTransitionAnimation(android.app.Activity, androidx.core.util.Pair<android.view.View!,java.lang.String!>!...?);
+ method public static androidx.core.app.ActivityOptionsCompat makeTaskLaunchBehind();
+ method public static androidx.core.app.ActivityOptionsCompat makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
+ method public void requestUsageTimeReport(android.app.PendingIntent);
+ method public androidx.core.app.ActivityOptionsCompat setLaunchBounds(android.graphics.Rect?);
+ method public androidx.core.app.ActivityOptionsCompat setShareIdentityEnabled(boolean);
+ method public android.os.Bundle? toBundle();
+ method public void update(androidx.core.app.ActivityOptionsCompat);
+ field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
+ field public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
+ }
+
+ public final class AlarmManagerCompat {
+ method public static boolean canScheduleExactAlarms(android.app.AlarmManager);
+ method public static void setAlarmClock(android.app.AlarmManager, long, android.app.PendingIntent, android.app.PendingIntent);
+ method public static void setAndAllowWhileIdle(android.app.AlarmManager, int, long, android.app.PendingIntent);
+ method public static void setExact(android.app.AlarmManager, int, long, android.app.PendingIntent);
+ method public static void setExactAndAllowWhileIdle(android.app.AlarmManager, int, long, android.app.PendingIntent);
+ }
+
+ @RequiresApi(28) public class AppComponentFactory extends android.app.AppComponentFactory {
+ ctor public AppComponentFactory();
+ method public final android.app.Activity instantiateActivity(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.app.Activity instantiateActivityCompat(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.app.Application instantiateApplication(ClassLoader, String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.app.Application instantiateApplicationCompat(ClassLoader, String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.content.ContentProvider instantiateProvider(ClassLoader, String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.content.ContentProvider instantiateProviderCompat(ClassLoader, String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.content.BroadcastReceiver instantiateReceiver(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.content.BroadcastReceiver instantiateReceiverCompat(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.app.Service instantiateService(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.app.Service instantiateServiceCompat(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ }
+
+ public class AppLaunchChecker {
+ ctor @Deprecated public AppLaunchChecker();
+ method public static boolean hasStartedFromLauncher(android.content.Context);
+ method public static void onActivityCreate(android.app.Activity);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class AppLocalesStorageHelper {
+ method public static void persistLocales(android.content.Context, String);
+ method public static String readLocales(android.content.Context);
+ }
+
+ public final class AppOpsManagerCompat {
+ method public static int checkOrNoteProxyOp(android.content.Context, int, String, String);
+ method public static int noteOp(android.content.Context, String, int, String);
+ method public static int noteOpNoThrow(android.content.Context, String, int, String);
+ method public static int noteProxyOp(android.content.Context, String, String);
+ method public static int noteProxyOpNoThrow(android.content.Context, String, String);
+ method public static String? permissionToOp(String);
+ field public static final int MODE_ALLOWED = 0; // 0x0
+ field public static final int MODE_DEFAULT = 3; // 0x3
+ field public static final int MODE_ERRORED = 2; // 0x2
+ field public static final int MODE_IGNORED = 1; // 0x1
+ }
+
+ @Deprecated public final class BundleCompat {
+ method @Deprecated public static android.os.IBinder? getBinder(android.os.Bundle, String?);
+ method @Deprecated public static void putBinder(android.os.Bundle, String?, android.os.IBinder?);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component androidx.lifecycle.LifecycleOwner {
+ ctor public ComponentActivity();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T extends androidx.core.app.ComponentActivity.ExtraData> T? getExtraData(Class<T> extraDataClass);
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void putExtraData(androidx.core.app.ComponentActivity.ExtraData extraData);
+ method protected final boolean shouldDumpInternalState(String[]? args);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean superDispatchKeyEvent(android.view.KeyEvent event);
+ property public androidx.lifecycle.Lifecycle lifecycle;
+ }
+
+ @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class ComponentActivity.ExtraData {
+ ctor @Deprecated public ComponentActivity.ExtraData();
+ }
+
+ @RequiresApi(api=28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CoreComponentFactory extends android.app.AppComponentFactory {
+ ctor public CoreComponentFactory();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface CoreComponentFactory.CompatWrapped {
+ method public Object! getWrapper();
+ }
+
+ public class DialogCompat {
+ method public static android.view.View requireViewById(android.app.Dialog, int);
+ }
+
+ public class FrameMetricsAggregator {
+ ctor public FrameMetricsAggregator();
+ ctor public FrameMetricsAggregator(@androidx.core.app.FrameMetricsAggregator.MetricType int);
+ method public void add(android.app.Activity);
+ method public android.util.SparseIntArray![]? getMetrics();
+ method public android.util.SparseIntArray![]? remove(android.app.Activity);
+ method public android.util.SparseIntArray![]? reset();
+ method public android.util.SparseIntArray![]? stop();
+ field public static final int ANIMATION_DURATION = 256; // 0x100
+ field public static final int ANIMATION_INDEX = 8; // 0x8
+ field public static final int COMMAND_DURATION = 32; // 0x20
+ field public static final int COMMAND_INDEX = 5; // 0x5
+ field public static final int DELAY_DURATION = 128; // 0x80
+ field public static final int DELAY_INDEX = 7; // 0x7
+ field public static final int DRAW_DURATION = 8; // 0x8
+ field public static final int DRAW_INDEX = 3; // 0x3
+ field public static final int EVERY_DURATION = 511; // 0x1ff
+ field public static final int INPUT_DURATION = 2; // 0x2
+ field public static final int INPUT_INDEX = 1; // 0x1
+ field public static final int LAYOUT_MEASURE_DURATION = 4; // 0x4
+ field public static final int LAYOUT_MEASURE_INDEX = 2; // 0x2
+ field public static final int SWAP_DURATION = 64; // 0x40
+ field public static final int SWAP_INDEX = 6; // 0x6
+ field public static final int SYNC_DURATION = 16; // 0x10
+ field public static final int SYNC_INDEX = 4; // 0x4
+ field public static final int TOTAL_DURATION = 1; // 0x1
+ field public static final int TOTAL_INDEX = 0; // 0x0
+ }
+
+ @IntDef(flag=true, value={androidx.core.app.FrameMetricsAggregator.TOTAL_DURATION, androidx.core.app.FrameMetricsAggregator.INPUT_DURATION, androidx.core.app.FrameMetricsAggregator.LAYOUT_MEASURE_DURATION, androidx.core.app.FrameMetricsAggregator.DRAW_DURATION, androidx.core.app.FrameMetricsAggregator.SYNC_DURATION, androidx.core.app.FrameMetricsAggregator.COMMAND_DURATION, androidx.core.app.FrameMetricsAggregator.SWAP_DURATION, androidx.core.app.FrameMetricsAggregator.DELAY_DURATION, androidx.core.app.FrameMetricsAggregator.ANIMATION_DURATION, androidx.core.app.FrameMetricsAggregator.EVERY_DURATION}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface FrameMetricsAggregator.MetricType {
+ }
+
+ public final class GrammaticalInflectionManagerCompat {
+ method @AnyThread public static int getApplicationGrammaticalGender(android.content.Context);
+ method @AnyThread public static void setRequestedApplicationGrammaticalGender(android.content.Context, int);
+ field public static final int GRAMMATICAL_GENDER_FEMININE = 2; // 0x2
+ field public static final int GRAMMATICAL_GENDER_MASCULINE = 3; // 0x3
+ field public static final int GRAMMATICAL_GENDER_NEUTRAL = 1; // 0x1
+ field public static final int GRAMMATICAL_GENDER_NOT_SPECIFIED = 0; // 0x0
+ }
+
+ @Deprecated public abstract class JobIntentService extends android.app.Service {
+ ctor @Deprecated public JobIntentService();
+ method @Deprecated public static void enqueueWork(android.content.Context, android.content.ComponentName, int, android.content.Intent);
+ method @Deprecated public static void enqueueWork(android.content.Context, Class<?>, int, android.content.Intent);
+ method @Deprecated public boolean isStopped();
+ method @Deprecated public android.os.IBinder! onBind(android.content.Intent);
+ method @Deprecated protected abstract void onHandleWork(android.content.Intent);
+ method @Deprecated public boolean onStopCurrentWork();
+ method @Deprecated public void setInterruptIfStopped(boolean);
+ }
+
+ public final class LocaleManagerCompat {
+ method @AnyThread public static androidx.core.os.LocaleListCompat getApplicationLocales(android.content.Context);
+ method @AnyThread public static androidx.core.os.LocaleListCompat getSystemLocales(android.content.Context);
+ }
+
+ public final class MultiWindowModeChangedInfo {
+ ctor public MultiWindowModeChangedInfo(boolean isInMultiWindowMode);
+ ctor @RequiresApi(26) public MultiWindowModeChangedInfo(boolean isInMultiWindowMode, android.content.res.Configuration newConfig);
+ method @RequiresApi(26) public android.content.res.Configuration getNewConfig();
+ method public boolean isInMultiWindowMode();
+ property public final boolean isInMultiWindowMode;
+ property @RequiresApi(26) public final android.content.res.Configuration newConfig;
+ }
+
+ public final class NavUtils {
+ method public static android.content.Intent? getParentActivityIntent(android.app.Activity);
+ method public static android.content.Intent? getParentActivityIntent(android.content.Context, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static android.content.Intent? getParentActivityIntent(android.content.Context, Class<?>) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static String? getParentActivityName(android.app.Activity);
+ method public static String? getParentActivityName(android.content.Context, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static void navigateUpFromSameTask(android.app.Activity);
+ method public static void navigateUpTo(android.app.Activity, android.content.Intent);
+ method public static boolean shouldUpRecreateTask(android.app.Activity, android.content.Intent);
+ field public static final String PARENT_ACTIVITY = "android.support.PARENT_ACTIVITY";
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface NotificationBuilderWithBuilderAccessor {
+ method public android.app.Notification.Builder! getBuilder();
+ }
+
+ public class NotificationChannelCompat {
+ method public boolean canBubble();
+ method public boolean canBypassDnd();
+ method public boolean canShowBadge();
+ method public android.media.AudioAttributes? getAudioAttributes();
+ method public String? getConversationId();
+ method public String? getDescription();
+ method public String? getGroup();
+ method public String getId();
+ method public int getImportance();
+ method public int getLightColor();
+ method @androidx.core.app.NotificationCompat.NotificationVisibility public int getLockscreenVisibility();
+ method public CharSequence? getName();
+ method public String? getParentChannelId();
+ method public android.net.Uri? getSound();
+ method public long[]? getVibrationPattern();
+ method public boolean isImportantConversation();
+ method public boolean shouldShowLights();
+ method public boolean shouldVibrate();
+ method public androidx.core.app.NotificationChannelCompat.Builder toBuilder();
+ field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
+ }
+
+ public static class NotificationChannelCompat.Builder {
+ ctor public NotificationChannelCompat.Builder(String, int);
+ method public androidx.core.app.NotificationChannelCompat build();
+ method public androidx.core.app.NotificationChannelCompat.Builder setConversationId(String, String);
+ method public androidx.core.app.NotificationChannelCompat.Builder setDescription(String?);
+ method public androidx.core.app.NotificationChannelCompat.Builder setGroup(String?);
+ method public androidx.core.app.NotificationChannelCompat.Builder setImportance(int);
+ method public androidx.core.app.NotificationChannelCompat.Builder setLightColor(int);
+ method public androidx.core.app.NotificationChannelCompat.Builder setLightsEnabled(boolean);
+ method public androidx.core.app.NotificationChannelCompat.Builder setName(CharSequence?);
+ method public androidx.core.app.NotificationChannelCompat.Builder setShowBadge(boolean);
+ method public androidx.core.app.NotificationChannelCompat.Builder setSound(android.net.Uri?, android.media.AudioAttributes?);
+ method public androidx.core.app.NotificationChannelCompat.Builder setVibrationEnabled(boolean);
+ method public androidx.core.app.NotificationChannelCompat.Builder setVibrationPattern(long[]?);
+ }
+
+ public class NotificationChannelGroupCompat {
+ method public java.util.List<androidx.core.app.NotificationChannelCompat!> getChannels();
+ method public String? getDescription();
+ method public String getId();
+ method public CharSequence? getName();
+ method public boolean isBlocked();
+ method public androidx.core.app.NotificationChannelGroupCompat.Builder toBuilder();
+ }
+
+ public static class NotificationChannelGroupCompat.Builder {
+ ctor public NotificationChannelGroupCompat.Builder(String);
+ method public androidx.core.app.NotificationChannelGroupCompat build();
+ method public androidx.core.app.NotificationChannelGroupCompat.Builder setDescription(String?);
+ method public androidx.core.app.NotificationChannelGroupCompat.Builder setName(CharSequence?);
+ }
+
+ public class NotificationCompat {
+ ctor @Deprecated public NotificationCompat();
+ method public static androidx.core.app.NotificationCompat.Action? getAction(android.app.Notification, int);
+ method public static int getActionCount(android.app.Notification);
+ method public static boolean getAllowSystemGeneratedContextualActions(android.app.Notification);
+ method public static boolean getAutoCancel(android.app.Notification);
+ method public static int getBadgeIconType(android.app.Notification);
+ method public static androidx.core.app.NotificationCompat.BubbleMetadata? getBubbleMetadata(android.app.Notification);
+ method public static String? getCategory(android.app.Notification);
+ method public static String? getChannelId(android.app.Notification);
+ method public static int getColor(android.app.Notification);
+ method public static CharSequence? getContentInfo(android.app.Notification);
+ method public static CharSequence? getContentText(android.app.Notification);
+ method public static CharSequence? getContentTitle(android.app.Notification);
+ method public static android.os.Bundle? getExtras(android.app.Notification);
+ method public static String? getGroup(android.app.Notification);
+ method @androidx.core.app.NotificationCompat.GroupAlertBehavior public static int getGroupAlertBehavior(android.app.Notification);
+ method @RequiresApi(21) public static java.util.List<androidx.core.app.NotificationCompat.Action!> getInvisibleActions(android.app.Notification);
+ method public static boolean getLocalOnly(android.app.Notification);
+ method public static androidx.core.content.LocusIdCompat? getLocusId(android.app.Notification);
+ method public static boolean getOngoing(android.app.Notification);
+ method public static boolean getOnlyAlertOnce(android.app.Notification);
+ method public static java.util.List<androidx.core.app.Person!> getPeople(android.app.Notification);
+ method public static android.app.Notification? getPublicVersion(android.app.Notification);
+ method public static CharSequence? getSettingsText(android.app.Notification);
+ method public static String? getShortcutId(android.app.Notification);
+ method public static boolean getShowWhen(android.app.Notification);
+ method public static String? getSortKey(android.app.Notification);
+ method public static CharSequence? getSubText(android.app.Notification);
+ method public static long getTimeoutAfter(android.app.Notification);
+ method public static boolean getUsesChronometer(android.app.Notification);
+ method @androidx.core.app.NotificationCompat.NotificationVisibility public static int getVisibility(android.app.Notification);
+ method public static boolean isGroupSummary(android.app.Notification);
+ method public static android.graphics.Bitmap? reduceLargeIconSize(android.content.Context, android.graphics.Bitmap?);
+ field public static final int BADGE_ICON_LARGE = 2; // 0x2
+ field public static final int BADGE_ICON_NONE = 0; // 0x0
+ field public static final int BADGE_ICON_SMALL = 1; // 0x1
+ field public static final String CATEGORY_ALARM = "alarm";
+ field public static final String CATEGORY_CALL = "call";
+ field public static final String CATEGORY_EMAIL = "email";
+ field public static final String CATEGORY_ERROR = "err";
+ field public static final String CATEGORY_EVENT = "event";
+ field public static final String CATEGORY_LOCATION_SHARING = "location_sharing";
+ field public static final String CATEGORY_MESSAGE = "msg";
+ field public static final String CATEGORY_MISSED_CALL = "missed_call";
+ field public static final String CATEGORY_NAVIGATION = "navigation";
+ field public static final String CATEGORY_PROGRESS = "progress";
+ field public static final String CATEGORY_PROMO = "promo";
+ field public static final String CATEGORY_RECOMMENDATION = "recommendation";
+ field public static final String CATEGORY_REMINDER = "reminder";
+ field public static final String CATEGORY_SERVICE = "service";
+ field public static final String CATEGORY_SOCIAL = "social";
+ field public static final String CATEGORY_STATUS = "status";
+ field public static final String CATEGORY_STOPWATCH = "stopwatch";
+ field public static final String CATEGORY_SYSTEM = "sys";
+ field public static final String CATEGORY_TRANSPORT = "transport";
+ field public static final String CATEGORY_WORKOUT = "workout";
+ field @ColorInt public static final int COLOR_DEFAULT = 0; // 0x0
+ field public static final int DEFAULT_ALL = -1; // 0xffffffff
+ field public static final int DEFAULT_LIGHTS = 4; // 0x4
+ field public static final int DEFAULT_SOUND = 1; // 0x1
+ field public static final int DEFAULT_VIBRATE = 2; // 0x2
+ field public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
+ field public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
+ field public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
+ field public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
+ field public static final String EXTRA_BIG_TEXT = "android.bigText";
+ field public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo";
+ field public static final String EXTRA_CALL_PERSON = "android.callPerson";
+ field public static final String EXTRA_CALL_PERSON_COMPAT = "android.callPersonCompat";
+ field public static final String EXTRA_CALL_TYPE = "android.callType";
+ field public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
+ field public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
+ field public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+ field public static final String EXTRA_COLORIZED = "android.colorized";
+ field public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
+ field public static final String EXTRA_COMPAT_TEMPLATE = "androidx.core.app.extra.COMPAT_TEMPLATE";
+ field public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+ field public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
+ field public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
+ field public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
+ field public static final String EXTRA_HIDDEN_CONVERSATION_TITLE = "android.hiddenConversationTitle";
+ field public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
+ field public static final String EXTRA_INFO_TEXT = "android.infoText";
+ field public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
+ field public static final String EXTRA_LARGE_ICON = "android.largeIcon";
+ field public static final String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
+ field public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
+ field public static final String EXTRA_MESSAGES = "android.messages";
+ field public static final String EXTRA_MESSAGING_STYLE_USER = "android.messagingStyleUser";
+ field public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
+ field public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
+ field @Deprecated public static final String EXTRA_PEOPLE = "android.people";
+ field public static final String EXTRA_PEOPLE_LIST = "android.people.list";
+ field public static final String EXTRA_PICTURE = "android.picture";
+ field public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = "android.pictureContentDescription";
+ field public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
+ field public static final String EXTRA_PROGRESS = "android.progress";
+ field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
+ field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
+ field public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
+ field public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
+ field public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = "android.showBigPictureWhenCollapsed";
+ field public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
+ field public static final String EXTRA_SHOW_WHEN = "android.showWhen";
+ field public static final String EXTRA_SMALL_ICON = "android.icon";
+ field public static final String EXTRA_SUB_TEXT = "android.subText";
+ field public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
+ field public static final String EXTRA_TEMPLATE = "android.template";
+ field public static final String EXTRA_TEXT = "android.text";
+ field public static final String EXTRA_TEXT_LINES = "android.textLines";
+ field public static final String EXTRA_TITLE = "android.title";
+ field public static final String EXTRA_TITLE_BIG = "android.title.big";
+ field public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
+ field public static final String EXTRA_VERIFICATION_ICON_COMPAT = "android.verificationIconCompat";
+ field public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
+ field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
+ field public static final int FLAG_BUBBLE = 4096; // 0x1000
+ field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
+ field public static final int FLAG_GROUP_SUMMARY = 512; // 0x200
+ field @Deprecated public static final int FLAG_HIGH_PRIORITY = 128; // 0x80
+ field public static final int FLAG_INSISTENT = 4; // 0x4
+ field public static final int FLAG_LOCAL_ONLY = 256; // 0x100
+ field public static final int FLAG_NO_CLEAR = 32; // 0x20
+ field public static final int FLAG_ONGOING_EVENT = 2; // 0x2
+ field public static final int FLAG_ONLY_ALERT_ONCE = 8; // 0x8
+ field public static final int FLAG_SHOW_LIGHTS = 1; // 0x1
+ field public static final int FOREGROUND_SERVICE_DEFAULT = 0; // 0x0
+ field public static final int FOREGROUND_SERVICE_DEFERRED = 2; // 0x2
+ field public static final int FOREGROUND_SERVICE_IMMEDIATE = 1; // 0x1
+ field public static final int GROUP_ALERT_ALL = 0; // 0x0
+ field public static final int GROUP_ALERT_CHILDREN = 2; // 0x2
+ field public static final int GROUP_ALERT_SUMMARY = 1; // 0x1
+ field public static final String GROUP_KEY_SILENT = "silent";
+ field public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES = "android.intent.category.NOTIFICATION_PREFERENCES";
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MAX_ACTION_BUTTONS = 3; // 0x3
+ field public static final int PRIORITY_DEFAULT = 0; // 0x0
+ field public static final int PRIORITY_HIGH = 1; // 0x1
+ field public static final int PRIORITY_LOW = -1; // 0xffffffff
+ field public static final int PRIORITY_MAX = 2; // 0x2
+ field public static final int PRIORITY_MIN = -2; // 0xfffffffe
+ field public static final int STREAM_DEFAULT = -1; // 0xffffffff
+ field public static final int VISIBILITY_PRIVATE = 0; // 0x0
+ field public static final int VISIBILITY_PUBLIC = 1; // 0x1
+ field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
+ }
+
+ public static class NotificationCompat.Action {
+ ctor public NotificationCompat.Action(androidx.core.graphics.drawable.IconCompat?, CharSequence?, android.app.PendingIntent?);
+ ctor public NotificationCompat.Action(int, CharSequence?, android.app.PendingIntent?);
+ method public android.app.PendingIntent? getActionIntent();
+ method public boolean getAllowGeneratedReplies();
+ method public androidx.core.app.RemoteInput![]? getDataOnlyRemoteInputs();
+ method public android.os.Bundle getExtras();
+ method @Deprecated public int getIcon();
+ method public androidx.core.graphics.drawable.IconCompat? getIconCompat();
+ method public androidx.core.app.RemoteInput![]? getRemoteInputs();
+ method @androidx.core.app.NotificationCompat.Action.SemanticAction public int getSemanticAction();
+ method public boolean getShowsUserInterface();
+ method public CharSequence? getTitle();
+ method public boolean isAuthenticationRequired();
+ method public boolean isContextual();
+ field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
+ field public static final int SEMANTIC_ACTION_CALL = 10; // 0xa
+ field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
+ field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
+ field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3
+ field public static final int SEMANTIC_ACTION_MUTE = 6; // 0x6
+ field public static final int SEMANTIC_ACTION_NONE = 0; // 0x0
+ field public static final int SEMANTIC_ACTION_REPLY = 1; // 0x1
+ field public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; // 0x9
+ field public static final int SEMANTIC_ACTION_THUMBS_UP = 8; // 0x8
+ field public static final int SEMANTIC_ACTION_UNMUTE = 7; // 0x7
+ field public android.app.PendingIntent? actionIntent;
+ field @Deprecated public int icon;
+ field public CharSequence! title;
+ }
+
+ public static final class NotificationCompat.Action.Builder {
+ ctor public NotificationCompat.Action.Builder(androidx.core.app.NotificationCompat.Action);
+ ctor public NotificationCompat.Action.Builder(androidx.core.graphics.drawable.IconCompat?, CharSequence?, android.app.PendingIntent?);
+ ctor public NotificationCompat.Action.Builder(int, CharSequence?, android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.Action.Builder addExtras(android.os.Bundle?);
+ method public androidx.core.app.NotificationCompat.Action.Builder addRemoteInput(androidx.core.app.RemoteInput?);
+ method public androidx.core.app.NotificationCompat.Action build();
+ method public androidx.core.app.NotificationCompat.Action.Builder extend(androidx.core.app.NotificationCompat.Action.Extender);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.app.NotificationCompat.Action.Builder fromAndroidAction(android.app.Notification.Action);
+ method public android.os.Bundle getExtras();
+ method public androidx.core.app.NotificationCompat.Action.Builder setAllowGeneratedReplies(boolean);
+ method public androidx.core.app.NotificationCompat.Action.Builder setAuthenticationRequired(boolean);
+ method public androidx.core.app.NotificationCompat.Action.Builder setContextual(boolean);
+ method public androidx.core.app.NotificationCompat.Action.Builder setSemanticAction(@androidx.core.app.NotificationCompat.Action.SemanticAction int);
+ method public androidx.core.app.NotificationCompat.Action.Builder setShowsUserInterface(boolean);
+ }
+
+ public static interface NotificationCompat.Action.Extender {
+ method public androidx.core.app.NotificationCompat.Action.Builder extend(androidx.core.app.NotificationCompat.Action.Builder);
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_NONE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_REPLY, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_UNREAD, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_DELETE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_ARCHIVE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_MUTE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_UNMUTE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_THUMBS_UP, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_THUMBS_DOWN, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_CALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.Action.SemanticAction {
+ }
+
+ public static final class NotificationCompat.Action.WearableExtender implements androidx.core.app.NotificationCompat.Action.Extender {
+ ctor public NotificationCompat.Action.WearableExtender();
+ ctor public NotificationCompat.Action.WearableExtender(androidx.core.app.NotificationCompat.Action);
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender clone();
+ method public androidx.core.app.NotificationCompat.Action.Builder extend(androidx.core.app.NotificationCompat.Action.Builder);
+ method @Deprecated public CharSequence? getCancelLabel();
+ method @Deprecated public CharSequence? getConfirmLabel();
+ method public boolean getHintDisplayActionInline();
+ method public boolean getHintLaunchesActivity();
+ method @Deprecated public CharSequence? getInProgressLabel();
+ method public boolean isAvailableOffline();
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender setAvailableOffline(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.Action.WearableExtender setCancelLabel(CharSequence?);
+ method @Deprecated public androidx.core.app.NotificationCompat.Action.WearableExtender setConfirmLabel(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender setHintDisplayActionInline(boolean);
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender setHintLaunchesActivity(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.Action.WearableExtender setInProgressLabel(CharSequence?);
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.BADGE_ICON_NONE, androidx.core.app.NotificationCompat.BADGE_ICON_SMALL, androidx.core.app.NotificationCompat.BADGE_ICON_LARGE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.BadgeIconType {
+ }
+
+ public static class NotificationCompat.BigPictureStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.BigPictureStyle();
+ ctor public NotificationCompat.BigPictureStyle(androidx.core.app.NotificationCompat.Builder?);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle bigLargeIcon(android.graphics.Bitmap?);
+ method @RequiresApi(23) public androidx.core.app.NotificationCompat.BigPictureStyle bigLargeIcon(android.graphics.drawable.Icon?);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle bigPicture(android.graphics.Bitmap?);
+ method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle bigPicture(android.graphics.drawable.Icon?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.graphics.drawable.IconCompat? getPictureIcon(android.os.Bundle?);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle setBigContentTitle(CharSequence?);
+ method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle setContentDescription(CharSequence?);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle setSummaryText(CharSequence?);
+ method @RequiresApi(31) public androidx.core.app.NotificationCompat.BigPictureStyle showBigPictureWhenCollapsed(boolean);
+ }
+
+ public static class NotificationCompat.BigTextStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.BigTextStyle();
+ ctor public NotificationCompat.BigTextStyle(androidx.core.app.NotificationCompat.Builder?);
+ method public androidx.core.app.NotificationCompat.BigTextStyle bigText(CharSequence?);
+ method public androidx.core.app.NotificationCompat.BigTextStyle setBigContentTitle(CharSequence?);
+ method public androidx.core.app.NotificationCompat.BigTextStyle setSummaryText(CharSequence?);
+ }
+
+ public static final class NotificationCompat.BubbleMetadata {
+ method public static androidx.core.app.NotificationCompat.BubbleMetadata? fromPlatform(android.app.Notification.BubbleMetadata?);
+ method public boolean getAutoExpandBubble();
+ method public android.app.PendingIntent? getDeleteIntent();
+ method @Dimension(unit=androidx.annotation.Dimension.DP) public int getDesiredHeight();
+ method @DimenRes public int getDesiredHeightResId();
+ method public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method public android.app.PendingIntent? getIntent();
+ method public String? getShortcutId();
+ method public boolean isNotificationSuppressed();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setFlags(int);
+ method public static android.app.Notification.BubbleMetadata? toPlatform(androidx.core.app.NotificationCompat.BubbleMetadata?);
+ }
+
+ public static final class NotificationCompat.BubbleMetadata.Builder {
+ ctor @Deprecated public NotificationCompat.BubbleMetadata.Builder();
+ ctor public NotificationCompat.BubbleMetadata.Builder(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat);
+ ctor @RequiresApi(30) public NotificationCompat.BubbleMetadata.Builder(String);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata build();
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setAutoExpandBubble(boolean);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setDeleteIntent(android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setDesiredHeight(@Dimension(unit=androidx.annotation.Dimension.DP) int);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setIcon(androidx.core.graphics.drawable.IconCompat);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setIntent(android.app.PendingIntent);
+ method public androidx.core.app.NotificationCompat.BubbleMetadata.Builder setSuppressNotification(boolean);
+ }
+
+ public static class NotificationCompat.Builder {
+ ctor @Deprecated public NotificationCompat.Builder(android.content.Context);
+ ctor public NotificationCompat.Builder(android.content.Context, android.app.Notification);
+ ctor public NotificationCompat.Builder(android.content.Context, String);
+ method public androidx.core.app.NotificationCompat.Builder addAction(androidx.core.app.NotificationCompat.Action?);
+ method public androidx.core.app.NotificationCompat.Builder addAction(int, CharSequence?, android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.Builder addExtras(android.os.Bundle?);
+ method @RequiresApi(21) public androidx.core.app.NotificationCompat.Builder addInvisibleAction(androidx.core.app.NotificationCompat.Action?);
+ method @RequiresApi(21) public androidx.core.app.NotificationCompat.Builder addInvisibleAction(int, CharSequence?, android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.Builder addPerson(androidx.core.app.Person?);
+ method @Deprecated public androidx.core.app.NotificationCompat.Builder addPerson(String?);
+ method public android.app.Notification build();
+ method public androidx.core.app.NotificationCompat.Builder clearActions();
+ method public androidx.core.app.NotificationCompat.Builder clearInvisibleActions();
+ method public androidx.core.app.NotificationCompat.Builder clearPeople();
+ method public android.widget.RemoteViews? createBigContentView();
+ method public android.widget.RemoteViews? createContentView();
+ method public android.widget.RemoteViews? createHeadsUpContentView();
+ method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Extender);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! getBigContentView();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.core.app.NotificationCompat.BubbleMetadata? getBubbleMetadata();
+ method @ColorInt @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getColor();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! getContentView();
+ method public android.os.Bundle getExtras();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getForegroundServiceBehavior();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! getHeadsUpContentView();
+ method @Deprecated public android.app.Notification getNotification();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getPriority();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public long getWhenIfShowing();
+ method protected static CharSequence? limitCharSequenceLength(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setAllowSystemGeneratedContextualActions(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setAutoCancel(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setBadgeIconType(@androidx.core.app.NotificationCompat.BadgeIconType int);
+ method public androidx.core.app.NotificationCompat.Builder setBubbleMetadata(androidx.core.app.NotificationCompat.BubbleMetadata?);
+ method public androidx.core.app.NotificationCompat.Builder setCategory(String?);
+ method public androidx.core.app.NotificationCompat.Builder setChannelId(String);
+ method @RequiresApi(24) public androidx.core.app.NotificationCompat.Builder setChronometerCountDown(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setColor(@ColorInt int);
+ method public androidx.core.app.NotificationCompat.Builder setColorized(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setContent(android.widget.RemoteViews?);
+ method public androidx.core.app.NotificationCompat.Builder setContentInfo(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setContentIntent(android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.Builder setContentText(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setContentTitle(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setCustomBigContentView(android.widget.RemoteViews?);
+ method public androidx.core.app.NotificationCompat.Builder setCustomContentView(android.widget.RemoteViews?);
+ method public androidx.core.app.NotificationCompat.Builder setCustomHeadsUpContentView(android.widget.RemoteViews?);
+ method public androidx.core.app.NotificationCompat.Builder setDefaults(int);
+ method public androidx.core.app.NotificationCompat.Builder setDeleteIntent(android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.Builder setExtras(android.os.Bundle?);
+ method public androidx.core.app.NotificationCompat.Builder setForegroundServiceBehavior(@androidx.core.app.NotificationCompat.ServiceNotificationBehavior int);
+ method public androidx.core.app.NotificationCompat.Builder setFullScreenIntent(android.app.PendingIntent?, boolean);
+ method public androidx.core.app.NotificationCompat.Builder setGroup(String?);
+ method public androidx.core.app.NotificationCompat.Builder setGroupAlertBehavior(@androidx.core.app.NotificationCompat.GroupAlertBehavior int);
+ method public androidx.core.app.NotificationCompat.Builder setGroupSummary(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setLargeIcon(android.graphics.Bitmap?);
+ method @RequiresApi(23) public androidx.core.app.NotificationCompat.Builder setLargeIcon(android.graphics.drawable.Icon?);
+ method public androidx.core.app.NotificationCompat.Builder setLights(@ColorInt int, int, int);
+ method public androidx.core.app.NotificationCompat.Builder setLocalOnly(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setLocusId(androidx.core.content.LocusIdCompat?);
+ method @Deprecated public androidx.core.app.NotificationCompat.Builder setNotificationSilent();
+ method public androidx.core.app.NotificationCompat.Builder setNumber(int);
+ method public androidx.core.app.NotificationCompat.Builder setOngoing(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setOnlyAlertOnce(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setPriority(int);
+ method public androidx.core.app.NotificationCompat.Builder setProgress(int, int, boolean);
+ method public androidx.core.app.NotificationCompat.Builder setPublicVersion(android.app.Notification?);
+ method public androidx.core.app.NotificationCompat.Builder setRemoteInputHistory(CharSequence![]?);
+ method public androidx.core.app.NotificationCompat.Builder setSettingsText(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setShortcutId(String?);
+ method public androidx.core.app.NotificationCompat.Builder setShortcutInfo(androidx.core.content.pm.ShortcutInfoCompat?);
+ method public androidx.core.app.NotificationCompat.Builder setShowWhen(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setSilent(boolean);
+ method @RequiresApi(23) public androidx.core.app.NotificationCompat.Builder setSmallIcon(androidx.core.graphics.drawable.IconCompat);
+ method public androidx.core.app.NotificationCompat.Builder setSmallIcon(int);
+ method public androidx.core.app.NotificationCompat.Builder setSmallIcon(int, int);
+ method public androidx.core.app.NotificationCompat.Builder setSortKey(String?);
+ method public androidx.core.app.NotificationCompat.Builder setSound(android.net.Uri?);
+ method public androidx.core.app.NotificationCompat.Builder setSound(android.net.Uri?, @androidx.core.app.NotificationCompat.StreamType int);
+ method public androidx.core.app.NotificationCompat.Builder setStyle(androidx.core.app.NotificationCompat.Style?);
+ method public androidx.core.app.NotificationCompat.Builder setSubText(CharSequence?);
+ method public androidx.core.app.NotificationCompat.Builder setTicker(CharSequence?);
+ method @Deprecated public androidx.core.app.NotificationCompat.Builder setTicker(CharSequence?, android.widget.RemoteViews?);
+ method public androidx.core.app.NotificationCompat.Builder setTimeoutAfter(long);
+ method public androidx.core.app.NotificationCompat.Builder setUsesChronometer(boolean);
+ method public androidx.core.app.NotificationCompat.Builder setVibrate(long[]?);
+ method public androidx.core.app.NotificationCompat.Builder setVisibility(@androidx.core.app.NotificationCompat.NotificationVisibility int);
+ method public androidx.core.app.NotificationCompat.Builder setWhen(long);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.ArrayList<androidx.core.app.NotificationCompat.Action!>! mActions;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.Context! mContext;
+ field @Deprecated public java.util.ArrayList<java.lang.String!>! mPeople;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.ArrayList<androidx.core.app.Person!> mPersonList;
+ }
+
+ public static class NotificationCompat.CallStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.CallStyle();
+ ctor public NotificationCompat.CallStyle(androidx.core.app.NotificationCompat.Builder?);
+ method public static androidx.core.app.NotificationCompat.CallStyle forIncomingCall(androidx.core.app.Person, android.app.PendingIntent, android.app.PendingIntent);
+ method public static androidx.core.app.NotificationCompat.CallStyle forOngoingCall(androidx.core.app.Person, android.app.PendingIntent);
+ method public static androidx.core.app.NotificationCompat.CallStyle forScreeningCall(androidx.core.app.Person, android.app.PendingIntent, android.app.PendingIntent);
+ method @RequiresApi(20) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.ArrayList<androidx.core.app.NotificationCompat.Action!> getActionsListWithSystemActions();
+ method public androidx.core.app.NotificationCompat.CallStyle setAnswerButtonColorHint(@ColorInt int);
+ method public androidx.core.app.NotificationCompat.CallStyle setDeclineButtonColorHint(@ColorInt int);
+ method public androidx.core.app.NotificationCompat.CallStyle setIsVideo(boolean);
+ method public androidx.core.app.NotificationCompat.CallStyle setVerificationIcon(android.graphics.Bitmap?);
+ method @RequiresApi(23) public androidx.core.app.NotificationCompat.CallStyle setVerificationIcon(android.graphics.drawable.Icon?);
+ method public androidx.core.app.NotificationCompat.CallStyle setVerificationText(CharSequence?);
+ field public static final int CALL_TYPE_INCOMING = 1; // 0x1
+ field public static final int CALL_TYPE_ONGOING = 2; // 0x2
+ field public static final int CALL_TYPE_SCREENING = 3; // 0x3
+ field public static final int CALL_TYPE_UNKNOWN = 0; // 0x0
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.CallStyle.CALL_TYPE_UNKNOWN, androidx.core.app.NotificationCompat.CallStyle.CALL_TYPE_INCOMING, androidx.core.app.NotificationCompat.CallStyle.CALL_TYPE_ONGOING, androidx.core.app.NotificationCompat.CallStyle.CALL_TYPE_SCREENING}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.CallStyle.CallType {
+ }
+
+ public static final class NotificationCompat.CarExtender implements androidx.core.app.NotificationCompat.Extender {
+ ctor public NotificationCompat.CarExtender();
+ ctor public NotificationCompat.CarExtender(android.app.Notification);
+ method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
+ method @ColorInt public int getColor();
+ method public android.graphics.Bitmap? getLargeIcon();
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation? getUnreadConversation();
+ method public androidx.core.app.NotificationCompat.CarExtender setColor(@ColorInt int);
+ method public androidx.core.app.NotificationCompat.CarExtender setLargeIcon(android.graphics.Bitmap?);
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender setUnreadConversation(androidx.core.app.NotificationCompat.CarExtender.UnreadConversation?);
+ }
+
+ @Deprecated public static class NotificationCompat.CarExtender.UnreadConversation {
+ method @Deprecated public long getLatestTimestamp();
+ method @Deprecated public String![]? getMessages();
+ method @Deprecated public String? getParticipant();
+ method @Deprecated public String![]? getParticipants();
+ method @Deprecated public android.app.PendingIntent? getReadPendingIntent();
+ method @Deprecated public androidx.core.app.RemoteInput? getRemoteInput();
+ method @Deprecated public android.app.PendingIntent? getReplyPendingIntent();
+ }
+
+ @Deprecated public static class NotificationCompat.CarExtender.UnreadConversation.Builder {
+ ctor @Deprecated public NotificationCompat.CarExtender.UnreadConversation.Builder(String);
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder addMessage(String?);
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation build();
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder setLatestTimestamp(long);
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder setReadPendingIntent(android.app.PendingIntent?);
+ method @Deprecated public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder setReplyAction(android.app.PendingIntent?, androidx.core.app.RemoteInput?);
+ }
+
+ public static class NotificationCompat.DecoratedCustomViewStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.DecoratedCustomViewStyle();
+ }
+
+ public static interface NotificationCompat.Extender {
+ method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.GROUP_ALERT_ALL, androidx.core.app.NotificationCompat.GROUP_ALERT_SUMMARY, androidx.core.app.NotificationCompat.GROUP_ALERT_CHILDREN}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.GroupAlertBehavior {
+ }
+
+ public static class NotificationCompat.InboxStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.InboxStyle();
+ ctor public NotificationCompat.InboxStyle(androidx.core.app.NotificationCompat.Builder?);
+ method public androidx.core.app.NotificationCompat.InboxStyle addLine(CharSequence?);
+ method public androidx.core.app.NotificationCompat.InboxStyle setBigContentTitle(CharSequence?);
+ method public androidx.core.app.NotificationCompat.InboxStyle setSummaryText(CharSequence?);
+ }
+
+ public static class NotificationCompat.MessagingStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MessagingStyle(androidx.core.app.Person);
+ ctor @Deprecated public NotificationCompat.MessagingStyle(CharSequence);
+ method public androidx.core.app.NotificationCompat.MessagingStyle addHistoricMessage(androidx.core.app.NotificationCompat.MessagingStyle.Message?);
+ method public androidx.core.app.NotificationCompat.MessagingStyle addMessage(androidx.core.app.NotificationCompat.MessagingStyle.Message?);
+ method public androidx.core.app.NotificationCompat.MessagingStyle addMessage(CharSequence?, long, androidx.core.app.Person?);
+ method @Deprecated public androidx.core.app.NotificationCompat.MessagingStyle addMessage(CharSequence?, long, CharSequence?);
+ method public static androidx.core.app.NotificationCompat.MessagingStyle? extractMessagingStyleFromNotification(android.app.Notification);
+ method public CharSequence? getConversationTitle();
+ method public java.util.List<androidx.core.app.NotificationCompat.MessagingStyle.Message!> getHistoricMessages();
+ method public java.util.List<androidx.core.app.NotificationCompat.MessagingStyle.Message!> getMessages();
+ method public androidx.core.app.Person getUser();
+ method @Deprecated public CharSequence? getUserDisplayName();
+ method public boolean isGroupConversation();
+ method public androidx.core.app.NotificationCompat.MessagingStyle setConversationTitle(CharSequence?);
+ method public androidx.core.app.NotificationCompat.MessagingStyle setGroupConversation(boolean);
+ field public static final int MAXIMUM_RETAINED_MESSAGES = 25; // 0x19
+ }
+
+ public static final class NotificationCompat.MessagingStyle.Message {
+ ctor public NotificationCompat.MessagingStyle.Message(CharSequence?, long, androidx.core.app.Person?);
+ ctor @Deprecated public NotificationCompat.MessagingStyle.Message(CharSequence?, long, CharSequence?);
+ method public String? getDataMimeType();
+ method public android.net.Uri? getDataUri();
+ method public android.os.Bundle getExtras();
+ method public androidx.core.app.Person? getPerson();
+ method @Deprecated public CharSequence? getSender();
+ method public CharSequence? getText();
+ method public long getTimestamp();
+ method public androidx.core.app.NotificationCompat.MessagingStyle.Message setData(String?, android.net.Uri?);
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC, androidx.core.app.NotificationCompat.VISIBILITY_PRIVATE, androidx.core.app.NotificationCompat.VISIBILITY_SECRET}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.NotificationVisibility {
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_DEFAULT, androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE, androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_DEFERRED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.ServiceNotificationBehavior {
+ }
+
+ @IntDef({android.media.AudioManager.STREAM_VOICE_CALL, android.media.AudioManager.STREAM_SYSTEM, android.media.AudioManager.STREAM_RING, android.media.AudioManager.STREAM_MUSIC, android.media.AudioManager.STREAM_ALARM, android.media.AudioManager.STREAM_NOTIFICATION, android.media.AudioManager.STREAM_DTMF, android.media.AudioManager.STREAM_ACCESSIBILITY}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.StreamType {
+ }
+
+ public abstract static class NotificationCompat.Style {
+ ctor public NotificationCompat.Style();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addCompatExtras(android.os.Bundle);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void apply(androidx.core.app.NotificationBuilderWithBuilderAccessor!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews applyStandardTemplate(boolean, int, boolean);
+ method public android.app.Notification? build();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void buildIntoRemoteViews(android.widget.RemoteViews!, android.widget.RemoteViews!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected void clearCompatExtraKeys(android.os.Bundle);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.Bitmap! createColoredBitmap(int, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean displayCustomViewInline();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.app.NotificationCompat.Style? extractStyleFromNotification(android.app.Notification);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected String? getClassName();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! makeBigContentView(androidx.core.app.NotificationBuilderWithBuilderAccessor!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! makeContentView(androidx.core.app.NotificationBuilderWithBuilderAccessor!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! makeHeadsUpContentView(androidx.core.app.NotificationBuilderWithBuilderAccessor!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected void restoreFromCompatExtras(android.os.Bundle);
+ method public void setBuilder(androidx.core.app.NotificationCompat.Builder?);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected androidx.core.app.NotificationCompat.Builder! mBuilder;
+ }
+
+ public static final class NotificationCompat.TvExtender implements androidx.core.app.NotificationCompat.Extender {
+ ctor public NotificationCompat.TvExtender();
+ ctor public NotificationCompat.TvExtender(android.app.Notification);
+ method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
+ method public String? getChannelId();
+ method public android.app.PendingIntent? getContentIntent();
+ method public android.app.PendingIntent? getDeleteIntent();
+ method public boolean isAvailableOnTv();
+ method public boolean isSuppressShowOverApps();
+ method public androidx.core.app.NotificationCompat.TvExtender setChannelId(String?);
+ method public androidx.core.app.NotificationCompat.TvExtender setContentIntent(android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.TvExtender setDeleteIntent(android.app.PendingIntent?);
+ method public androidx.core.app.NotificationCompat.TvExtender setSuppressShowOverApps(boolean);
+ }
+
+ public static final class NotificationCompat.WearableExtender implements androidx.core.app.NotificationCompat.Extender {
+ ctor public NotificationCompat.WearableExtender();
+ ctor public NotificationCompat.WearableExtender(android.app.Notification);
+ method public androidx.core.app.NotificationCompat.WearableExtender addAction(androidx.core.app.NotificationCompat.Action);
+ method public androidx.core.app.NotificationCompat.WearableExtender addActions(java.util.List<androidx.core.app.NotificationCompat.Action!>);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender addPage(android.app.Notification);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender addPages(java.util.List<android.app.Notification!>);
+ method public androidx.core.app.NotificationCompat.WearableExtender clearActions();
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender clearPages();
+ method public androidx.core.app.NotificationCompat.WearableExtender clone();
+ method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
+ method public java.util.List<androidx.core.app.NotificationCompat.Action!> getActions();
+ method @Deprecated public android.graphics.Bitmap? getBackground();
+ method public String? getBridgeTag();
+ method public int getContentAction();
+ method @Deprecated public int getContentIcon();
+ method @Deprecated public int getContentIconGravity();
+ method public boolean getContentIntentAvailableOffline();
+ method @Deprecated public int getCustomContentHeight();
+ method @Deprecated public int getCustomSizePreset();
+ method public String? getDismissalId();
+ method @Deprecated public android.app.PendingIntent? getDisplayIntent();
+ method @Deprecated public int getGravity();
+ method @Deprecated public boolean getHintAmbientBigPicture();
+ method @Deprecated public boolean getHintAvoidBackgroundClipping();
+ method public boolean getHintContentIntentLaunchesActivity();
+ method @Deprecated public boolean getHintHideIcon();
+ method @Deprecated public int getHintScreenTimeout();
+ method @Deprecated public boolean getHintShowBackgroundOnly();
+ method @Deprecated public java.util.List<android.app.Notification!> getPages();
+ method public boolean getStartScrollBottom();
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setBackground(android.graphics.Bitmap?);
+ method public androidx.core.app.NotificationCompat.WearableExtender setBridgeTag(String?);
+ method public androidx.core.app.NotificationCompat.WearableExtender setContentAction(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setContentIcon(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setContentIconGravity(int);
+ method public androidx.core.app.NotificationCompat.WearableExtender setContentIntentAvailableOffline(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setCustomContentHeight(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setCustomSizePreset(int);
+ method public androidx.core.app.NotificationCompat.WearableExtender setDismissalId(String?);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setDisplayIntent(android.app.PendingIntent?);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setGravity(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setHintAmbientBigPicture(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setHintAvoidBackgroundClipping(boolean);
+ method public androidx.core.app.NotificationCompat.WearableExtender setHintContentIntentLaunchesActivity(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setHintHideIcon(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setHintScreenTimeout(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender setHintShowBackgroundOnly(boolean);
+ method public androidx.core.app.NotificationCompat.WearableExtender setStartScrollBottom(boolean);
+ field @Deprecated public static final int SCREEN_TIMEOUT_LONG = -1; // 0xffffffff
+ field @Deprecated public static final int SCREEN_TIMEOUT_SHORT = 0; // 0x0
+ field @Deprecated public static final int SIZE_DEFAULT = 0; // 0x0
+ field @Deprecated public static final int SIZE_FULL_SCREEN = 5; // 0x5
+ field @Deprecated public static final int SIZE_LARGE = 4; // 0x4
+ field @Deprecated public static final int SIZE_MEDIUM = 3; // 0x3
+ field @Deprecated public static final int SIZE_SMALL = 2; // 0x2
+ field @Deprecated public static final int SIZE_XSMALL = 1; // 0x1
+ field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff
+ }
+
+ public final class NotificationCompatExtras {
+ field public static final String EXTRA_ACTION_EXTRAS = "android.support.actionExtras";
+ field public static final String EXTRA_GROUP_KEY = "android.support.groupKey";
+ field public static final String EXTRA_GROUP_SUMMARY = "android.support.isGroupSummary";
+ field public static final String EXTRA_LOCAL_ONLY = "android.support.localOnly";
+ field public static final String EXTRA_REMOTE_INPUTS = "android.support.remoteInputs";
+ field public static final String EXTRA_SORT_KEY = "android.support.sortKey";
+ }
+
+ public abstract class NotificationCompatSideChannelService extends android.app.Service {
+ ctor public NotificationCompatSideChannelService();
+ method public abstract void cancel(String!, int, String!);
+ method public abstract void cancelAll(String!);
+ method public abstract void notify(String!, int, String!, android.app.Notification!);
+ method @DeprecatedSinceApi(api=19, message="SDKs past 19 have no need for side channeling.") public android.os.IBinder! onBind(android.content.Intent!);
+ }
+
+ public final class NotificationManagerCompat {
+ method public boolean areNotificationsEnabled();
+ method public boolean canUseFullScreenIntent();
+ method public void cancel(int);
+ method public void cancel(String?, int);
+ method public void cancelAll();
+ method public void createNotificationChannel(android.app.NotificationChannel);
+ method public void createNotificationChannel(androidx.core.app.NotificationChannelCompat);
+ method public void createNotificationChannelGroup(android.app.NotificationChannelGroup);
+ method public void createNotificationChannelGroup(androidx.core.app.NotificationChannelGroupCompat);
+ method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup!>);
+ method public void createNotificationChannelGroupsCompat(java.util.List<androidx.core.app.NotificationChannelGroupCompat!>);
+ method public void createNotificationChannels(java.util.List<android.app.NotificationChannel!>);
+ method public void createNotificationChannelsCompat(java.util.List<androidx.core.app.NotificationChannelCompat!>);
+ method public void deleteNotificationChannel(String);
+ method public void deleteNotificationChannelGroup(String);
+ method public void deleteUnlistedNotificationChannels(java.util.Collection<java.lang.String!>);
+ method public static androidx.core.app.NotificationManagerCompat from(android.content.Context);
+ method public java.util.List<android.service.notification.StatusBarNotification!> getActiveNotifications();
+ method @androidx.core.app.NotificationManagerCompat.InterruptionFilter public int getCurrentInterruptionFilter();
+ method public static java.util.Set<java.lang.String!> getEnabledListenerPackages(android.content.Context);
+ method public int getImportance();
+ method public android.app.NotificationChannel? getNotificationChannel(String);
+ method public android.app.NotificationChannel? getNotificationChannel(String, String);
+ method public androidx.core.app.NotificationChannelCompat? getNotificationChannelCompat(String);
+ method public androidx.core.app.NotificationChannelCompat? getNotificationChannelCompat(String, String);
+ method public android.app.NotificationChannelGroup? getNotificationChannelGroup(String);
+ method public androidx.core.app.NotificationChannelGroupCompat? getNotificationChannelGroupCompat(String);
+ method public java.util.List<android.app.NotificationChannelGroup!> getNotificationChannelGroups();
+ method public java.util.List<androidx.core.app.NotificationChannelGroupCompat!> getNotificationChannelGroupsCompat();
+ method public java.util.List<android.app.NotificationChannel!> getNotificationChannels();
+ method public java.util.List<androidx.core.app.NotificationChannelCompat!> getNotificationChannelsCompat();
+ method @RequiresPermission(android.Manifest.permission.POST_NOTIFICATIONS) public void notify(int, android.app.Notification);
+ method @RequiresPermission(android.Manifest.permission.POST_NOTIFICATIONS) public void notify(String?, int, android.app.Notification);
+ method @RequiresPermission(android.Manifest.permission.POST_NOTIFICATIONS) public void notify(java.util.List<androidx.core.app.NotificationManagerCompat.NotificationWithIdAndTag!>);
+ field public static final String ACTION_BIND_SIDE_CHANNEL = "android.support.BIND_NOTIFICATION_SIDE_CHANNEL";
+ field public static final String EXTRA_USE_SIDE_CHANNEL = "android.support.useSideChannel";
+ field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
+ field public static final int IMPORTANCE_HIGH = 4; // 0x4
+ field public static final int IMPORTANCE_LOW = 2; // 0x2
+ field public static final int IMPORTANCE_MAX = 5; // 0x5
+ field public static final int IMPORTANCE_MIN = 1; // 0x1
+ field public static final int IMPORTANCE_NONE = 0; // 0x0
+ field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
+ field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
+ field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
+ field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
+ field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
+ field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+ }
+
+ @IntDef({androidx.core.app.NotificationManagerCompat.INTERRUPTION_FILTER_UNKNOWN, androidx.core.app.NotificationManagerCompat.INTERRUPTION_FILTER_ALL, androidx.core.app.NotificationManagerCompat.INTERRUPTION_FILTER_PRIORITY, androidx.core.app.NotificationManagerCompat.INTERRUPTION_FILTER_NONE, androidx.core.app.NotificationManagerCompat.INTERRUPTION_FILTER_ALARMS}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationManagerCompat.InterruptionFilter {
+ }
+
+ public static class NotificationManagerCompat.NotificationWithIdAndTag {
+ ctor public NotificationManagerCompat.NotificationWithIdAndTag(int, android.app.Notification);
+ ctor public NotificationManagerCompat.NotificationWithIdAndTag(String?, int, android.app.Notification);
+ }
+
+ public interface OnMultiWindowModeChangedProvider {
+ method public void addOnMultiWindowModeChangedListener(androidx.core.util.Consumer<androidx.core.app.MultiWindowModeChangedInfo> listener);
+ method public void removeOnMultiWindowModeChangedListener(androidx.core.util.Consumer<androidx.core.app.MultiWindowModeChangedInfo> listener);
+ }
+
+ public interface OnNewIntentProvider {
+ method public void addOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
+ method public void removeOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
+ }
+
+ public interface OnPictureInPictureModeChangedProvider {
+ method public void addOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
+ method public void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
+ }
+
+ public interface OnUserLeaveHintProvider {
+ method public void addOnUserLeaveHintListener(Runnable listener);
+ method public void removeOnUserLeaveHintListener(Runnable listener);
+ }
+
+ public final class PendingIntentCompat {
+ method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, android.os.Bundle?, boolean);
+ method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, boolean);
+ method public static android.app.PendingIntent? getActivity(android.content.Context, int, android.content.Intent, int, android.os.Bundle?, boolean);
+ method public static android.app.PendingIntent? getActivity(android.content.Context, int, android.content.Intent, int, boolean);
+ method public static android.app.PendingIntent? getBroadcast(android.content.Context, int, android.content.Intent, int, boolean);
+ method @RequiresApi(26) public static android.app.PendingIntent getForegroundService(android.content.Context, int, android.content.Intent, int, boolean);
+ method public static android.app.PendingIntent? getService(android.content.Context, int, android.content.Intent, int, boolean);
+ method public static void send(android.app.PendingIntent, android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished?, android.os.Handler?) throws android.app.PendingIntent.CanceledException;
+ method public static void send(android.app.PendingIntent, android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished?, android.os.Handler?, String?, android.os.Bundle?) throws android.app.PendingIntent.CanceledException;
+ method public static void send(android.app.PendingIntent, int, android.app.PendingIntent.OnFinished?, android.os.Handler?) throws android.app.PendingIntent.CanceledException;
+ }
+
+ public class Person {
+ method @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.app.Person fromAndroidPerson(android.app.Person);
+ method public static androidx.core.app.Person fromBundle(android.os.Bundle);
+ method @RequiresApi(22) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.app.Person fromPersistableBundle(android.os.PersistableBundle);
+ method public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method public String? getKey();
+ method public CharSequence? getName();
+ method public String? getUri();
+ method public boolean isBot();
+ method public boolean isImportant();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String resolveToLegacyUri();
+ method @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.app.Person toAndroidPerson();
+ method public androidx.core.app.Person.Builder toBuilder();
+ method public android.os.Bundle toBundle();
+ method @RequiresApi(22) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.PersistableBundle toPersistableBundle();
+ }
+
+ public static class Person.Builder {
+ ctor public Person.Builder();
+ method public androidx.core.app.Person build();
+ method public androidx.core.app.Person.Builder setBot(boolean);
+ method public androidx.core.app.Person.Builder setIcon(androidx.core.graphics.drawable.IconCompat?);
+ method public androidx.core.app.Person.Builder setImportant(boolean);
+ method public androidx.core.app.Person.Builder setKey(String?);
+ method public androidx.core.app.Person.Builder setName(CharSequence?);
+ method public androidx.core.app.Person.Builder setUri(String?);
+ }
+
+ public final class PictureInPictureModeChangedInfo {
+ ctor public PictureInPictureModeChangedInfo(boolean isInPictureInPictureMode);
+ ctor @RequiresApi(26) public PictureInPictureModeChangedInfo(boolean isInPictureInPictureMode, android.content.res.Configuration newConfig);
+ method @RequiresApi(26) public android.content.res.Configuration getNewConfig();
+ method public boolean isInPictureInPictureMode();
+ property public final boolean isInPictureInPictureMode;
+ property @RequiresApi(26) public final android.content.res.Configuration newConfig;
+ }
+
+ @androidx.versionedparcelable.VersionedParcelize(jetifyAs="android.support.v4.app.RemoteActionCompat") public final class RemoteActionCompat implements androidx.versionedparcelable.VersionedParcelable {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public RemoteActionCompat();
+ ctor public RemoteActionCompat(androidx.core.app.RemoteActionCompat);
+ ctor public RemoteActionCompat(androidx.core.graphics.drawable.IconCompat, CharSequence, CharSequence, android.app.PendingIntent);
+ method @RequiresApi(26) public static androidx.core.app.RemoteActionCompat createFromRemoteAction(android.app.RemoteAction);
+ method public android.app.PendingIntent getActionIntent();
+ method public CharSequence getContentDescription();
+ method public androidx.core.graphics.drawable.IconCompat getIcon();
+ method public CharSequence getTitle();
+ method public boolean isEnabled();
+ method public void setEnabled(boolean);
+ method public void setShouldShowIcon(boolean);
+ method public boolean shouldShowIcon();
+ method @RequiresApi(26) public android.app.RemoteAction toRemoteAction();
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(4) public android.app.PendingIntent mActionIntent;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(3) public CharSequence mContentDescription;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(5) public boolean mEnabled;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(1) public androidx.core.graphics.drawable.IconCompat mIcon;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(6) public boolean mShouldShowIcon;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(2) public CharSequence mTitle;
+ }
+
+ public final class RemoteInput {
+ method public static void addDataResultToIntent(androidx.core.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String!,android.net.Uri!>);
+ method public static void addResultsToIntent(androidx.core.app.RemoteInput![], android.content.Intent, android.os.Bundle);
+ method public boolean getAllowFreeFormInput();
+ method public java.util.Set<java.lang.String!>? getAllowedDataTypes();
+ method public CharSequence![]? getChoices();
+ method public static java.util.Map<java.lang.String!,android.net.Uri!>? getDataResultsFromIntent(android.content.Intent, String);
+ method @androidx.core.app.RemoteInput.EditChoicesBeforeSending public int getEditChoicesBeforeSending();
+ method public android.os.Bundle getExtras();
+ method public CharSequence? getLabel();
+ method public String getResultKey();
+ method public static android.os.Bundle? getResultsFromIntent(android.content.Intent);
+ method @androidx.core.app.RemoteInput.Source public static int getResultsSource(android.content.Intent);
+ method public boolean isDataOnly();
+ method public static void setResultsSource(android.content.Intent, @androidx.core.app.RemoteInput.Source int);
+ field public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0; // 0x0
+ field public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1; // 0x1
+ field public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2; // 0x2
+ field public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
+ field public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results";
+ field public static final int SOURCE_CHOICE = 1; // 0x1
+ field public static final int SOURCE_FREE_FORM_INPUT = 0; // 0x0
+ }
+
+ public static final class RemoteInput.Builder {
+ ctor public RemoteInput.Builder(String);
+ method public androidx.core.app.RemoteInput.Builder addExtras(android.os.Bundle);
+ method public androidx.core.app.RemoteInput build();
+ method public android.os.Bundle getExtras();
+ method public androidx.core.app.RemoteInput.Builder setAllowDataType(String, boolean);
+ method public androidx.core.app.RemoteInput.Builder setAllowFreeFormInput(boolean);
+ method public androidx.core.app.RemoteInput.Builder setChoices(CharSequence![]?);
+ method public androidx.core.app.RemoteInput.Builder setEditChoicesBeforeSending(@androidx.core.app.RemoteInput.EditChoicesBeforeSending int);
+ method public androidx.core.app.RemoteInput.Builder setLabel(CharSequence?);
+ }
+
+ @IntDef({androidx.core.app.RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO, androidx.core.app.RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED, androidx.core.app.RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RemoteInput.EditChoicesBeforeSending {
+ }
+
+ @IntDef({androidx.core.app.RemoteInput.SOURCE_FREE_FORM_INPUT, androidx.core.app.RemoteInput.SOURCE_CHOICE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RemoteInput.Source {
+ }
+
+ public final class ServiceCompat {
+ method public static void startForeground(android.app.Service, int, android.app.Notification, int);
+ method public static void stopForeground(android.app.Service, @androidx.core.app.ServiceCompat.StopForegroundFlags int);
+ field public static final int START_STICKY = 1; // 0x1
+ field public static final int STOP_FOREGROUND_DETACH = 2; // 0x2
+ field public static final int STOP_FOREGROUND_REMOVE = 1; // 0x1
+ }
+
+ @IntDef(flag=true, value={androidx.core.app.ServiceCompat.STOP_FOREGROUND_REMOVE, androidx.core.app.ServiceCompat.STOP_FOREGROUND_DETACH}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ServiceCompat.StopForegroundFlags {
+ }
+
+ public final class ShareCompat {
+ method @Deprecated public static void configureMenuItem(android.view.Menu, @IdRes int, androidx.core.app.ShareCompat.IntentBuilder);
+ method @Deprecated public static void configureMenuItem(android.view.MenuItem, androidx.core.app.ShareCompat.IntentBuilder);
+ method public static android.content.ComponentName? getCallingActivity(android.app.Activity);
+ method public static String? getCallingPackage(android.app.Activity);
+ field public static final String EXTRA_CALLING_ACTIVITY = "androidx.core.app.EXTRA_CALLING_ACTIVITY";
+ field public static final String EXTRA_CALLING_ACTIVITY_INTEROP = "android.support.v4.app.EXTRA_CALLING_ACTIVITY";
+ field public static final String EXTRA_CALLING_PACKAGE = "androidx.core.app.EXTRA_CALLING_PACKAGE";
+ field public static final String EXTRA_CALLING_PACKAGE_INTEROP = "android.support.v4.app.EXTRA_CALLING_PACKAGE";
+ }
+
+ public static class ShareCompat.IntentBuilder {
+ ctor public ShareCompat.IntentBuilder(android.content.Context);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailBcc(String);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailBcc(String![]);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailCc(String);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailCc(String![]);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailTo(String);
+ method public androidx.core.app.ShareCompat.IntentBuilder addEmailTo(String![]);
+ method public androidx.core.app.ShareCompat.IntentBuilder addStream(android.net.Uri);
+ method public android.content.Intent createChooserIntent();
+ method @Deprecated public static androidx.core.app.ShareCompat.IntentBuilder from(android.app.Activity);
+ method public android.content.Intent getIntent();
+ method public androidx.core.app.ShareCompat.IntentBuilder setChooserTitle(@StringRes int);
+ method public androidx.core.app.ShareCompat.IntentBuilder setChooserTitle(CharSequence?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setEmailBcc(String![]?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setEmailCc(String![]?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setEmailTo(String![]?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setHtmlText(String?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setStream(android.net.Uri?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setSubject(String?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setText(CharSequence?);
+ method public androidx.core.app.ShareCompat.IntentBuilder setType(String?);
+ method public void startChooser();
+ }
+
+ public static class ShareCompat.IntentReader {
+ ctor public ShareCompat.IntentReader(android.app.Activity);
+ ctor public ShareCompat.IntentReader(android.content.Context, android.content.Intent);
+ method @Deprecated public static androidx.core.app.ShareCompat.IntentReader from(android.app.Activity);
+ method public android.content.ComponentName? getCallingActivity();
+ method public android.graphics.drawable.Drawable? getCallingActivityIcon();
+ method public android.graphics.drawable.Drawable? getCallingApplicationIcon();
+ method public CharSequence? getCallingApplicationLabel();
+ method public String? getCallingPackage();
+ method public String![]? getEmailBcc();
+ method public String![]? getEmailCc();
+ method public String![]? getEmailTo();
+ method public String? getHtmlText();
+ method public android.net.Uri? getStream();
+ method public android.net.Uri? getStream(int);
+ method public int getStreamCount();
+ method public String? getSubject();
+ method public CharSequence? getText();
+ method public String? getType();
+ method public boolean isMultipleShare();
+ method public boolean isShareIntent();
+ method public boolean isSingleShare();
+ }
+
+ public abstract class SharedElementCallback {
+ ctor public SharedElementCallback();
+ method public android.os.Parcelable! onCaptureSharedElementSnapshot(android.view.View!, android.graphics.Matrix!, android.graphics.RectF!);
+ method public android.view.View! onCreateSnapshotView(android.content.Context!, android.os.Parcelable!);
+ method public void onMapSharedElements(java.util.List<java.lang.String!>!, java.util.Map<java.lang.String!,android.view.View!>!);
+ method public void onRejectSharedElements(java.util.List<android.view.View!>!);
+ method public void onSharedElementEnd(java.util.List<java.lang.String!>!, java.util.List<android.view.View!>!, java.util.List<android.view.View!>!);
+ method public void onSharedElementStart(java.util.List<java.lang.String!>!, java.util.List<android.view.View!>!, java.util.List<android.view.View!>!);
+ method public void onSharedElementsArrived(java.util.List<java.lang.String!>!, java.util.List<android.view.View!>!, androidx.core.app.SharedElementCallback.OnSharedElementsReadyListener!);
+ }
+
+ public static interface SharedElementCallback.OnSharedElementsReadyListener {
+ method public void onSharedElementsReady();
+ }
+
+ public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent> {
+ method public androidx.core.app.TaskStackBuilder addNextIntent(android.content.Intent);
+ method public androidx.core.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
+ method public androidx.core.app.TaskStackBuilder addParentStack(android.app.Activity);
+ method public androidx.core.app.TaskStackBuilder addParentStack(android.content.ComponentName);
+ method public androidx.core.app.TaskStackBuilder addParentStack(Class<?>);
+ method public static androidx.core.app.TaskStackBuilder create(android.content.Context);
+ method public android.content.Intent? editIntentAt(int);
+ method @Deprecated public static androidx.core.app.TaskStackBuilder! from(android.content.Context!);
+ method @Deprecated public android.content.Intent! getIntent(int);
+ method public int getIntentCount();
+ method public android.content.Intent![] getIntents();
+ method public android.app.PendingIntent? getPendingIntent(int, int);
+ method public android.app.PendingIntent? getPendingIntent(int, int, android.os.Bundle?);
+ method @Deprecated public java.util.Iterator<android.content.Intent!> iterator();
+ method public void startActivities();
+ method public void startActivities(android.os.Bundle?);
+ }
+
+ public static interface TaskStackBuilder.SupportParentable {
+ method public android.content.Intent? getSupportParentActivityIntent();
+ }
+
+}
+
+package androidx.core.content {
+
+ public final class ContentProviderCompat {
+ method public static android.content.Context requireContext(android.content.ContentProvider);
+ }
+
+ public final class ContentResolverCompat {
+ method public static android.database.Cursor? query(android.content.ContentResolver, android.net.Uri, String![]?, String?, String![]?, String?, android.os.CancellationSignal?);
+ method @Deprecated public static android.database.Cursor? query(android.content.ContentResolver, android.net.Uri, String![]?, String?, String![]?, String?, androidx.core.os.CancellationSignal?);
+ }
+
+ public class ContextCompat {
+ ctor protected ContextCompat();
+ method public static int checkSelfPermission(android.content.Context, String);
+ method public static android.content.Context createAttributionContext(android.content.Context, String?);
+ method public static android.content.Context? createDeviceProtectedStorageContext(android.content.Context);
+ method public static String? getAttributionTag(android.content.Context);
+ method public static java.io.File getCodeCacheDir(android.content.Context);
+ method @ColorInt public static int getColor(android.content.Context, @ColorRes int);
+ method public static android.content.res.ColorStateList? getColorStateList(android.content.Context, @ColorRes int);
+ method public static android.content.Context getContextForLanguage(android.content.Context);
+ method public static java.io.File? getDataDir(android.content.Context);
+ method public static android.view.Display getDisplayOrDefault(@DisplayContext android.content.Context);
+ method public static android.graphics.drawable.Drawable? getDrawable(android.content.Context, @DrawableRes int);
+ method public static java.io.File![] getExternalCacheDirs(android.content.Context);
+ method public static java.io.File![] getExternalFilesDirs(android.content.Context, String?);
+ method public static java.util.concurrent.Executor getMainExecutor(android.content.Context);
+ method public static java.io.File? getNoBackupFilesDir(android.content.Context);
+ method public static java.io.File![] getObbDirs(android.content.Context);
+ method public static String getString(android.content.Context, int);
+ method public static <T> T? getSystemService(android.content.Context, Class<T!>);
+ method public static String? getSystemServiceName(android.content.Context, Class<?>);
+ method public static boolean isDeviceProtectedStorage(android.content.Context);
+ method public static android.content.Intent? registerReceiver(android.content.Context, android.content.BroadcastReceiver?, android.content.IntentFilter, int);
+ method public static android.content.Intent? registerReceiver(android.content.Context, android.content.BroadcastReceiver?, android.content.IntentFilter, String?, android.os.Handler?, int);
+ method public static boolean startActivities(android.content.Context, android.content.Intent![]);
+ method public static boolean startActivities(android.content.Context, android.content.Intent![], android.os.Bundle?);
+ method public static void startActivity(android.content.Context, android.content.Intent, android.os.Bundle?);
+ method public static void startForegroundService(android.content.Context, android.content.Intent);
+ field public static final int RECEIVER_EXPORTED = 2; // 0x2
+ field public static final int RECEIVER_NOT_EXPORTED = 4; // 0x4
+ field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
+ }
+
+ public class FileProvider extends android.content.ContentProvider {
+ ctor public FileProvider();
+ ctor protected FileProvider(@XmlRes int);
+ method public int delete(android.net.Uri, String?, String![]?);
+ method public String? getType(android.net.Uri);
+ method public static android.net.Uri! getUriForFile(android.content.Context, String, java.io.File);
+ method public static android.net.Uri getUriForFile(android.content.Context, String, java.io.File, String);
+ method public android.net.Uri! insert(android.net.Uri, android.content.ContentValues);
+ method public boolean onCreate();
+ method public android.database.Cursor query(android.net.Uri, String![]?, String?, String![]?, String?);
+ method public int update(android.net.Uri, android.content.ContentValues, String?, String![]?);
+ }
+
+ public final class IntentCompat {
+ method public static android.content.Intent createManageUnusedAppRestrictionsIntent(android.content.Context, String);
+ method public static android.os.Parcelable![]? getParcelableArrayExtra(android.content.Intent, String?, Class<? extends android.os.Parcelable!>);
+ method public static <T> java.util.ArrayList<T!>? getParcelableArrayListExtra(android.content.Intent, String?, Class<? extends T!>);
+ method public static <T> T? getParcelableExtra(android.content.Intent, String?, Class<T!>);
+ method public static <T extends java.io.Serializable> T? getSerializableExtra(android.content.Intent, String?, Class<T!>);
+ method public static android.content.Intent makeMainSelectorActivity(String, String);
+ field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
+ field public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
+ field public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
+ field public static final String EXTRA_START_PLAYBACK = "android.intent.extra.START_PLAYBACK";
+ field public static final String EXTRA_TIME = "android.intent.extra.TIME";
+ }
+
+ public class IntentSanitizer {
+ method public android.content.Intent sanitize(android.content.Intent, androidx.core.util.Consumer<java.lang.String!>);
+ method public android.content.Intent sanitizeByFiltering(android.content.Intent);
+ method public android.content.Intent sanitizeByThrowing(android.content.Intent);
+ }
+
+ public static final class IntentSanitizer.Builder {
+ ctor public IntentSanitizer.Builder();
+ method public androidx.core.content.IntentSanitizer.Builder allowAction(androidx.core.util.Predicate<java.lang.String!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowAction(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowAnyComponent();
+ method public androidx.core.content.IntentSanitizer.Builder allowCategory(androidx.core.util.Predicate<java.lang.String!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowCategory(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowClipData(androidx.core.util.Predicate<android.content.ClipData!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowClipDataText();
+ method public androidx.core.content.IntentSanitizer.Builder allowClipDataUri(androidx.core.util.Predicate<android.net.Uri!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowClipDataUriWithAuthority(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowComponent(android.content.ComponentName);
+ method public androidx.core.content.IntentSanitizer.Builder allowComponent(androidx.core.util.Predicate<android.content.ComponentName!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowComponentWithPackage(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowData(androidx.core.util.Predicate<android.net.Uri!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowDataWithAuthority(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtra(String, androidx.core.util.Predicate<java.lang.Object!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtra(String, Class<?>);
+ method public <T> androidx.core.content.IntentSanitizer.Builder allowExtra(String, Class<T!>, androidx.core.util.Predicate<T!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtraOutput(androidx.core.util.Predicate<android.net.Uri!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtraOutput(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtraStream(androidx.core.util.Predicate<android.net.Uri!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowExtraStreamUriWithAuthority(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowFlags(int);
+ method public androidx.core.content.IntentSanitizer.Builder allowHistoryStackFlags();
+ method public androidx.core.content.IntentSanitizer.Builder allowIdentifier();
+ method public androidx.core.content.IntentSanitizer.Builder allowPackage(androidx.core.util.Predicate<java.lang.String!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowPackage(String);
+ method public androidx.core.content.IntentSanitizer.Builder allowReceiverFlags();
+ method public androidx.core.content.IntentSanitizer.Builder allowSelector();
+ method public androidx.core.content.IntentSanitizer.Builder allowSourceBounds();
+ method public androidx.core.content.IntentSanitizer.Builder allowType(androidx.core.util.Predicate<java.lang.String!>);
+ method public androidx.core.content.IntentSanitizer.Builder allowType(String);
+ method public androidx.core.content.IntentSanitizer build();
+ }
+
+ public final class LocusIdCompat {
+ ctor public LocusIdCompat(String);
+ method public String getId();
+ method @RequiresApi(29) public android.content.LocusId toLocusId();
+ method @RequiresApi(29) public static androidx.core.content.LocusIdCompat toLocusIdCompat(android.content.LocusId);
+ }
+
+ public final class MimeTypeFilter {
+ method public static boolean matches(String?, String);
+ method public static String? matches(String?, String![]);
+ method public static String? matches(String![]?, String);
+ method public static String![] matchesMany(String![]?, String);
+ }
+
+ public interface OnConfigurationChangedProvider {
+ method public void addOnConfigurationChangedListener(androidx.core.util.Consumer<android.content.res.Configuration> listener);
+ method public void removeOnConfigurationChangedListener(androidx.core.util.Consumer<android.content.res.Configuration> listener);
+ }
+
+ public interface OnTrimMemoryProvider {
+ method public void addOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
+ method public void removeOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
+ }
+
+ public final class PackageManagerCompat {
+ method public static com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> getUnusedAppRestrictionsStatus(android.content.Context);
+ field public static final String ACTION_PERMISSION_REVOCATION_SETTINGS = "android.intent.action.AUTO_REVOKE_PERMISSIONS";
+ }
+
+ public final class PermissionChecker {
+ method @androidx.core.content.PermissionChecker.PermissionResult public static int checkCallingOrSelfPermission(android.content.Context, String);
+ method @androidx.core.content.PermissionChecker.PermissionResult public static int checkCallingPermission(android.content.Context, String, String?);
+ method @androidx.core.content.PermissionChecker.PermissionResult public static int checkPermission(android.content.Context, String, int, int, String?);
+ method @androidx.core.content.PermissionChecker.PermissionResult public static int checkSelfPermission(android.content.Context, String);
+ field public static final int PERMISSION_DENIED = -1; // 0xffffffff
+ field public static final int PERMISSION_DENIED_APP_OP = -2; // 0xfffffffe
+ field public static final int PERMISSION_GRANTED = 0; // 0x0
+ }
+
+ @IntDef({androidx.core.content.PermissionChecker.PERMISSION_GRANTED, androidx.core.content.PermissionChecker.PERMISSION_DENIED, androidx.core.content.PermissionChecker.PERMISSION_DENIED_APP_OP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PermissionChecker.PermissionResult {
+ }
+
+ @Deprecated public final class SharedPreferencesCompat {
+ }
+
+ @Deprecated public static final class SharedPreferencesCompat.EditorCompat {
+ method @Deprecated public void apply(android.content.SharedPreferences.Editor);
+ method @Deprecated public static androidx.core.content.SharedPreferencesCompat.EditorCompat! getInstance();
+ }
+
+ public class UnusedAppRestrictionsBackportCallback {
+ method public void onResult(boolean, boolean) throws android.os.RemoteException;
+ }
+
+ public abstract class UnusedAppRestrictionsBackportService extends android.app.Service {
+ ctor public UnusedAppRestrictionsBackportService();
+ method protected abstract void isPermissionRevocationEnabled(androidx.core.content.UnusedAppRestrictionsBackportCallback);
+ method public android.os.IBinder? onBind(android.content.Intent?);
+ field public static final String ACTION_UNUSED_APP_RESTRICTIONS_BACKPORT_CONNECTION = "android.support.unusedapprestrictions.action.CustomUnusedAppRestrictionsBackportService";
+ }
+
+ public final class UnusedAppRestrictionsConstants {
+ field public static final int API_30 = 4; // 0x4
+ field public static final int API_30_BACKPORT = 3; // 0x3
+ field public static final int API_31 = 5; // 0x5
+ field public static final int DISABLED = 2; // 0x2
+ field public static final int ERROR = 0; // 0x0
+ field public static final int FEATURE_NOT_AVAILABLE = 1; // 0x1
+ }
+
+ public class UriMatcherCompat {
+ method public static androidx.core.util.Predicate<android.net.Uri!> asPredicate(android.content.UriMatcher);
+ }
+
+}
+
+package androidx.core.content.pm {
+
+ @Deprecated public final class ActivityInfoCompat {
+ field @Deprecated public static final int CONFIG_UI_MODE = 512; // 0x200
+ }
+
+ public final class PackageInfoCompat {
+ method public static long getLongVersionCode(android.content.pm.PackageInfo);
+ method public static java.util.List<android.content.pm.Signature!> getSignatures(android.content.pm.PackageManager, String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static boolean hasSignatures(android.content.pm.PackageManager, String, @Size(min=1) java.util.Map<byte[]!,java.lang.Integer!>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+ }
+
+ public final class PermissionInfoCompat {
+ method public static int getProtection(android.content.pm.PermissionInfo);
+ method public static int getProtectionFlags(android.content.pm.PermissionInfo);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class ShortcutInfoChangeListener {
+ ctor public ShortcutInfoChangeListener();
+ method @AnyThread public void onAllShortcutsRemoved();
+ method @AnyThread public void onShortcutAdded(java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>);
+ method @AnyThread public void onShortcutRemoved(java.util.List<java.lang.String!>);
+ method @AnyThread public void onShortcutUpdated(java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>);
+ method @AnyThread public void onShortcutUsageReported(java.util.List<java.lang.String!>);
+ }
+
+ public class ShortcutInfoCompat {
+ method public android.content.ComponentName? getActivity();
+ method public java.util.Set<java.lang.String!>? getCategories();
+ method public CharSequence? getDisabledMessage();
+ method public int getDisabledReason();
+ method @androidx.core.content.pm.ShortcutInfoCompat.Surface public int getExcludedFromSurfaces();
+ method public android.os.PersistableBundle? getExtras();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.core.graphics.drawable.IconCompat! getIcon();
+ method public String getId();
+ method public android.content.Intent getIntent();
+ method public android.content.Intent![] getIntents();
+ method public long getLastChangedTimestamp();
+ method public androidx.core.content.LocusIdCompat? getLocusId();
+ method public CharSequence? getLongLabel();
+ method public String getPackage();
+ method public int getRank();
+ method public CharSequence getShortLabel();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.Bundle? getTransientExtras();
+ method public android.os.UserHandle? getUserHandle();
+ method public boolean hasKeyFieldsOnly();
+ method public boolean isCached();
+ method public boolean isDeclaredInManifest();
+ method public boolean isDynamic();
+ method public boolean isEnabled();
+ method public boolean isExcludedFromSurfaces(@androidx.core.content.pm.ShortcutInfoCompat.Surface int);
+ method public boolean isImmutable();
+ method public boolean isPinned();
+ method @RequiresApi(25) public android.content.pm.ShortcutInfo! toShortcutInfo();
+ field public static final int SURFACE_LAUNCHER = 1; // 0x1
+ }
+
+ public static class ShortcutInfoCompat.Builder {
+ ctor @RequiresApi(25) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public ShortcutInfoCompat.Builder(android.content.Context, android.content.pm.ShortcutInfo);
+ ctor public ShortcutInfoCompat.Builder(android.content.Context, String);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public ShortcutInfoCompat.Builder(androidx.core.content.pm.ShortcutInfoCompat);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder addCapabilityBinding(String);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder addCapabilityBinding(String, String, java.util.List<java.lang.String!>);
+ method public androidx.core.content.pm.ShortcutInfoCompat build();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setActivity(android.content.ComponentName);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setAlwaysBadged();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setCategories(java.util.Set<java.lang.String!>);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setDisabledMessage(CharSequence);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setExcludedFromSurfaces(int);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setExtras(android.os.PersistableBundle);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIcon(androidx.core.graphics.drawable.IconCompat!);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent![]);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIsConversation();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLocusId(androidx.core.content.LocusIdCompat?);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
+ method @Deprecated public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived(boolean);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person![]);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setRank(int);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setShortLabel(CharSequence);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setSliceUri(android.net.Uri);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.core.content.pm.ShortcutInfoCompat.Builder setTransientExtras(android.os.Bundle);
+ }
+
+ @IntDef(flag=true, value={androidx.core.content.pm.ShortcutInfoCompat.SURFACE_LAUNCHER}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ShortcutInfoCompat.Surface {
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class ShortcutInfoCompatSaver<T> {
+ ctor public ShortcutInfoCompatSaver();
+ method @AnyThread public abstract T! addShortcuts(java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>!);
+ method @WorkerThread public java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>! getShortcuts() throws java.lang.Exception;
+ method @AnyThread public abstract T! removeAllShortcuts();
+ method @AnyThread public abstract T! removeShortcuts(java.util.List<java.lang.String!>!);
+ }
+
+ public class ShortcutManagerCompat {
+ method public static boolean addDynamicShortcuts(android.content.Context, java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>);
+ method public static android.content.Intent createShortcutResultIntent(android.content.Context, androidx.core.content.pm.ShortcutInfoCompat);
+ method public static void disableShortcuts(android.content.Context, java.util.List<java.lang.String!>, CharSequence?);
+ method public static void enableShortcuts(android.content.Context, java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>);
+ method public static java.util.List<androidx.core.content.pm.ShortcutInfoCompat!> getDynamicShortcuts(android.content.Context);
+ method public static int getIconMaxHeight(android.content.Context);
+ method public static int getIconMaxWidth(android.content.Context);
+ method public static int getMaxShortcutCountPerActivity(android.content.Context);
+ method public static java.util.List<androidx.core.content.pm.ShortcutInfoCompat!> getShortcuts(android.content.Context, @androidx.core.content.pm.ShortcutManagerCompat.ShortcutMatchFlags int);
+ method public static boolean isRateLimitingActive(android.content.Context);
+ method public static boolean isRequestPinShortcutSupported(android.content.Context);
+ method public static boolean pushDynamicShortcut(android.content.Context, androidx.core.content.pm.ShortcutInfoCompat);
+ method public static void removeAllDynamicShortcuts(android.content.Context);
+ method public static void removeDynamicShortcuts(android.content.Context, java.util.List<java.lang.String!>);
+ method public static void removeLongLivedShortcuts(android.content.Context, java.util.List<java.lang.String!>);
+ method public static void reportShortcutUsed(android.content.Context, String);
+ method public static boolean requestPinShortcut(android.content.Context, androidx.core.content.pm.ShortcutInfoCompat, android.content.IntentSender?);
+ method public static boolean setDynamicShortcuts(android.content.Context, java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>);
+ method public static boolean updateShortcuts(android.content.Context, java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>);
+ field public static final String EXTRA_SHORTCUT_ID = "android.intent.extra.shortcut.ID";
+ field public static final int FLAG_MATCH_CACHED = 8; // 0x8
+ field public static final int FLAG_MATCH_DYNAMIC = 2; // 0x2
+ field public static final int FLAG_MATCH_MANIFEST = 1; // 0x1
+ field public static final int FLAG_MATCH_PINNED = 4; // 0x4
+ }
+
+ @IntDef(flag=true, value={androidx.core.content.pm.ShortcutManagerCompat.FLAG_MATCH_MANIFEST, androidx.core.content.pm.ShortcutManagerCompat.FLAG_MATCH_DYNAMIC, androidx.core.content.pm.ShortcutManagerCompat.FLAG_MATCH_PINNED, androidx.core.content.pm.ShortcutManagerCompat.FLAG_MATCH_CACHED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ShortcutManagerCompat.ShortcutMatchFlags {
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class ShortcutXmlParser {
+ method @WorkerThread public static java.util.List<java.lang.String!> getShortcutIds(android.content.Context);
+ method @VisibleForTesting public static java.util.List<java.lang.String!> parseShortcutIds(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ }
+
+}
+
+package androidx.core.content.res {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CamColor {
+ method public static void getM3HCTfromColor(@ColorInt int, @Size(3) float[]);
+ method public static int toColor(@FloatRange(from=0.0, to=360.0) float, @FloatRange(from=0.0, to=java.lang.Double.POSITIVE_INFINITY, toInclusive=false) float, @FloatRange(from=0.0, to=100.0) float);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ColorStateListInflaterCompat {
+ method public static android.content.res.ColorStateList createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static android.content.res.ColorStateList createFromXmlInner(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static android.content.res.ColorStateList? inflate(android.content.res.Resources, @XmlRes int, android.content.res.Resources.Theme?);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ComplexColorCompat {
+ method @ColorInt public int getColor();
+ method public android.graphics.Shader? getShader();
+ method public static androidx.core.content.res.ComplexColorCompat? inflate(android.content.res.Resources, @ColorRes int, android.content.res.Resources.Theme?);
+ method public boolean isGradient();
+ method public boolean isStateful();
+ method public boolean onStateChanged(int[]!);
+ method public void setColor(@ColorInt int);
+ method public boolean willDraw();
+ }
+
+ public final class ConfigurationHelper {
+ method public static int getDensityDpi(android.content.res.Resources);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FontResourcesParserCompat {
+ method public static androidx.core.content.res.FontResourcesParserCompat.FamilyResourceEntry? parse(org.xmlpull.v1.XmlPullParser, android.content.res.Resources) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static java.util.List<java.util.List<byte[]!>!> readCerts(android.content.res.Resources, @ArrayRes int);
+ field public static final int FETCH_STRATEGY_ASYNC = 1; // 0x1
+ field public static final int FETCH_STRATEGY_BLOCKING = 0; // 0x0
+ field public static final int INFINITE_TIMEOUT_VALUE = -1; // 0xffffffff
+ }
+
+ public static interface FontResourcesParserCompat.FamilyResourceEntry {
+ }
+
+ @IntDef({androidx.core.content.res.FontResourcesParserCompat.FETCH_STRATEGY_BLOCKING, androidx.core.content.res.FontResourcesParserCompat.FETCH_STRATEGY_ASYNC}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface FontResourcesParserCompat.FetchStrategy {
+ }
+
+ public static final class FontResourcesParserCompat.FontFamilyFilesResourceEntry implements androidx.core.content.res.FontResourcesParserCompat.FamilyResourceEntry {
+ ctor public FontResourcesParserCompat.FontFamilyFilesResourceEntry(androidx.core.content.res.FontResourcesParserCompat.FontFileResourceEntry![]);
+ method public androidx.core.content.res.FontResourcesParserCompat.FontFileResourceEntry![] getEntries();
+ }
+
+ public static final class FontResourcesParserCompat.FontFileResourceEntry {
+ ctor public FontResourcesParserCompat.FontFileResourceEntry(String, int, boolean, String?, int, int);
+ method public String getFileName();
+ method public int getResourceId();
+ method public int getTtcIndex();
+ method public String? getVariationSettings();
+ method public int getWeight();
+ method public boolean isItalic();
+ }
+
+ public static final class FontResourcesParserCompat.ProviderResourceEntry implements androidx.core.content.res.FontResourcesParserCompat.FamilyResourceEntry {
+ ctor public FontResourcesParserCompat.ProviderResourceEntry(androidx.core.provider.FontRequest, @androidx.core.content.res.FontResourcesParserCompat.FetchStrategy int, int);
+ method @androidx.core.content.res.FontResourcesParserCompat.FetchStrategy public int getFetchStrategy();
+ method public androidx.core.provider.FontRequest getRequest();
+ method public int getTimeout();
+ }
+
+ public final class ResourcesCompat {
+ method public static void clearCachesForTheme(android.content.res.Resources.Theme);
+ method public static android.graphics.Typeface? getCachedFont(android.content.Context, @FontRes int) throws android.content.res.Resources.NotFoundException;
+ method @ColorInt public static int getColor(android.content.res.Resources, @ColorRes int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static android.content.res.ColorStateList? getColorStateList(android.content.res.Resources, @ColorRes int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static android.graphics.drawable.Drawable? getDrawable(android.content.res.Resources, @DrawableRes int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static android.graphics.drawable.Drawable? getDrawableForDensity(android.content.res.Resources, @DrawableRes int, int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static float getFloat(android.content.res.Resources, @DimenRes int);
+ method public static android.graphics.Typeface? getFont(android.content.Context, @FontRes int) throws android.content.res.Resources.NotFoundException;
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface? getFont(android.content.Context, @FontRes int, android.util.TypedValue, int, androidx.core.content.res.ResourcesCompat.FontCallback?) throws android.content.res.Resources.NotFoundException;
+ method public static void getFont(android.content.Context, @FontRes int, androidx.core.content.res.ResourcesCompat.FontCallback, android.os.Handler?) throws android.content.res.Resources.NotFoundException;
+ field @AnyRes public static final int ID_NULL = 0; // 0x0
+ }
+
+ public abstract static class ResourcesCompat.FontCallback {
+ ctor public ResourcesCompat.FontCallback();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final void callbackFailAsync(@androidx.core.provider.FontsContractCompat.FontRequestCallback.FontRequestFailReason int, android.os.Handler?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final void callbackSuccessAsync(android.graphics.Typeface, android.os.Handler?);
+ method public abstract void onFontRetrievalFailed(@androidx.core.provider.FontsContractCompat.FontRequestCallback.FontRequestFailReason int);
+ method public abstract void onFontRetrieved(android.graphics.Typeface);
+ }
+
+ public static final class ResourcesCompat.ThemeCompat {
+ method public static void rebase(android.content.res.Resources.Theme);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class TypedArrayUtils {
+ method public static int getAttr(android.content.Context, int, int);
+ method public static boolean getBoolean(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int, boolean);
+ method public static android.graphics.drawable.Drawable? getDrawable(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int);
+ method public static int getInt(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int, int);
+ method public static boolean getNamedBoolean(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int, boolean);
+ method @ColorInt public static int getNamedColor(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int, @ColorInt int);
+ method public static android.content.res.ColorStateList? getNamedColorStateList(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme?, String, @StyleableRes int);
+ method public static androidx.core.content.res.ComplexColorCompat! getNamedComplexColor(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme?, String, @StyleableRes int, @ColorInt int);
+ method public static float getNamedFloat(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int, float);
+ method public static int getNamedInt(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int, int);
+ method @AnyRes public static int getNamedResourceId(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int, @AnyRes int);
+ method public static String? getNamedString(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int);
+ method @AnyRes public static int getResourceId(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int, @AnyRes int);
+ method public static String? getString(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int);
+ method public static CharSequence? getText(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int);
+ method public static CharSequence![]? getTextArray(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int);
+ method public static boolean hasAttribute(org.xmlpull.v1.XmlPullParser, String);
+ method public static android.content.res.TypedArray obtainAttributes(android.content.res.Resources, android.content.res.Resources.Theme?, android.util.AttributeSet, int[]);
+ method public static android.util.TypedValue? peekNamedValue(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, int);
+ }
+
+}
+
+package androidx.core.database {
+
+ public final class CursorWindowCompat {
+ method public static android.database.CursorWindow create(String?, long);
+ }
+
+ @Deprecated public final class DatabaseUtilsCompat {
+ method @Deprecated public static String![]! appendSelectionArgs(String![]!, String![]!);
+ method @Deprecated public static String! concatenateWhere(String!, String!);
+ }
+
+}
+
+package androidx.core.database.sqlite {
+
+ public final class SQLiteCursorCompat {
+ method public static void setFillWindowForwardOnly(android.database.sqlite.SQLiteCursor, boolean);
+ }
+
+}
+
+package androidx.core.graphics {
+
+ public final class BitmapCompat {
+ method public static android.graphics.Bitmap createScaledBitmap(android.graphics.Bitmap, int, int, android.graphics.Rect?, boolean);
+ method public static int getAllocationByteCount(android.graphics.Bitmap);
+ method public static boolean hasMipMap(android.graphics.Bitmap);
+ method public static void setHasMipMap(android.graphics.Bitmap, boolean);
+ }
+
+ public class BlendModeColorFilterCompat {
+ method public static android.graphics.ColorFilter? createBlendModeColorFilterCompat(int, androidx.core.graphics.BlendModeCompat);
+ }
+
+ public enum BlendModeCompat {
+ enum_constant public static final androidx.core.graphics.BlendModeCompat CLEAR;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat COLOR;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat COLOR_BURN;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat COLOR_DODGE;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DARKEN;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat DIFFERENCE;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DST;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DST_ATOP;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DST_IN;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DST_OUT;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat DST_OVER;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat EXCLUSION;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat HARD_LIGHT;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat HUE;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat LIGHTEN;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat LUMINOSITY;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat MODULATE;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat MULTIPLY;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat OVERLAY;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat PLUS;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat SATURATION;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SCREEN;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.Q) public static final androidx.core.graphics.BlendModeCompat SOFT_LIGHT;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SRC;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SRC_ATOP;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SRC_IN;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SRC_OUT;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat SRC_OVER;
+ enum_constant public static final androidx.core.graphics.BlendModeCompat XOR;
+ }
+
+ public final class ColorUtils {
+ method @ColorInt public static int HSLToColor(float[]);
+ method @ColorInt public static int LABToColor(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double);
+ method public static void LABToXYZ(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double, double[]);
+ method @ColorInt public static int M3HCTToColor(@FloatRange(from=0.0, to=360, toInclusive=false) float, @FloatRange(from=0.0, to=java.lang.Double.POSITIVE_INFINITY, toInclusive=false) float, @FloatRange(from=0.0, to=100) float);
+ method public static void RGBToHSL(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, float[]);
+ method public static void RGBToLAB(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
+ method public static void RGBToXYZ(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
+ method @ColorInt public static int XYZToColor(@FloatRange(from=0.0f, to=95.047) double, @FloatRange(from=0.0f, to=0x64) double, @FloatRange(from=0.0f, to=108.883) double);
+ method public static void XYZToLAB(@FloatRange(from=0.0f, to=95.047) double, @FloatRange(from=0.0f, to=0x64) double, @FloatRange(from=0.0f, to=108.883) double, double[]);
+ method @ColorInt public static int blendARGB(@ColorInt int, @ColorInt int, @FloatRange(from=0.0, to=1.0) float);
+ method public static void blendHSL(float[], float[], @FloatRange(from=0.0, to=1.0) float, float[]);
+ method public static void blendLAB(double[], double[], @FloatRange(from=0.0, to=1.0) double, double[]);
+ method public static double calculateContrast(@ColorInt int, @ColorInt int);
+ method @FloatRange(from=0.0, to=1.0) public static double calculateLuminance(@ColorInt int);
+ method public static int calculateMinimumAlpha(@ColorInt int, @ColorInt int, float);
+ method public static void colorToHSL(@ColorInt int, float[]);
+ method public static void colorToLAB(@ColorInt int, double[]);
+ method public static void colorToM3HCT(@ColorInt int, @Size(3) float[]);
+ method public static void colorToXYZ(@ColorInt int, double[]);
+ method @RequiresApi(26) public static android.graphics.Color compositeColors(android.graphics.Color, android.graphics.Color);
+ method public static int compositeColors(@ColorInt int, @ColorInt int);
+ method public static double distanceEuclidean(double[], double[]);
+ method @ColorInt public static int setAlphaComponent(@ColorInt int, @IntRange(from=0, to=255) int);
+ }
+
+ public final class Insets {
+ method public static androidx.core.graphics.Insets add(androidx.core.graphics.Insets, androidx.core.graphics.Insets);
+ method public static androidx.core.graphics.Insets max(androidx.core.graphics.Insets, androidx.core.graphics.Insets);
+ method public static androidx.core.graphics.Insets min(androidx.core.graphics.Insets, androidx.core.graphics.Insets);
+ method public static androidx.core.graphics.Insets of(android.graphics.Rect);
+ method public static androidx.core.graphics.Insets of(int, int, int, int);
+ method public static androidx.core.graphics.Insets subtract(androidx.core.graphics.Insets, androidx.core.graphics.Insets);
+ method @RequiresApi(api=29) public static androidx.core.graphics.Insets toCompatInsets(android.graphics.Insets);
+ method @RequiresApi(29) public android.graphics.Insets toPlatformInsets();
+ method @Deprecated @RequiresApi(api=29) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.graphics.Insets wrap(android.graphics.Insets);
+ field public static final androidx.core.graphics.Insets NONE;
+ field public final int bottom;
+ field public final int left;
+ field public final int right;
+ field public final int top;
+ }
+
+ public final class PaintCompat {
+ method public static boolean hasGlyph(android.graphics.Paint, String);
+ method public static boolean setBlendMode(android.graphics.Paint, androidx.core.graphics.BlendModeCompat?);
+ }
+
+ public class PathParser {
+ method public static boolean canMorph(androidx.core.graphics.PathParser.PathDataNode![]?, androidx.core.graphics.PathParser.PathDataNode![]?);
+ method public static androidx.core.graphics.PathParser.PathDataNode![] createNodesFromPathData(String);
+ method public static android.graphics.Path createPathFromPathData(String);
+ method public static androidx.core.graphics.PathParser.PathDataNode![] deepCopyNodes(androidx.core.graphics.PathParser.PathDataNode![]);
+ method public static boolean interpolatePathDataNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![], float);
+ method public static void updateNodes(androidx.core.graphics.PathParser.PathDataNode![], androidx.core.graphics.PathParser.PathDataNode![]);
+ }
+
+ public static class PathParser.PathDataNode {
+ method public float[] getParams();
+ method public char getType();
+ method public void interpolatePathDataNode(androidx.core.graphics.PathParser.PathDataNode, androidx.core.graphics.PathParser.PathDataNode, float);
+ method public static void nodesToPath(androidx.core.graphics.PathParser.PathDataNode![], android.graphics.Path);
+ }
+
+ public final class PathSegment {
+ ctor public PathSegment(android.graphics.PointF, float, android.graphics.PointF, float);
+ method public android.graphics.PointF getEnd();
+ method public float getEndFraction();
+ method public android.graphics.PointF getStart();
+ method public float getStartFraction();
+ }
+
+ public final class PathUtils {
+ method @RequiresApi(26) public static java.util.Collection<androidx.core.graphics.PathSegment!> flatten(android.graphics.Path);
+ method @RequiresApi(26) public static java.util.Collection<androidx.core.graphics.PathSegment!> flatten(android.graphics.Path, @FloatRange(from=0) float);
+ }
+
+ public class TypefaceCompat {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @VisibleForTesting public static void clearCache();
+ method public static android.graphics.Typeface create(android.content.Context, android.graphics.Typeface?, int);
+ method public static android.graphics.Typeface create(android.content.Context, android.graphics.Typeface?, @IntRange(from=1, to=1000) int, boolean);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface? createFromFontInfo(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo![], int);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface? createFromResourcesFamilyXml(android.content.Context, androidx.core.content.res.FontResourcesParserCompat.FamilyResourceEntry, android.content.res.Resources, int, int, androidx.core.content.res.ResourcesCompat.FontCallback?, android.os.Handler?, boolean);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface? createFromResourcesFontFile(android.content.Context, android.content.res.Resources, int, String!, int);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface? findFromCache(android.content.res.Resources, int, int);
+ }
+
+ @RequiresApi(26) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class TypefaceCompatApi26Impl {
+ ctor public TypefaceCompatApi26Impl();
+ method protected android.graphics.Typeface? createFromFamiliesWithDefault(Object!);
+ method public android.graphics.Typeface? createFromFontFamilyFilesResourceEntry(android.content.Context!, androidx.core.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry!, android.content.res.Resources!, int);
+ method public android.graphics.Typeface? createFromFontInfo(android.content.Context!, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo![], int);
+ method public android.graphics.Typeface? createFromResourcesFontFile(android.content.Context!, android.content.res.Resources!, int, String!, int);
+ method protected java.lang.reflect.Method! obtainAbortCreationMethod(Class<?>!) throws java.lang.NoSuchMethodException;
+ method protected java.lang.reflect.Method! obtainAddFontFromAssetManagerMethod(Class<?>!) throws java.lang.NoSuchMethodException;
+ method protected java.lang.reflect.Method! obtainAddFontFromBufferMethod(Class<?>!) throws java.lang.NoSuchMethodException;
+ method protected java.lang.reflect.Method! obtainCreateFromFamiliesWithDefaultMethod(Class<?>!) throws java.lang.NoSuchMethodException;
+ method protected Class<?>! obtainFontFamily() throws java.lang.ClassNotFoundException;
+ method protected java.lang.reflect.Constructor<?>! obtainFontFamilyCtor(Class<?>!) throws java.lang.NoSuchMethodException;
+ method protected java.lang.reflect.Method! obtainFreezeMethod(Class<?>!) throws java.lang.NoSuchMethodException;
+ field protected final java.lang.reflect.Method! mAbortCreation;
+ field protected final java.lang.reflect.Method! mAddFontFromAssetManager;
+ field protected final java.lang.reflect.Method! mAddFontFromBuffer;
+ field protected final java.lang.reflect.Method! mCreateFromFamiliesWithDefault;
+ field protected final Class<?>! mFontFamily;
+ field protected final java.lang.reflect.Constructor<?>! mFontFamilyCtor;
+ field protected final java.lang.reflect.Method! mFreeze;
+ }
+
+ @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class TypefaceCompatApi28Impl extends androidx.core.graphics.TypefaceCompatApi26Impl {
+ ctor public TypefaceCompatApi28Impl();
+ }
+
+ @RequiresApi(29) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class TypefaceCompatApi29Impl {
+ ctor public TypefaceCompatApi29Impl();
+ method public android.graphics.Typeface? createFromFontFamilyFilesResourceEntry(android.content.Context!, androidx.core.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry!, android.content.res.Resources!, int);
+ method public android.graphics.Typeface? createFromFontInfo(android.content.Context!, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo![], int);
+ method protected android.graphics.Typeface! createFromInputStream(android.content.Context!, java.io.InputStream!);
+ method public android.graphics.Typeface? createFromResourcesFontFile(android.content.Context!, android.content.res.Resources!, int, String!, int);
+ method protected androidx.core.provider.FontsContractCompat.FontInfo! findBestInfo(androidx.core.provider.FontsContractCompat.FontInfo![]!, int);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class TypefaceCompatUtil {
+ method public static void closeQuietly(java.io.Closeable?);
+ method public static java.nio.ByteBuffer? copyToDirectBuffer(android.content.Context, android.content.res.Resources, int);
+ method public static boolean copyToFile(java.io.File, android.content.res.Resources, int);
+ method public static boolean copyToFile(java.io.File, java.io.InputStream);
+ method public static java.io.File? getTempFile(android.content.Context);
+ method public static java.nio.ByteBuffer? mmap(android.content.Context, android.os.CancellationSignal?, android.net.Uri);
+ }
+
+}
+
+package androidx.core.graphics.drawable {
+
+ public final class DrawableCompat {
+ method public static void applyTheme(android.graphics.drawable.Drawable, android.content.res.Resources.Theme);
+ method public static boolean canApplyTheme(android.graphics.drawable.Drawable);
+ method public static void clearColorFilter(android.graphics.drawable.Drawable);
+ method public static int getAlpha(android.graphics.drawable.Drawable);
+ method public static android.graphics.ColorFilter? getColorFilter(android.graphics.drawable.Drawable);
+ method public static int getLayoutDirection(android.graphics.drawable.Drawable);
+ method public static void inflate(android.graphics.drawable.Drawable, android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static boolean isAutoMirrored(android.graphics.drawable.Drawable);
+ method @Deprecated public static void jumpToCurrentState(android.graphics.drawable.Drawable);
+ method public static void setAutoMirrored(android.graphics.drawable.Drawable, boolean);
+ method public static void setHotspot(android.graphics.drawable.Drawable, float, float);
+ method public static void setHotspotBounds(android.graphics.drawable.Drawable, int, int, int, int);
+ method public static boolean setLayoutDirection(android.graphics.drawable.Drawable, int);
+ method public static void setTint(android.graphics.drawable.Drawable, @ColorInt int);
+ method public static void setTintList(android.graphics.drawable.Drawable, android.content.res.ColorStateList?);
+ method public static void setTintMode(android.graphics.drawable.Drawable, android.graphics.PorterDuff.Mode?);
+ method public static <T extends android.graphics.drawable.Drawable> T! unwrap(android.graphics.drawable.Drawable);
+ method public static android.graphics.drawable.Drawable wrap(android.graphics.drawable.Drawable);
+ }
+
+ @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true, ignoreParcelables=true, isCustom=true, jetifyAs="android.support.v4.graphics.drawable.IconCompat") public class IconCompat extends androidx.versionedparcelable.CustomVersionedParcelable {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addToShortcutIntent(android.content.Intent, android.graphics.drawable.Drawable?, android.content.Context);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void checkResource(android.content.Context);
+ method public static androidx.core.graphics.drawable.IconCompat? createFromBundle(android.os.Bundle);
+ method @RequiresApi(23) public static androidx.core.graphics.drawable.IconCompat? createFromIcon(android.content.Context, android.graphics.drawable.Icon);
+ method @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.graphics.drawable.IconCompat? createFromIcon(android.graphics.drawable.Icon);
+ method @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.graphics.drawable.IconCompat? createFromIconOrNullIfZeroResId(android.graphics.drawable.Icon);
+ method public static androidx.core.graphics.drawable.IconCompat createWithAdaptiveBitmap(android.graphics.Bitmap);
+ method public static androidx.core.graphics.drawable.IconCompat createWithAdaptiveBitmapContentUri(android.net.Uri);
+ method public static androidx.core.graphics.drawable.IconCompat createWithAdaptiveBitmapContentUri(String);
+ method public static androidx.core.graphics.drawable.IconCompat createWithBitmap(android.graphics.Bitmap);
+ method public static androidx.core.graphics.drawable.IconCompat createWithContentUri(android.net.Uri);
+ method public static androidx.core.graphics.drawable.IconCompat createWithContentUri(String);
+ method public static androidx.core.graphics.drawable.IconCompat createWithData(byte[], int, int);
+ method public static androidx.core.graphics.drawable.IconCompat createWithResource(android.content.Context, @DrawableRes int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.graphics.drawable.IconCompat createWithResource(android.content.res.Resources?, String, @DrawableRes int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.Bitmap? getBitmap();
+ method @DrawableRes public int getResId();
+ method public String getResPackage();
+ method public int getType();
+ method public android.net.Uri getUri();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public java.io.InputStream? getUriInputStream(android.content.Context);
+ method public android.graphics.drawable.Drawable? loadDrawable(android.content.Context);
+ method public androidx.core.graphics.drawable.IconCompat setTint(@ColorInt int);
+ method public androidx.core.graphics.drawable.IconCompat setTintList(android.content.res.ColorStateList?);
+ method public androidx.core.graphics.drawable.IconCompat setTintMode(android.graphics.PorterDuff.Mode?);
+ method public android.os.Bundle toBundle();
+ method @Deprecated @RequiresApi(23) public android.graphics.drawable.Icon toIcon();
+ method @RequiresApi(23) public android.graphics.drawable.Icon toIcon(android.content.Context?);
+ field public static final int TYPE_ADAPTIVE_BITMAP = 5; // 0x5
+ field public static final int TYPE_BITMAP = 1; // 0x1
+ field public static final int TYPE_DATA = 3; // 0x3
+ field public static final int TYPE_RESOURCE = 2; // 0x2
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ field public static final int TYPE_URI = 4; // 0x4
+ field public static final int TYPE_URI_ADAPTIVE_BITMAP = 6; // 0x6
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.ParcelField(value=1, defaultValue="androidx.core.graphics.drawable.IconCompat.TYPE_UNKNOWN") public int mType;
+ }
+
+ public abstract class RoundedBitmapDrawable extends android.graphics.drawable.Drawable {
+ method public void draw(android.graphics.Canvas);
+ method public final android.graphics.Bitmap? getBitmap();
+ method public float getCornerRadius();
+ method public int getGravity();
+ method public int getOpacity();
+ method public final android.graphics.Paint getPaint();
+ method public boolean hasAntiAlias();
+ method public boolean hasMipMap();
+ method public boolean isCircular();
+ method public void setAlpha(int);
+ method public void setAntiAlias(boolean);
+ method public void setCircular(boolean);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setCornerRadius(float);
+ method public void setDither(boolean);
+ method public void setGravity(int);
+ method public void setMipMap(boolean);
+ method public void setTargetDensity(android.graphics.Canvas);
+ method public void setTargetDensity(android.util.DisplayMetrics);
+ method public void setTargetDensity(int);
+ }
+
+ public final class RoundedBitmapDrawableFactory {
+ method public static androidx.core.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, android.graphics.Bitmap?);
+ method public static androidx.core.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, java.io.InputStream);
+ method public static androidx.core.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, String);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface TintAwareDrawable {
+ method public void setTint(@ColorInt int);
+ method public void setTintList(android.content.res.ColorStateList!);
+ method public void setTintMode(android.graphics.PorterDuff.Mode!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface WrappedDrawable {
+ method public android.graphics.drawable.Drawable! getWrappedDrawable();
+ method public void setWrappedDrawable(android.graphics.drawable.Drawable!);
+ }
+
+}
+
+package androidx.core.hardware.display {
+
+ public final class DisplayManagerCompat {
+ method public android.view.Display? getDisplay(int);
+ method public android.view.Display![] getDisplays();
+ method public android.view.Display![] getDisplays(String?);
+ method public static androidx.core.hardware.display.DisplayManagerCompat getInstance(android.content.Context);
+ field public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
+ }
+
+}
+
+package androidx.core.hardware.fingerprint {
+
+ @Deprecated public class FingerprintManagerCompat {
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public void authenticate(androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject?, int, android.os.CancellationSignal?, androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback, android.os.Handler?);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public void authenticate(androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject?, int, androidx.core.os.CancellationSignal?, androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback, android.os.Handler?);
+ method @Deprecated public static androidx.core.hardware.fingerprint.FingerprintManagerCompat from(android.content.Context);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
+ }
+
+ @Deprecated public abstract static class FingerprintManagerCompat.AuthenticationCallback {
+ ctor @Deprecated public FingerprintManagerCompat.AuthenticationCallback();
+ method @Deprecated public void onAuthenticationError(int, CharSequence!);
+ method @Deprecated public void onAuthenticationFailed();
+ method @Deprecated public void onAuthenticationHelp(int, CharSequence!);
+ method @Deprecated public void onAuthenticationSucceeded(androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationResult!);
+ }
+
+ @Deprecated public static final class FingerprintManagerCompat.AuthenticationResult {
+ ctor @Deprecated public FingerprintManagerCompat.AuthenticationResult(androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject!);
+ method @Deprecated public androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject! getCryptoObject();
+ }
+
+ @Deprecated public static class FingerprintManagerCompat.CryptoObject {
+ ctor @Deprecated public FingerprintManagerCompat.CryptoObject(java.security.Signature);
+ ctor @Deprecated public FingerprintManagerCompat.CryptoObject(javax.crypto.Cipher);
+ ctor @Deprecated public FingerprintManagerCompat.CryptoObject(javax.crypto.Mac);
+ method @Deprecated public javax.crypto.Cipher? getCipher();
+ method @Deprecated public javax.crypto.Mac? getMac();
+ method @Deprecated public java.security.Signature? getSignature();
+ }
+
+}
+
+package androidx.core.internal.view {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface SupportMenu extends android.view.Menu {
+ method public void setGroupDividerEnabled(boolean);
+ field public static final int CATEGORY_MASK = -65536; // 0xffff0000
+ field public static final int CATEGORY_SHIFT = 16; // 0x10
+ field public static final int FLAG_KEEP_OPEN_ON_SUBMENU_OPENED = 4; // 0x4
+ field public static final int SUPPORTED_MODIFIERS_MASK = 69647; // 0x1100f
+ field public static final int USER_MASK = 65535; // 0xffff
+ field public static final int USER_SHIFT = 0; // 0x0
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface SupportMenuItem extends android.view.MenuItem {
+ method public int getAlphabeticModifiers();
+ method public CharSequence? getContentDescription();
+ method public android.content.res.ColorStateList? getIconTintList();
+ method public android.graphics.PorterDuff.Mode? getIconTintMode();
+ method public int getNumericModifiers();
+ method public androidx.core.view.ActionProvider? getSupportActionProvider();
+ method public CharSequence? getTooltipText();
+ method public boolean requiresActionButton();
+ method public boolean requiresOverflow();
+ method public android.view.MenuItem setAlphabeticShortcut(char, int);
+ method public androidx.core.internal.view.SupportMenuItem setContentDescription(CharSequence?);
+ method public android.view.MenuItem setIconTintList(android.content.res.ColorStateList?);
+ method public android.view.MenuItem setIconTintMode(android.graphics.PorterDuff.Mode?);
+ method public android.view.MenuItem setNumericShortcut(char, int);
+ method public android.view.MenuItem setShortcut(char, char, int, int);
+ method public androidx.core.internal.view.SupportMenuItem setSupportActionProvider(androidx.core.view.ActionProvider?);
+ method public androidx.core.internal.view.SupportMenuItem setTooltipText(CharSequence?);
+ field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
+ field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
+ field public static final int SHOW_AS_ACTION_IF_ROOM = 1; // 0x1
+ field public static final int SHOW_AS_ACTION_NEVER = 0; // 0x0
+ field public static final int SHOW_AS_ACTION_WITH_TEXT = 4; // 0x4
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface SupportSubMenu extends androidx.core.internal.view.SupportMenu android.view.SubMenu {
+ }
+
+}
+
+package androidx.core.location {
+
+ public abstract class GnssStatusCompat {
+ method @FloatRange(from=0, to=360) public abstract float getAzimuthDegrees(@IntRange(from=0) int);
+ method @FloatRange(from=0, to=63) public abstract float getBasebandCn0DbHz(@IntRange(from=0) int);
+ method @FloatRange(from=0) public abstract float getCarrierFrequencyHz(@IntRange(from=0) int);
+ method @FloatRange(from=0, to=63) public abstract float getCn0DbHz(@IntRange(from=0) int);
+ method public abstract int getConstellationType(@IntRange(from=0) int);
+ method @FloatRange(from=0xffffffa6, to=90) public abstract float getElevationDegrees(@IntRange(from=0) int);
+ method @IntRange(from=0) public abstract int getSatelliteCount();
+ method @IntRange(from=1, to=200) public abstract int getSvid(@IntRange(from=0) int);
+ method public abstract boolean hasAlmanacData(@IntRange(from=0) int);
+ method public abstract boolean hasBasebandCn0DbHz(@IntRange(from=0) int);
+ method public abstract boolean hasCarrierFrequencyHz(@IntRange(from=0) int);
+ method public abstract boolean hasEphemerisData(@IntRange(from=0) int);
+ method public abstract boolean usedInFix(@IntRange(from=0) int);
+ method @RequiresApi(android.os.Build.VERSION_CODES.N) public static androidx.core.location.GnssStatusCompat wrap(android.location.GnssStatus);
+ method public static androidx.core.location.GnssStatusCompat wrap(android.location.GpsStatus);
+ field public static final int CONSTELLATION_BEIDOU = 5; // 0x5
+ field public static final int CONSTELLATION_GALILEO = 6; // 0x6
+ field public static final int CONSTELLATION_GLONASS = 3; // 0x3
+ field public static final int CONSTELLATION_GPS = 1; // 0x1
+ field public static final int CONSTELLATION_IRNSS = 7; // 0x7
+ field public static final int CONSTELLATION_QZSS = 4; // 0x4
+ field public static final int CONSTELLATION_SBAS = 2; // 0x2
+ field public static final int CONSTELLATION_UNKNOWN = 0; // 0x0
+ }
+
+ public abstract static class GnssStatusCompat.Callback {
+ ctor public GnssStatusCompat.Callback();
+ method public void onFirstFix(@IntRange(from=0) int);
+ method public void onSatelliteStatusChanged(androidx.core.location.GnssStatusCompat);
+ method public void onStarted();
+ method public void onStopped();
+ }
+
+ public final class LocationCompat {
+ method public static float getBearingAccuracyDegrees(android.location.Location);
+ method public static long getElapsedRealtimeMillis(android.location.Location);
+ method public static long getElapsedRealtimeNanos(android.location.Location);
+ method @FloatRange(from=0.0) public static float getMslAltitudeAccuracyMeters(android.location.Location);
+ method public static double getMslAltitudeMeters(android.location.Location);
+ method public static float getSpeedAccuracyMetersPerSecond(android.location.Location);
+ method public static float getVerticalAccuracyMeters(android.location.Location);
+ method public static boolean hasBearingAccuracy(android.location.Location);
+ method public static boolean hasMslAltitude(android.location.Location);
+ method public static boolean hasMslAltitudeAccuracy(android.location.Location);
+ method public static boolean hasSpeedAccuracy(android.location.Location);
+ method public static boolean hasVerticalAccuracy(android.location.Location);
+ method public static boolean isMock(android.location.Location);
+ method public static void removeBearingAccuracy(android.location.Location);
+ method public static void removeMslAltitude(android.location.Location);
+ method public static void removeMslAltitudeAccuracy(android.location.Location);
+ method public static void removeSpeedAccuracy(android.location.Location);
+ method public static void removeVerticalAccuracy(android.location.Location);
+ method public static void setBearingAccuracyDegrees(android.location.Location, float);
+ method public static void setMock(android.location.Location, boolean);
+ method public static void setMslAltitudeAccuracyMeters(android.location.Location, @FloatRange(from=0.0) float);
+ method public static void setMslAltitudeMeters(android.location.Location, double);
+ method public static void setSpeedAccuracyMetersPerSecond(android.location.Location, float);
+ method public static void setVerticalAccuracyMeters(android.location.Location, float);
+ field public static final String EXTRA_BEARING_ACCURACY = "bearingAccuracy";
+ field public static final String EXTRA_IS_MOCK = "mockLocation";
+ field public static final String EXTRA_MSL_ALTITUDE = "androidx.core.location.extra.MSL_ALTITUDE";
+ field public static final String EXTRA_MSL_ALTITUDE_ACCURACY = "androidx.core.location.extra.MSL_ALTITUDE_ACCURACY";
+ field public static final String EXTRA_SPEED_ACCURACY = "speedAccuracy";
+ field public static final String EXTRA_VERTICAL_ACCURACY = "verticalAccuracy";
+ }
+
+ public interface LocationListenerCompat extends android.location.LocationListener {
+ method public default void onStatusChanged(String, int, android.os.Bundle?);
+ }
+
+ public final class LocationManagerCompat {
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static void getCurrentLocation(android.location.LocationManager, String, android.os.CancellationSignal?, java.util.concurrent.Executor, androidx.core.util.Consumer<android.location.Location!>);
+ method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static void getCurrentLocation(android.location.LocationManager, String, androidx.core.os.CancellationSignal?, java.util.concurrent.Executor, androidx.core.util.Consumer<android.location.Location!>);
+ method public static String? getGnssHardwareModelName(android.location.LocationManager);
+ method public static int getGnssYearOfHardware(android.location.LocationManager);
+ method public static boolean hasProvider(android.location.LocationManager, String);
+ method public static boolean isLocationEnabled(android.location.LocationManager);
+ method @RequiresApi(android.os.Build.VERSION_CODES.N) @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static boolean registerGnssMeasurementsCallback(android.location.LocationManager, android.location.GnssMeasurementsEvent.Callback, android.os.Handler);
+ method @RequiresApi(android.os.Build.VERSION_CODES.N) @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static boolean registerGnssMeasurementsCallback(android.location.LocationManager, java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static boolean registerGnssStatusCallback(android.location.LocationManager, androidx.core.location.GnssStatusCompat.Callback, android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static boolean registerGnssStatusCallback(android.location.LocationManager, java.util.concurrent.Executor, androidx.core.location.GnssStatusCompat.Callback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static void removeUpdates(android.location.LocationManager, androidx.core.location.LocationListenerCompat);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static void requestLocationUpdates(android.location.LocationManager, String, androidx.core.location.LocationRequestCompat, androidx.core.location.LocationListenerCompat, android.os.Looper);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static void requestLocationUpdates(android.location.LocationManager, String, androidx.core.location.LocationRequestCompat, java.util.concurrent.Executor, androidx.core.location.LocationListenerCompat);
+ method @RequiresApi(android.os.Build.VERSION_CODES.N) public static void unregisterGnssMeasurementsCallback(android.location.LocationManager, android.location.GnssMeasurementsEvent.Callback);
+ method public static void unregisterGnssStatusCallback(android.location.LocationManager, androidx.core.location.GnssStatusCompat.Callback);
+ }
+
+ public final class LocationRequestCompat {
+ method @IntRange(from=1) public long getDurationMillis();
+ method @IntRange(from=0) public long getIntervalMillis();
+ method @IntRange(from=0) public long getMaxUpdateDelayMillis();
+ method @IntRange(from=1, to=java.lang.Integer.MAX_VALUE) public int getMaxUpdates();
+ method @FloatRange(from=0, to=java.lang.Float.MAX_VALUE) public float getMinUpdateDistanceMeters();
+ method @IntRange(from=0) public long getMinUpdateIntervalMillis();
+ method public int getQuality();
+ method @RequiresApi(31) public android.location.LocationRequest toLocationRequest();
+ method public android.location.LocationRequest? toLocationRequest(String);
+ field public static final long PASSIVE_INTERVAL = 9223372036854775807L; // 0x7fffffffffffffffL
+ field public static final int QUALITY_BALANCED_POWER_ACCURACY = 102; // 0x66
+ field public static final int QUALITY_HIGH_ACCURACY = 100; // 0x64
+ field public static final int QUALITY_LOW_POWER = 104; // 0x68
+ }
+
+ public static final class LocationRequestCompat.Builder {
+ ctor public LocationRequestCompat.Builder(androidx.core.location.LocationRequestCompat);
+ ctor public LocationRequestCompat.Builder(long);
+ method public androidx.core.location.LocationRequestCompat build();
+ method public androidx.core.location.LocationRequestCompat.Builder clearMinUpdateIntervalMillis();
+ method public androidx.core.location.LocationRequestCompat.Builder setDurationMillis(@IntRange(from=1) long);
+ method public androidx.core.location.LocationRequestCompat.Builder setIntervalMillis(@IntRange(from=0) long);
+ method public androidx.core.location.LocationRequestCompat.Builder setMaxUpdateDelayMillis(@IntRange(from=0) long);
+ method public androidx.core.location.LocationRequestCompat.Builder setMaxUpdates(@IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int);
+ method public androidx.core.location.LocationRequestCompat.Builder setMinUpdateDistanceMeters(@FloatRange(from=0, to=java.lang.Float.MAX_VALUE) float);
+ method public androidx.core.location.LocationRequestCompat.Builder setMinUpdateIntervalMillis(@IntRange(from=0) long);
+ method public androidx.core.location.LocationRequestCompat.Builder setQuality(int);
+ }
+
+}
+
+package androidx.core.math {
+
+ public class MathUtils {
+ method public static int addExact(int, int);
+ method public static long addExact(long, long);
+ method public static double clamp(double, double, double);
+ method public static float clamp(float, float, float);
+ method public static int clamp(int, int, int);
+ method public static long clamp(long, long, long);
+ method public static int decrementExact(int);
+ method public static long decrementExact(long);
+ method public static int incrementExact(int);
+ method public static long incrementExact(long);
+ method public static int multiplyExact(int, int);
+ method public static long multiplyExact(long, long);
+ method public static int negateExact(int);
+ method public static long negateExact(long);
+ method public static int subtractExact(int, int);
+ method public static long subtractExact(long, long);
+ method public static int toIntExact(long);
+ }
+
+}
+
+package androidx.core.net {
+
+ public final class ConnectivityManagerCompat {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public static android.net.NetworkInfo? getNetworkInfoFromBroadcast(android.net.ConnectivityManager, android.content.Intent);
+ method @androidx.core.net.ConnectivityManagerCompat.RestrictBackgroundStatus public static int getRestrictBackgroundStatus(android.net.ConnectivityManager);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public static boolean isActiveNetworkMetered(android.net.ConnectivityManager);
+ field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+ field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+ field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
+ }
+
+ @IntDef({androidx.core.net.ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_DISABLED, androidx.core.net.ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_WHITELISTED, androidx.core.net.ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_ENABLED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ConnectivityManagerCompat.RestrictBackgroundStatus {
+ }
+
+ public final class MailTo {
+ method public String? getBcc();
+ method public String? getBody();
+ method public String? getCc();
+ method public java.util.Map<java.lang.String!,java.lang.String!>? getHeaders();
+ method public String? getSubject();
+ method public String? getTo();
+ method public static boolean isMailTo(android.net.Uri?);
+ method public static boolean isMailTo(String?);
+ method public static androidx.core.net.MailTo parse(android.net.Uri) throws androidx.core.net.ParseException;
+ method public static androidx.core.net.MailTo parse(String) throws androidx.core.net.ParseException;
+ field public static final String MAILTO_SCHEME = "mailto:";
+ }
+
+ public class ParseException extends java.lang.RuntimeException {
+ field public final String response;
+ }
+
+ public final class TrafficStatsCompat {
+ method @Deprecated public static void clearThreadStatsTag();
+ method @Deprecated public static int getThreadStatsTag();
+ method @Deprecated public static void incrementOperationCount(int);
+ method @Deprecated public static void incrementOperationCount(int, int);
+ method @Deprecated public static void setThreadStatsTag(int);
+ method public static void tagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
+ method @Deprecated public static void tagSocket(java.net.Socket!) throws java.net.SocketException;
+ method public static void untagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
+ method @Deprecated public static void untagSocket(java.net.Socket!) throws java.net.SocketException;
+ }
+
+ public final class UriCompat {
+ method public static String toSafeString(android.net.Uri);
+ }
+
+}
+
+package androidx.core.os {
+
+ public final class BuildCompat {
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.N) public static boolean isAtLeastN();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.N_MR1) public static boolean isAtLeastNMR1();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.O) public static boolean isAtLeastO();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.O_MR1) public static boolean isAtLeastOMR1();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.P) public static boolean isAtLeastP();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
+ method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
+ method @Deprecated @ChecksSdkIntAtLeast(api=31, codename="S") public static boolean isAtLeastS();
+ method @Deprecated @ChecksSdkIntAtLeast(api=32, codename="Sv2") public static boolean isAtLeastSv2();
+ method @Deprecated @ChecksSdkIntAtLeast(api=33, codename="Tiramisu") public static boolean isAtLeastT();
+ method @Deprecated @ChecksSdkIntAtLeast(api=34, codename="UpsideDownCake") public static boolean isAtLeastU();
+ method @SuppressCompatibility @ChecksSdkIntAtLeast(codename="VanillaIceCream") @androidx.core.os.BuildCompat.PrereleaseSdkCheck public static boolean isAtLeastV();
+ field @ChecksSdkIntAtLeast(extension=android.os.ext.SdkExtensions.AD_SERVICES) public static final int AD_SERVICES_EXTENSION_INT;
+ field public static final androidx.core.os.BuildCompat INSTANCE;
+ field @ChecksSdkIntAtLeast(extension=android.os.Build.VERSION_CODES.R) public static final int R_EXTENSION_INT;
+ field @ChecksSdkIntAtLeast(extension=android.os.Build.VERSION_CODES.S) public static final int S_EXTENSION_INT;
+ field @ChecksSdkIntAtLeast(extension=android.os.Build.VERSION_CODES.TIRAMISU) public static final int T_EXTENSION_INT;
+ }
+
+ @SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public static @interface BuildCompat.PrereleaseSdkCheck {
+ }
+
+ public final class BundleCompat {
+ method public static android.os.IBinder? getBinder(android.os.Bundle, String?);
+ method public static <T> T? getParcelable(android.os.Bundle, String?, Class<T!>);
+ method public static android.os.Parcelable![]? getParcelableArray(android.os.Bundle, String?, Class<? extends android.os.Parcelable!>);
+ method public static <T> java.util.ArrayList<T!>? getParcelableArrayList(android.os.Bundle, String?, Class<? extends T!>);
+ method public static <T extends java.io.Serializable> T? getSerializable(android.os.Bundle, String?, Class<T!>);
+ method public static <T> android.util.SparseArray<T!>? getSparseParcelableArray(android.os.Bundle, String?, Class<? extends T!>);
+ method public static void putBinder(android.os.Bundle, String?, android.os.IBinder?);
+ }
+
+ @Deprecated public final class CancellationSignal {
+ ctor @Deprecated public CancellationSignal();
+ method @Deprecated public void cancel();
+ method @Deprecated public Object? getCancellationSignalObject();
+ method @Deprecated public boolean isCanceled();
+ method @Deprecated public void setOnCancelListener(androidx.core.os.CancellationSignal.OnCancelListener?);
+ method @Deprecated public void throwIfCanceled();
+ }
+
+ @Deprecated public static interface CancellationSignal.OnCancelListener {
+ method @Deprecated public void onCancel();
+ }
+
+ public final class ConfigurationCompat {
+ method public static androidx.core.os.LocaleListCompat getLocales(android.content.res.Configuration);
+ method public static void setLocales(android.content.res.Configuration, androidx.core.os.LocaleListCompat);
+ }
+
+ public final class EnvironmentCompat {
+ method public static String getStorageState(java.io.File);
+ field public static final String MEDIA_UNKNOWN = "unknown";
+ }
+
+ public final class ExecutorCompat {
+ method public static java.util.concurrent.Executor create(android.os.Handler);
+ }
+
+ public final class HandlerCompat {
+ method public static android.os.Handler createAsync(android.os.Looper);
+ method public static android.os.Handler createAsync(android.os.Looper, android.os.Handler.Callback);
+ method public static boolean hasCallbacks(android.os.Handler, Runnable);
+ method public static boolean postDelayed(android.os.Handler, Runnable, Object?, long);
+ }
+
+ public final class LocaleListCompat {
+ method public static androidx.core.os.LocaleListCompat create(java.util.Locale!...);
+ method public static androidx.core.os.LocaleListCompat forLanguageTags(String?);
+ method public java.util.Locale? get(int);
+ method @Size(min=1) public static androidx.core.os.LocaleListCompat getAdjustedDefault();
+ method @Size(min=1) public static androidx.core.os.LocaleListCompat getDefault();
+ method public static androidx.core.os.LocaleListCompat getEmptyLocaleList();
+ method public java.util.Locale? getFirstMatch(String![]);
+ method @IntRange(from=0xffffffff) public int indexOf(java.util.Locale?);
+ method public boolean isEmpty();
+ method @RequiresApi(21) public static boolean matchesLanguageAndScript(java.util.Locale, java.util.Locale);
+ method @IntRange(from=0) public int size();
+ method public String toLanguageTags();
+ method public Object? unwrap();
+ method @RequiresApi(24) public static androidx.core.os.LocaleListCompat wrap(android.os.LocaleList);
+ method @Deprecated @RequiresApi(24) public static androidx.core.os.LocaleListCompat! wrap(Object!);
+ }
+
+ public final class MessageCompat {
+ method public static boolean isAsynchronous(android.os.Message);
+ method public static void setAsynchronous(android.os.Message, boolean);
+ }
+
+ public class OperationCanceledException extends java.lang.RuntimeException {
+ ctor public OperationCanceledException();
+ ctor public OperationCanceledException(String?);
+ }
+
+ public final class ParcelCompat {
+ method public static <T> Object![]? readArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> java.util.ArrayList<T!>? readArrayList(android.os.Parcel, ClassLoader?, Class<? extends T!>);
+ method public static boolean readBoolean(android.os.Parcel);
+ method public static <K, V> java.util.HashMap<K!,V!>? readHashMap(android.os.Parcel, ClassLoader?, Class<? extends K!>, Class<? extends V!>);
+ method public static <T> void readList(android.os.Parcel, java.util.List<? super T!>, ClassLoader?, Class<T!>);
+ method public static <K, V> void readMap(android.os.Parcel, java.util.Map<? super K!,? super V!>, ClassLoader?, Class<K!>, Class<V!>);
+ method public static <T extends android.os.Parcelable> T? readParcelable(android.os.Parcel, ClassLoader?, Class<T!>);
+ method @Deprecated public static <T> T![]? readParcelableArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> android.os.Parcelable![]? readParcelableArrayTyped(android.os.Parcel, ClassLoader?, Class<T!>);
+ method @RequiresApi(30) public static <T> android.os.Parcelable.Creator<T!>? readParcelableCreator(android.os.Parcel, ClassLoader?, Class<T!>);
+ method @RequiresApi(api=android.os.Build.VERSION_CODES.Q) public static <T> java.util.List<T!> readParcelableList(android.os.Parcel, java.util.List<T!>, ClassLoader?, Class<T!>);
+ method public static <T extends java.io.Serializable> T? readSerializable(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> android.util.SparseArray<T!>? readSparseArray(android.os.Parcel, ClassLoader?, Class<? extends T!>);
+ method public static void writeBoolean(android.os.Parcel, boolean);
+ }
+
+ @Deprecated public final class ParcelableCompat {
+ method @Deprecated public static <T> android.os.Parcelable.Creator<T!>! newCreator(androidx.core.os.ParcelableCompatCreatorCallbacks<T!>!);
+ }
+
+ @Deprecated public interface ParcelableCompatCreatorCallbacks<T> {
+ method @Deprecated public T! createFromParcel(android.os.Parcel!, ClassLoader!);
+ method @Deprecated public T![]! newArray(int);
+ }
+
+ public final class ProcessCompat {
+ method public static boolean isApplicationUid(int);
+ }
+
+ @Deprecated public final class TraceCompat {
+ method @Deprecated public static void beginAsyncSection(String, int);
+ method @Deprecated public static void beginSection(String);
+ method @Deprecated public static void endAsyncSection(String, int);
+ method @Deprecated public static void endSection();
+ method @Deprecated public static boolean isEnabled();
+ method @Deprecated public static void setCounter(String, int);
+ }
+
+ public class UserHandleCompat {
+ method public static android.os.UserHandle getUserHandleForUid(int);
+ }
+
+ public class UserManagerCompat {
+ method public static boolean isUserUnlocked(android.content.Context);
+ }
+
+}
+
+package androidx.core.provider {
+
+ public final class DocumentsContractCompat {
+ method public static android.net.Uri? buildChildDocumentsUri(String, String?);
+ method public static android.net.Uri? buildChildDocumentsUriUsingTree(android.net.Uri, String);
+ method public static android.net.Uri? buildDocumentUri(String, String);
+ method public static android.net.Uri? buildDocumentUriUsingTree(android.net.Uri, String);
+ method public static android.net.Uri? buildTreeDocumentUri(String, String);
+ method public static android.net.Uri? createDocument(android.content.ContentResolver, android.net.Uri, String, String) throws java.io.FileNotFoundException;
+ method public static String? getDocumentId(android.net.Uri);
+ method public static String? getTreeDocumentId(android.net.Uri);
+ method public static boolean isDocumentUri(android.content.Context, android.net.Uri?);
+ method public static boolean isTreeUri(android.net.Uri);
+ method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+ method public static android.net.Uri? renameDocument(android.content.ContentResolver, android.net.Uri, String) throws java.io.FileNotFoundException;
+ }
+
+ public static final class DocumentsContractCompat.DocumentCompat {
+ field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200
+ }
+
+ public final class FontRequest {
+ ctor public FontRequest(String, String, String, @ArrayRes int);
+ ctor public FontRequest(String, String, String, java.util.List<java.util.List<byte[]!>!>);
+ method public java.util.List<java.util.List<byte[]!>!>? getCertificates();
+ method @ArrayRes public int getCertificatesArrayResId();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! getIdentifier();
+ method public String getProviderAuthority();
+ method public String getProviderPackage();
+ method public String getQuery();
+ }
+
+ public class FontsContractCompat {
+ method public static android.graphics.Typeface? buildTypeface(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo![]);
+ method public static androidx.core.provider.FontsContractCompat.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface! getFontSync(android.content.Context!, androidx.core.provider.FontRequest!, androidx.core.content.res.ResourcesCompat.FontCallback?, android.os.Handler?, boolean, int, int);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @VisibleForTesting public static android.content.pm.ProviderInfo? getProvider(android.content.pm.PackageManager, androidx.core.provider.FontRequest, android.content.res.Resources?) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static java.util.Map<android.net.Uri!,java.nio.ByteBuffer!>! prepareFontData(android.content.Context!, androidx.core.provider.FontsContractCompat.FontInfo![]!, android.os.CancellationSignal!);
+ method public static void requestFont(android.content.Context, androidx.core.provider.FontRequest, androidx.core.provider.FontsContractCompat.FontRequestCallback, android.os.Handler);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void resetCache();
+ field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String PARCEL_FONT_RESULTS = "font_results";
+ }
+
+ public static final class FontsContractCompat.Columns implements android.provider.BaseColumns {
+ ctor public FontsContractCompat.Columns();
+ field public static final String FILE_ID = "file_id";
+ field public static final String ITALIC = "font_italic";
+ field public static final String RESULT_CODE = "result_code";
+ field public static final int RESULT_CODE_FONT_NOT_FOUND = 1; // 0x1
+ field public static final int RESULT_CODE_FONT_UNAVAILABLE = 2; // 0x2
+ field public static final int RESULT_CODE_MALFORMED_QUERY = 3; // 0x3
+ field public static final int RESULT_CODE_OK = 0; // 0x0
+ field public static final String TTC_INDEX = "font_ttc_index";
+ field public static final String VARIATION_SETTINGS = "font_variation_settings";
+ field public static final String WEIGHT = "font_weight";
+ }
+
+ public static class FontsContractCompat.FontFamilyResult {
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontFamilyResult(int, androidx.core.provider.FontsContractCompat.FontInfo![]?);
+ method public androidx.core.provider.FontsContractCompat.FontInfo![]! getFonts();
+ method public int getStatusCode();
+ field public static final int STATUS_OK = 0; // 0x0
+ field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
+ field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
+ }
+
+ public static class FontsContractCompat.FontInfo {
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontInfo(android.net.Uri, @IntRange(from=0) int, @IntRange(from=1, to=1000) int, boolean, int);
+ method public int getResultCode();
+ method @IntRange(from=0) public int getTtcIndex();
+ method public android.net.Uri getUri();
+ method @IntRange(from=1, to=1000) public int getWeight();
+ method public boolean isItalic();
+ }
+
+ public static class FontsContractCompat.FontRequestCallback {
+ ctor public FontsContractCompat.FontRequestCallback();
+ method public void onTypefaceRequestFailed(@androidx.core.provider.FontsContractCompat.FontRequestCallback.FontRequestFailReason int);
+ method public void onTypefaceRetrieved(android.graphics.Typeface!);
+ field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
+ field public static final int FAIL_REASON_FONT_NOT_FOUND = 1; // 0x1
+ field public static final int FAIL_REASON_FONT_UNAVAILABLE = 2; // 0x2
+ field public static final int FAIL_REASON_MALFORMED_QUERY = 3; // 0x3
+ field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
+ field public static final int FAIL_REASON_SECURITY_VIOLATION = -4; // 0xfffffffc
+ field public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
+ field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int RESULT_OK = 0; // 0x0
+ }
+
+ @IntDef({androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_UNAVAILABLE, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_MALFORMED_QUERY, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_SECURITY_VIOLATION, androidx.core.provider.FontsContractCompat.FontRequestCallback.RESULT_OK}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface FontsContractCompat.FontRequestCallback.FontRequestFailReason {
+ }
+
+ @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SelfDestructiveThread {
+ ctor @Deprecated public SelfDestructiveThread(String!, int, int);
+ method @Deprecated @VisibleForTesting public int getGeneration();
+ method @Deprecated @VisibleForTesting public boolean isRunning();
+ method @Deprecated public <T> void postAndReply(java.util.concurrent.Callable<T!>!, androidx.core.provider.SelfDestructiveThread.ReplyCallback<T!>!);
+ method @Deprecated public <T> T! postAndWait(java.util.concurrent.Callable<T!>!, int) throws java.lang.InterruptedException;
+ }
+
+ @Deprecated public static interface SelfDestructiveThread.ReplyCallback<T> {
+ method @Deprecated public void onReply(T!);
+ }
+
+}
+
+package androidx.core.service.quicksettings {
+
+ public class PendingIntentActivityWrapper {
+ ctor public PendingIntentActivityWrapper(android.content.Context, int, android.content.Intent, int, android.os.Bundle?, boolean);
+ ctor public PendingIntentActivityWrapper(android.content.Context, int, android.content.Intent, int, boolean);
+ method public android.content.Context getContext();
+ method public int getFlags();
+ method public android.content.Intent getIntent();
+ method public android.os.Bundle getOptions();
+ method public android.app.PendingIntent? getPendingIntent();
+ method public int getRequestCode();
+ method public boolean isMutable();
+ }
+
+ public class TileServiceCompat {
+ method public static void startActivityAndCollapse(android.service.quicksettings.TileService, androidx.core.service.quicksettings.PendingIntentActivityWrapper);
+ }
+
+}
+
+package androidx.core.telephony {
+
+ @RequiresApi(22) public class SubscriptionManagerCompat {
+ method public static int getSlotIndex(int);
+ }
+
+ public class TelephonyManagerCompat {
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static String? getImei(android.telephony.TelephonyManager);
+ method public static int getSubscriptionId(android.telephony.TelephonyManager);
+ }
+
+}
+
+package androidx.core.telephony.mbms {
+
+ public final class MbmsHelper {
+ method public static CharSequence? getBestNameForService(android.content.Context, android.telephony.mbms.ServiceInfo);
+ }
+
+}
+
+package androidx.core.text {
+
+ public final class BidiFormatter {
+ method public static androidx.core.text.BidiFormatter! getInstance();
+ method public static androidx.core.text.BidiFormatter! getInstance(boolean);
+ method public static androidx.core.text.BidiFormatter! getInstance(java.util.Locale!);
+ method public boolean getStereoReset();
+ method public boolean isRtl(CharSequence!);
+ method public boolean isRtl(String!);
+ method public boolean isRtlContext();
+ method public CharSequence! unicodeWrap(CharSequence!);
+ method public CharSequence! unicodeWrap(CharSequence!, androidx.core.text.TextDirectionHeuristicCompat!);
+ method public CharSequence! unicodeWrap(CharSequence!, androidx.core.text.TextDirectionHeuristicCompat!, boolean);
+ method public CharSequence! unicodeWrap(CharSequence!, boolean);
+ method public String! unicodeWrap(String!);
+ method public String! unicodeWrap(String!, androidx.core.text.TextDirectionHeuristicCompat!);
+ method public String! unicodeWrap(String!, androidx.core.text.TextDirectionHeuristicCompat!, boolean);
+ method public String! unicodeWrap(String!, boolean);
+ }
+
+ public static final class BidiFormatter.Builder {
+ ctor public BidiFormatter.Builder();
+ ctor public BidiFormatter.Builder(boolean);
+ ctor public BidiFormatter.Builder(java.util.Locale!);
+ method public androidx.core.text.BidiFormatter! build();
+ method public androidx.core.text.BidiFormatter.Builder! setTextDirectionHeuristic(androidx.core.text.TextDirectionHeuristicCompat!);
+ method public androidx.core.text.BidiFormatter.Builder! stereoReset(boolean);
+ }
+
+ public final class HtmlCompat {
+ method public static android.text.Spanned fromHtml(String, int);
+ method public static android.text.Spanned fromHtml(String, int, android.text.Html.ImageGetter?, android.text.Html.TagHandler?);
+ method public static String toHtml(android.text.Spanned, int);
+ field public static final int FROM_HTML_MODE_COMPACT = 63; // 0x3f
+ field public static final int FROM_HTML_MODE_LEGACY = 0; // 0x0
+ field public static final int FROM_HTML_OPTION_USE_CSS_COLORS = 256; // 0x100
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE = 32; // 0x20
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_DIV = 16; // 0x10
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_HEADING = 2; // 0x2
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST = 8; // 0x8
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM = 4; // 0x4
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH = 1; // 0x1
+ field public static final int TO_HTML_PARAGRAPH_LINES_CONSECUTIVE = 0; // 0x0
+ field public static final int TO_HTML_PARAGRAPH_LINES_INDIVIDUAL = 1; // 0x1
+ }
+
+ public final class ICUCompat {
+ method public static String? maximizeAndGetScript(java.util.Locale);
+ }
+
+ public class PrecomputedTextCompat implements android.text.Spannable {
+ method public char charAt(int);
+ method public static androidx.core.text.PrecomputedTextCompat! create(CharSequence, androidx.core.text.PrecomputedTextCompat.Params);
+ method @IntRange(from=0) public int getParagraphCount();
+ method @IntRange(from=0) public int getParagraphEnd(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getParagraphStart(@IntRange(from=0) int);
+ method public androidx.core.text.PrecomputedTextCompat.Params getParams();
+ method @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.text.PrecomputedText? getPrecomputedText();
+ method public int getSpanEnd(Object!);
+ method public int getSpanFlags(Object!);
+ method public int getSpanStart(Object!);
+ method public <T> T![]! getSpans(int, int, Class<T!>!);
+ method @UiThread public static java.util.concurrent.Future<androidx.core.text.PrecomputedTextCompat!>! getTextFuture(CharSequence, androidx.core.text.PrecomputedTextCompat.Params, java.util.concurrent.Executor?);
+ method public int length();
+ method public int nextSpanTransition(int, int, Class!);
+ method public void removeSpan(Object!);
+ method public void setSpan(Object!, int, int, int);
+ method public CharSequence! subSequence(int, int);
+ }
+
+ public static final class PrecomputedTextCompat.Params {
+ ctor @RequiresApi(28) public PrecomputedTextCompat.Params(android.text.PrecomputedText.Params);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean equalsWithoutTextDirection(androidx.core.text.PrecomputedTextCompat.Params);
+ method @RequiresApi(23) public int getBreakStrategy();
+ method @RequiresApi(23) public int getHyphenationFrequency();
+ method public android.text.TextDirectionHeuristic? getTextDirection();
+ method public android.text.TextPaint getTextPaint();
+ }
+
+ public static class PrecomputedTextCompat.Params.Builder {
+ ctor public PrecomputedTextCompat.Params.Builder(android.text.TextPaint);
+ method public androidx.core.text.PrecomputedTextCompat.Params build();
+ method @RequiresApi(23) public androidx.core.text.PrecomputedTextCompat.Params.Builder! setBreakStrategy(int);
+ method @RequiresApi(23) public androidx.core.text.PrecomputedTextCompat.Params.Builder! setHyphenationFrequency(int);
+ method public androidx.core.text.PrecomputedTextCompat.Params.Builder! setTextDirection(android.text.TextDirectionHeuristic);
+ }
+
+ public interface TextDirectionHeuristicCompat {
+ method public boolean isRtl(char[]!, int, int);
+ method public boolean isRtl(CharSequence!, int, int);
+ }
+
+ public final class TextDirectionHeuristicsCompat {
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! ANYRTL_LTR;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! FIRSTSTRONG_LTR;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! FIRSTSTRONG_RTL;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! LOCALE;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! LTR;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! RTL;
+ }
+
+ public final class TextUtilsCompat {
+ method public static int getLayoutDirectionFromLocale(java.util.Locale?);
+ method public static String htmlEncode(String);
+ }
+
+}
+
+package androidx.core.text.method {
+
+ public class LinkMovementMethodCompat extends android.text.method.LinkMovementMethod {
+ method public static androidx.core.text.method.LinkMovementMethodCompat getInstance();
+ }
+
+}
+
+package androidx.core.text.util {
+
+ public final class LinkifyCompat {
+ method public static boolean addLinks(android.text.Spannable, @androidx.core.text.util.LinkifyCompat.LinkifyMask int);
+ method public static boolean addLinks(android.text.Spannable, java.util.regex.Pattern, String?);
+ method public static boolean addLinks(android.text.Spannable, java.util.regex.Pattern, String?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ method public static boolean addLinks(android.text.Spannable, java.util.regex.Pattern, String?, String![]?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ method public static boolean addLinks(android.widget.TextView, @androidx.core.text.util.LinkifyCompat.LinkifyMask int);
+ method public static void addLinks(android.widget.TextView, java.util.regex.Pattern, String?);
+ method public static void addLinks(android.widget.TextView, java.util.regex.Pattern, String?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ method public static void addLinks(android.widget.TextView, java.util.regex.Pattern, String?, String![]?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ }
+
+ @IntDef(flag=true, value={android.text.util.Linkify.WEB_URLS, android.text.util.Linkify.EMAIL_ADDRESSES, android.text.util.Linkify.PHONE_NUMBERS, android.text.util.Linkify.MAP_ADDRESSES, android.text.util.Linkify.ALL}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface LinkifyCompat.LinkifyMask {
+ }
+
+ @RequiresApi(android.os.Build.VERSION_CODES.LOLLIPOP) public final class LocalePreferences {
+ method public static String getCalendarType();
+ method public static String getCalendarType(boolean);
+ method public static String getCalendarType(java.util.Locale);
+ method public static String getCalendarType(java.util.Locale, boolean);
+ method public static String getFirstDayOfWeek();
+ method public static String getFirstDayOfWeek(boolean);
+ method public static String getFirstDayOfWeek(java.util.Locale);
+ method public static String getFirstDayOfWeek(java.util.Locale, boolean);
+ method public static String getHourCycle();
+ method public static String getHourCycle(boolean);
+ method public static String getHourCycle(java.util.Locale);
+ method public static String getHourCycle(java.util.Locale, boolean);
+ method public static String getTemperatureUnit();
+ method public static String getTemperatureUnit(boolean);
+ method public static String getTemperatureUnit(java.util.Locale);
+ method public static String getTemperatureUnit(java.util.Locale, boolean);
+ }
+
+ public static class LocalePreferences.CalendarType {
+ field public static final String CHINESE = "chinese";
+ field public static final String DANGI = "dangi";
+ field public static final String DEFAULT = "";
+ field public static final String GREGORIAN = "gregorian";
+ field public static final String HEBREW = "hebrew";
+ field public static final String INDIAN = "indian";
+ field public static final String ISLAMIC = "islamic";
+ field public static final String ISLAMIC_CIVIL = "islamic-civil";
+ field public static final String ISLAMIC_RGSA = "islamic-rgsa";
+ field public static final String ISLAMIC_TBLA = "islamic-tbla";
+ field public static final String ISLAMIC_UMALQURA = "islamic-umalqura";
+ field public static final String PERSIAN = "persian";
+ }
+
+ public static class LocalePreferences.FirstDayOfWeek {
+ field public static final String DEFAULT = "";
+ field public static final String FRIDAY = "fri";
+ field public static final String MONDAY = "mon";
+ field public static final String SATURDAY = "sat";
+ field public static final String SUNDAY = "sun";
+ field public static final String THURSDAY = "thu";
+ field public static final String TUESDAY = "tue";
+ field public static final String WEDNESDAY = "wed";
+ }
+
+ public static class LocalePreferences.HourCycle {
+ field public static final String DEFAULT = "";
+ field public static final String H11 = "h11";
+ field public static final String H12 = "h12";
+ field public static final String H23 = "h23";
+ field public static final String H24 = "h24";
+ }
+
+ public static class LocalePreferences.TemperatureUnit {
+ field public static final String CELSIUS = "celsius";
+ field public static final String DEFAULT = "";
+ field public static final String FAHRENHEIT = "fahrenhe";
+ field public static final String KELVIN = "kelvin";
+ }
+
+}
+
+package androidx.core.util {
+
+ public class AtomicFile {
+ ctor public AtomicFile(java.io.File);
+ method public void delete();
+ method public void failWrite(java.io.FileOutputStream?);
+ method public void finishWrite(java.io.FileOutputStream?);
+ method public java.io.File getBaseFile();
+ method public java.io.FileInputStream openRead() throws java.io.FileNotFoundException;
+ method public byte[] readFully() throws java.io.IOException;
+ method public java.io.FileOutputStream startWrite() throws java.io.IOException;
+ }
+
+ public fun interface Consumer<T> {
+ method public void accept(T value);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DebugUtils {
+ method public static void buildShortClassTag(Object!, StringBuilder!);
+ }
+
+ public fun interface Function<T, R> {
+ method public R apply(T value);
+ }
+
+ @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class LogWriter extends java.io.Writer {
+ ctor @Deprecated public LogWriter(String!);
+ method @Deprecated public void close();
+ method @Deprecated public void flush();
+ method @Deprecated public void write(char[]!, int, int);
+ }
+
+ public class ObjectsCompat {
+ method public static boolean equals(Object?, Object?);
+ method public static int hash(java.lang.Object!...?);
+ method public static int hashCode(Object?);
+ method public static <T> T requireNonNull(T?);
+ method public static <T> T requireNonNull(T?, String);
+ method public static String? toString(Object?, String?);
+ }
+
+ public class Pair<F, S> {
+ ctor public Pair(F!, S!);
+ method public static <A, B> androidx.core.util.Pair<A!,B!> create(A!, B!);
+ field public final F! first;
+ field public final S! second;
+ }
+
+ public final class PatternsCompat {
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final java.util.regex.Pattern AUTOLINK_EMAIL_ADDRESS;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final java.util.regex.Pattern AUTOLINK_WEB_URL;
+ field public static final java.util.regex.Pattern DOMAIN_NAME;
+ field public static final java.util.regex.Pattern EMAIL_ADDRESS;
+ field public static final java.util.regex.Pattern IP_ADDRESS;
+ field public static final java.util.regex.Pattern WEB_URL;
+ }
+
+ public final class Pools {
+ }
+
+ public static interface Pools.Pool<T> {
+ method public T? acquire();
+ method public boolean release(T instance);
+ }
+
+ public static class Pools.SimplePool<T> implements androidx.core.util.Pools.Pool<T> {
+ ctor public Pools.SimplePool(@IntRange(from=1L) int maxPoolSize);
+ method public T? acquire();
+ method public boolean release(T instance);
+ }
+
+ public static class Pools.SynchronizedPool<T> extends androidx.core.util.Pools.SimplePool<T> {
+ ctor public Pools.SynchronizedPool(int maxPoolSize);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class Preconditions {
+ method public static void checkArgument(boolean);
+ method public static void checkArgument(boolean, Object);
+ method public static void checkArgument(boolean, String, java.lang.Object!...);
+ method public static float checkArgumentFinite(float, String);
+ method public static double checkArgumentInRange(double, double, double, String);
+ method public static float checkArgumentInRange(float, float, float, String);
+ method public static int checkArgumentInRange(int, int, int, String);
+ method public static long checkArgumentInRange(long, long, long, String);
+ method @IntRange(from=0) public static int checkArgumentNonnegative(int);
+ method @IntRange(from=0) public static int checkArgumentNonnegative(int, String?);
+ method public static int checkFlagsArgument(int, int);
+ method public static <T> T checkNotNull(T?);
+ method public static <T> T checkNotNull(T?, Object);
+ method public static void checkState(boolean);
+ method public static void checkState(boolean, String?);
+ method public static <T extends java.lang.CharSequence> T checkStringNotEmpty(T?);
+ method public static <T extends java.lang.CharSequence> T checkStringNotEmpty(T?, Object);
+ method public static <T extends java.lang.CharSequence> T checkStringNotEmpty(T?, String, java.lang.Object!...);
+ }
+
+ public interface Predicate<T> {
+ method public default androidx.core.util.Predicate<T!>! and(androidx.core.util.Predicate<? super T!>!);
+ method public static <T> androidx.core.util.Predicate<T!>! isEqual(Object!);
+ method public default androidx.core.util.Predicate<T!>! negate();
+ method public static <T> androidx.core.util.Predicate<T!>! not(androidx.core.util.Predicate<? super T!>!);
+ method public default androidx.core.util.Predicate<T!>! or(androidx.core.util.Predicate<? super T!>!);
+ method public boolean test(T!);
+ }
+
+ public final class SizeFCompat {
+ ctor public SizeFCompat(float, float);
+ method public float getHeight();
+ method public float getWidth();
+ method @RequiresApi(21) public android.util.SizeF toSizeF();
+ method @RequiresApi(21) public static androidx.core.util.SizeFCompat toSizeFCompat(android.util.SizeF);
+ }
+
+ public fun interface Supplier<T> {
+ method public T get();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TimeUtils {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void formatDuration(long, java.io.PrintWriter!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void formatDuration(long, java.io.PrintWriter!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void formatDuration(long, StringBuilder!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void formatDuration(long, long, java.io.PrintWriter!);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int HUNDRED_DAY_FIELD_LEN = 19; // 0x13
+ }
+
+ public class TypedValueCompat {
+ method public static float deriveDimension(int, float, android.util.DisplayMetrics);
+ method public static float dpToPx(float, android.util.DisplayMetrics);
+ method public static int getUnitFromComplexDimension(int);
+ method public static float pxToDp(float, android.util.DisplayMetrics);
+ method public static float pxToSp(float, android.util.DisplayMetrics);
+ method public static float spToPx(float, android.util.DisplayMetrics);
+ }
+
+}
+
+package androidx.core.view {
+
+ public class AccessibilityDelegateCompat {
+ ctor public AccessibilityDelegateCompat();
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public AccessibilityDelegateCompat(android.view.View.AccessibilityDelegate);
+ method public boolean dispatchPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public androidx.core.view.accessibility.AccessibilityNodeProviderCompat? getAccessibilityNodeProvider(android.view.View);
+ method public void onInitializeAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityNodeInfo(android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat);
+ method public void onPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public boolean onRequestSendAccessibilityEvent(android.view.ViewGroup, android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public boolean performAccessibilityAction(android.view.View, int, android.os.Bundle?);
+ method public void sendAccessibilityEvent(android.view.View, int);
+ method public void sendAccessibilityEventUnchecked(android.view.View, android.view.accessibility.AccessibilityEvent);
+ }
+
+ public abstract class ActionProvider {
+ ctor public ActionProvider(android.content.Context);
+ method public android.content.Context getContext();
+ method public boolean hasSubMenu();
+ method public boolean isVisible();
+ method public abstract android.view.View onCreateActionView();
+ method public android.view.View onCreateActionView(android.view.MenuItem);
+ method public boolean onPerformDefaultAction();
+ method public void onPrepareSubMenu(android.view.SubMenu);
+ method public boolean overridesItemVisibility();
+ method public void refreshVisibility();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void reset();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSubUiVisibilityListener(androidx.core.view.ActionProvider.SubUiVisibilityListener?);
+ method public void setVisibilityListener(androidx.core.view.ActionProvider.VisibilityListener?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void subUiVisibilityChanged(boolean);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface ActionProvider.SubUiVisibilityListener {
+ method public void onSubUiVisibilityChanged(boolean);
+ }
+
+ public static interface ActionProvider.VisibilityListener {
+ method public void onActionProviderVisibilityChanged(boolean);
+ }
+
+ public final class ContentInfoCompat {
+ method public android.content.ClipData getClip();
+ method public android.os.Bundle? getExtras();
+ method @androidx.core.view.ContentInfoCompat.Flags public int getFlags();
+ method public android.net.Uri? getLinkUri();
+ method @androidx.core.view.ContentInfoCompat.Source public int getSource();
+ method @RequiresApi(31) public static android.util.Pair<android.view.ContentInfo!,android.view.ContentInfo!> partition(android.view.ContentInfo, java.util.function.Predicate<android.content.ClipData.Item!>);
+ method public android.util.Pair<androidx.core.view.ContentInfoCompat!,androidx.core.view.ContentInfoCompat!> partition(androidx.core.util.Predicate<android.content.ClipData.Item!>);
+ method @RequiresApi(31) public android.view.ContentInfo toContentInfo();
+ method @RequiresApi(31) public static androidx.core.view.ContentInfoCompat toContentInfoCompat(android.view.ContentInfo);
+ field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
+ field public static final int SOURCE_APP = 0; // 0x0
+ field public static final int SOURCE_AUTOFILL = 4; // 0x4
+ field public static final int SOURCE_CLIPBOARD = 1; // 0x1
+ field public static final int SOURCE_DRAG_AND_DROP = 3; // 0x3
+ field public static final int SOURCE_INPUT_METHOD = 2; // 0x2
+ field public static final int SOURCE_PROCESS_TEXT = 5; // 0x5
+ }
+
+ public static final class ContentInfoCompat.Builder {
+ ctor public ContentInfoCompat.Builder(android.content.ClipData, @androidx.core.view.ContentInfoCompat.Source int);
+ ctor public ContentInfoCompat.Builder(androidx.core.view.ContentInfoCompat);
+ method public androidx.core.view.ContentInfoCompat build();
+ method public androidx.core.view.ContentInfoCompat.Builder setClip(android.content.ClipData);
+ method public androidx.core.view.ContentInfoCompat.Builder setExtras(android.os.Bundle?);
+ method public androidx.core.view.ContentInfoCompat.Builder setFlags(@androidx.core.view.ContentInfoCompat.Flags int);
+ method public androidx.core.view.ContentInfoCompat.Builder setLinkUri(android.net.Uri?);
+ method public androidx.core.view.ContentInfoCompat.Builder setSource(@androidx.core.view.ContentInfoCompat.Source int);
+ }
+
+ @IntDef(flag=true, value={androidx.core.view.ContentInfoCompat.FLAG_CONVERT_TO_PLAIN_TEXT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ContentInfoCompat.Flags {
+ }
+
+ @IntDef({androidx.core.view.ContentInfoCompat.SOURCE_APP, androidx.core.view.ContentInfoCompat.SOURCE_CLIPBOARD, androidx.core.view.ContentInfoCompat.SOURCE_INPUT_METHOD, androidx.core.view.ContentInfoCompat.SOURCE_DRAG_AND_DROP, androidx.core.view.ContentInfoCompat.SOURCE_AUTOFILL, androidx.core.view.ContentInfoCompat.SOURCE_PROCESS_TEXT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ContentInfoCompat.Source {
+ }
+
+ public class DifferentialMotionFlingController {
+ ctor public DifferentialMotionFlingController(android.content.Context, androidx.core.view.DifferentialMotionFlingTarget);
+ method public void onMotionEvent(android.view.MotionEvent, int);
+ }
+
+ public interface DifferentialMotionFlingTarget {
+ method public float getScaledScrollFactor();
+ method public boolean startDifferentialMotionFling(float);
+ method public void stopDifferentialMotionFling();
+ }
+
+ public final class DisplayCompat {
+ method public static androidx.core.view.DisplayCompat.ModeCompat getMode(android.content.Context, android.view.Display);
+ method public static androidx.core.view.DisplayCompat.ModeCompat![] getSupportedModes(android.content.Context, android.view.Display);
+ }
+
+ public static final class DisplayCompat.ModeCompat {
+ method public int getPhysicalHeight();
+ method public int getPhysicalWidth();
+ method @Deprecated public boolean isNative();
+ method @RequiresApi(android.os.Build.VERSION_CODES.M) public android.view.Display.Mode? toMode();
+ }
+
+ public final class DisplayCutoutCompat {
+ ctor public DisplayCutoutCompat(android.graphics.Rect?, java.util.List<android.graphics.Rect!>?);
+ ctor public DisplayCutoutCompat(androidx.core.graphics.Insets, android.graphics.Rect?, android.graphics.Rect?, android.graphics.Rect?, android.graphics.Rect?, androidx.core.graphics.Insets);
+ method public java.util.List<android.graphics.Rect!> getBoundingRects();
+ method public int getSafeInsetBottom();
+ method public int getSafeInsetLeft();
+ method public int getSafeInsetRight();
+ method public int getSafeInsetTop();
+ method public androidx.core.graphics.Insets getWaterfallInsets();
+ }
+
+ public final class DragAndDropPermissionsCompat {
+ method public void release();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.view.DragAndDropPermissionsCompat? request(android.app.Activity, android.view.DragEvent);
+ }
+
+ public class DragStartHelper {
+ ctor public DragStartHelper(android.view.View, androidx.core.view.DragStartHelper.OnDragStartListener);
+ method public void attach();
+ method public void detach();
+ method public void getTouchPosition(android.graphics.Point);
+ method public boolean onLongClick(android.view.View);
+ method public boolean onTouch(android.view.View, android.view.MotionEvent);
+ }
+
+ public static interface DragStartHelper.OnDragStartListener {
+ method public boolean onDragStart(android.view.View, androidx.core.view.DragStartHelper);
+ }
+
+ public final class GestureDetectorCompat {
+ ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener);
+ ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener, android.os.Handler?);
+ method public boolean isLongpressEnabled();
+ method public boolean onTouchEvent(android.view.MotionEvent);
+ method public void setIsLongpressEnabled(boolean);
+ method public void setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener?);
+ }
+
+ public final class GravityCompat {
+ method public static void apply(int, int, int, android.graphics.Rect, android.graphics.Rect, int);
+ method public static void apply(int, int, int, android.graphics.Rect, int, int, android.graphics.Rect, int);
+ method public static void applyDisplay(int, android.graphics.Rect, android.graphics.Rect, int);
+ method public static int getAbsoluteGravity(int, int);
+ field public static final int END = 8388613; // 0x800005
+ field public static final int RELATIVE_HORIZONTAL_GRAVITY_MASK = 8388615; // 0x800007
+ field public static final int RELATIVE_LAYOUT_DIRECTION = 8388608; // 0x800000
+ field public static final int START = 8388611; // 0x800003
+ }
+
+ public final class HapticFeedbackConstantsCompat {
+ field public static final int CLOCK_TICK = 4; // 0x4
+ field public static final int CONFIRM = 16; // 0x10
+ field public static final int CONTEXT_CLICK = 6; // 0x6
+ field public static final int DRAG_START = 25; // 0x19
+ field public static final int FLAG_IGNORE_VIEW_SETTING = 1; // 0x1
+ field public static final int GESTURE_END = 13; // 0xd
+ field public static final int GESTURE_START = 12; // 0xc
+ field public static final int GESTURE_THRESHOLD_ACTIVATE = 23; // 0x17
+ field public static final int GESTURE_THRESHOLD_DEACTIVATE = 24; // 0x18
+ field public static final int KEYBOARD_PRESS = 3; // 0x3
+ field public static final int KEYBOARD_RELEASE = 7; // 0x7
+ field public static final int KEYBOARD_TAP = 3; // 0x3
+ field public static final int LONG_PRESS = 0; // 0x0
+ field public static final int NO_HAPTICS = -1; // 0xffffffff
+ field public static final int REJECT = 17; // 0x11
+ field public static final int SEGMENT_FREQUENT_TICK = 27; // 0x1b
+ field public static final int SEGMENT_TICK = 26; // 0x1a
+ field public static final int TEXT_HANDLE_MOVE = 9; // 0x9
+ field public static final int TOGGLE_OFF = 22; // 0x16
+ field public static final int TOGGLE_ON = 21; // 0x15
+ field public static final int VIRTUAL_KEY = 1; // 0x1
+ field public static final int VIRTUAL_KEY_RELEASE = 8; // 0x8
+ }
+
+ public final class InputDeviceCompat {
+ field public static final int SOURCE_ANY = -256; // 0xffffff00
+ field public static final int SOURCE_CLASS_BUTTON = 1; // 0x1
+ field public static final int SOURCE_CLASS_JOYSTICK = 16; // 0x10
+ field public static final int SOURCE_CLASS_MASK = 255; // 0xff
+ field public static final int SOURCE_CLASS_NONE = 0; // 0x0
+ field public static final int SOURCE_CLASS_POINTER = 2; // 0x2
+ field public static final int SOURCE_CLASS_POSITION = 8; // 0x8
+ field public static final int SOURCE_CLASS_TRACKBALL = 4; // 0x4
+ field public static final int SOURCE_DPAD = 513; // 0x201
+ field public static final int SOURCE_GAMEPAD = 1025; // 0x401
+ field public static final int SOURCE_HDMI = 33554433; // 0x2000001
+ field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010
+ field public static final int SOURCE_KEYBOARD = 257; // 0x101
+ field public static final int SOURCE_MOUSE = 8194; // 0x2002
+ field public static final int SOURCE_ROTARY_ENCODER = 4194304; // 0x400000
+ field public static final int SOURCE_STYLUS = 16386; // 0x4002
+ field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
+ field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
+ field public static final int SOURCE_TOUCH_NAVIGATION = 2097152; // 0x200000
+ field public static final int SOURCE_TRACKBALL = 65540; // 0x10004
+ field public static final int SOURCE_UNKNOWN = 0; // 0x0
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class KeyEventDispatcher {
+ method public static boolean dispatchBeforeHierarchy(android.view.View, android.view.KeyEvent);
+ method public static boolean dispatchKeyEvent(androidx.core.view.KeyEventDispatcher.Component, android.view.View?, android.view.Window.Callback?, android.view.KeyEvent);
+ }
+
+ public static interface KeyEventDispatcher.Component {
+ method public boolean superDispatchKeyEvent(android.view.KeyEvent);
+ }
+
+ public final class LayoutInflaterCompat {
+ method @Deprecated public static androidx.core.view.LayoutInflaterFactory! getFactory(android.view.LayoutInflater!);
+ method @Deprecated public static void setFactory(android.view.LayoutInflater, androidx.core.view.LayoutInflaterFactory);
+ method public static void setFactory2(android.view.LayoutInflater, android.view.LayoutInflater.Factory2);
+ }
+
+ @Deprecated public interface LayoutInflaterFactory {
+ method @Deprecated public android.view.View! onCreateView(android.view.View!, String!, android.content.Context!, android.util.AttributeSet!);
+ }
+
+ public final class MarginLayoutParamsCompat {
+ method public static int getLayoutDirection(android.view.ViewGroup.MarginLayoutParams);
+ method public static int getMarginEnd(android.view.ViewGroup.MarginLayoutParams);
+ method public static int getMarginStart(android.view.ViewGroup.MarginLayoutParams);
+ method public static boolean isMarginRelative(android.view.ViewGroup.MarginLayoutParams);
+ method public static void resolveLayoutDirection(android.view.ViewGroup.MarginLayoutParams, int);
+ method public static void setLayoutDirection(android.view.ViewGroup.MarginLayoutParams, int);
+ method public static void setMarginEnd(android.view.ViewGroup.MarginLayoutParams, int);
+ method public static void setMarginStart(android.view.ViewGroup.MarginLayoutParams, int);
+ }
+
+ public final class MenuCompat {
+ method public static void setGroupDividerEnabled(android.view.Menu, boolean);
+ method @Deprecated public static void setShowAsAction(android.view.MenuItem!, int);
+ }
+
+ public interface MenuHost {
+ method public void addMenuProvider(androidx.core.view.MenuProvider);
+ method public void addMenuProvider(androidx.core.view.MenuProvider, androidx.lifecycle.LifecycleOwner);
+ method public void addMenuProvider(androidx.core.view.MenuProvider, androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State);
+ method public void invalidateMenu();
+ method public void removeMenuProvider(androidx.core.view.MenuProvider);
+ }
+
+ public class MenuHostHelper {
+ ctor public MenuHostHelper(Runnable);
+ method public void addMenuProvider(androidx.core.view.MenuProvider);
+ method public void addMenuProvider(androidx.core.view.MenuProvider, androidx.lifecycle.LifecycleOwner);
+ method public void addMenuProvider(androidx.core.view.MenuProvider, androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State);
+ method public void onCreateMenu(android.view.Menu, android.view.MenuInflater);
+ method public void onMenuClosed(android.view.Menu);
+ method public boolean onMenuItemSelected(android.view.MenuItem);
+ method public void onPrepareMenu(android.view.Menu);
+ method public void removeMenuProvider(androidx.core.view.MenuProvider);
+ }
+
+ public final class MenuItemCompat {
+ method @Deprecated public static boolean collapseActionView(android.view.MenuItem!);
+ method @Deprecated public static boolean expandActionView(android.view.MenuItem!);
+ method public static androidx.core.view.ActionProvider? getActionProvider(android.view.MenuItem);
+ method @Deprecated public static android.view.View! getActionView(android.view.MenuItem!);
+ method public static int getAlphabeticModifiers(android.view.MenuItem);
+ method public static CharSequence? getContentDescription(android.view.MenuItem);
+ method public static android.content.res.ColorStateList? getIconTintList(android.view.MenuItem);
+ method public static android.graphics.PorterDuff.Mode? getIconTintMode(android.view.MenuItem);
+ method public static int getNumericModifiers(android.view.MenuItem);
+ method public static CharSequence? getTooltipText(android.view.MenuItem);
+ method @Deprecated public static boolean isActionViewExpanded(android.view.MenuItem!);
+ method public static android.view.MenuItem? setActionProvider(android.view.MenuItem, androidx.core.view.ActionProvider?);
+ method @Deprecated public static android.view.MenuItem! setActionView(android.view.MenuItem!, android.view.View!);
+ method @Deprecated public static android.view.MenuItem! setActionView(android.view.MenuItem!, int);
+ method public static void setAlphabeticShortcut(android.view.MenuItem, char, int);
+ method public static void setContentDescription(android.view.MenuItem, CharSequence?);
+ method public static void setIconTintList(android.view.MenuItem, android.content.res.ColorStateList?);
+ method public static void setIconTintMode(android.view.MenuItem, android.graphics.PorterDuff.Mode?);
+ method public static void setNumericShortcut(android.view.MenuItem, char, int);
+ method @Deprecated public static android.view.MenuItem! setOnActionExpandListener(android.view.MenuItem!, androidx.core.view.MenuItemCompat.OnActionExpandListener!);
+ method public static void setShortcut(android.view.MenuItem, char, char, int, int);
+ method @Deprecated public static void setShowAsAction(android.view.MenuItem!, int);
+ method public static void setTooltipText(android.view.MenuItem, CharSequence?);
+ field @Deprecated public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
+ field @Deprecated public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
+ field @Deprecated public static final int SHOW_AS_ACTION_IF_ROOM = 1; // 0x1
+ field @Deprecated public static final int SHOW_AS_ACTION_NEVER = 0; // 0x0
+ field @Deprecated public static final int SHOW_AS_ACTION_WITH_TEXT = 4; // 0x4
+ }
+
+ @Deprecated public static interface MenuItemCompat.OnActionExpandListener {
+ method @Deprecated public boolean onMenuItemActionCollapse(android.view.MenuItem!);
+ method @Deprecated public boolean onMenuItemActionExpand(android.view.MenuItem!);
+ }
+
+ public interface MenuProvider {
+ method public void onCreateMenu(android.view.Menu, android.view.MenuInflater);
+ method public default void onMenuClosed(android.view.Menu);
+ method public boolean onMenuItemSelected(android.view.MenuItem);
+ method public default void onPrepareMenu(android.view.Menu);
+ }
+
+ public final class MotionEventCompat {
+ method @Deprecated public static int findPointerIndex(android.view.MotionEvent!, int);
+ method @Deprecated public static int getActionIndex(android.view.MotionEvent!);
+ method @Deprecated public static int getActionMasked(android.view.MotionEvent!);
+ method @Deprecated public static float getAxisValue(android.view.MotionEvent!, int);
+ method @Deprecated public static float getAxisValue(android.view.MotionEvent!, int, int);
+ method @Deprecated public static int getButtonState(android.view.MotionEvent!);
+ method @Deprecated public static int getPointerCount(android.view.MotionEvent!);
+ method @Deprecated public static int getPointerId(android.view.MotionEvent!, int);
+ method @Deprecated public static int getSource(android.view.MotionEvent!);
+ method @Deprecated public static float getX(android.view.MotionEvent!, int);
+ method @Deprecated public static float getY(android.view.MotionEvent!, int);
+ method public static boolean isFromSource(android.view.MotionEvent, int);
+ field @Deprecated public static final int ACTION_HOVER_ENTER = 9; // 0x9
+ field @Deprecated public static final int ACTION_HOVER_EXIT = 10; // 0xa
+ field @Deprecated public static final int ACTION_HOVER_MOVE = 7; // 0x7
+ field @Deprecated public static final int ACTION_MASK = 255; // 0xff
+ field @Deprecated public static final int ACTION_POINTER_DOWN = 5; // 0x5
+ field @Deprecated public static final int ACTION_POINTER_INDEX_MASK = 65280; // 0xff00
+ field @Deprecated public static final int ACTION_POINTER_INDEX_SHIFT = 8; // 0x8
+ field @Deprecated public static final int ACTION_POINTER_UP = 6; // 0x6
+ field @Deprecated public static final int ACTION_SCROLL = 8; // 0x8
+ field @Deprecated public static final int AXIS_BRAKE = 23; // 0x17
+ field @Deprecated public static final int AXIS_DISTANCE = 24; // 0x18
+ field @Deprecated public static final int AXIS_GAS = 22; // 0x16
+ field @Deprecated public static final int AXIS_GENERIC_1 = 32; // 0x20
+ field @Deprecated public static final int AXIS_GENERIC_10 = 41; // 0x29
+ field @Deprecated public static final int AXIS_GENERIC_11 = 42; // 0x2a
+ field @Deprecated public static final int AXIS_GENERIC_12 = 43; // 0x2b
+ field @Deprecated public static final int AXIS_GENERIC_13 = 44; // 0x2c
+ field @Deprecated public static final int AXIS_GENERIC_14 = 45; // 0x2d
+ field @Deprecated public static final int AXIS_GENERIC_15 = 46; // 0x2e
+ field @Deprecated public static final int AXIS_GENERIC_16 = 47; // 0x2f
+ field @Deprecated public static final int AXIS_GENERIC_2 = 33; // 0x21
+ field @Deprecated public static final int AXIS_GENERIC_3 = 34; // 0x22
+ field @Deprecated public static final int AXIS_GENERIC_4 = 35; // 0x23
+ field @Deprecated public static final int AXIS_GENERIC_5 = 36; // 0x24
+ field @Deprecated public static final int AXIS_GENERIC_6 = 37; // 0x25
+ field @Deprecated public static final int AXIS_GENERIC_7 = 38; // 0x26
+ field @Deprecated public static final int AXIS_GENERIC_8 = 39; // 0x27
+ field @Deprecated public static final int AXIS_GENERIC_9 = 40; // 0x28
+ field @Deprecated public static final int AXIS_HAT_X = 15; // 0xf
+ field @Deprecated public static final int AXIS_HAT_Y = 16; // 0x10
+ field @Deprecated public static final int AXIS_HSCROLL = 10; // 0xa
+ field @Deprecated public static final int AXIS_LTRIGGER = 17; // 0x11
+ field @Deprecated public static final int AXIS_ORIENTATION = 8; // 0x8
+ field @Deprecated public static final int AXIS_PRESSURE = 2; // 0x2
+ field public static final int AXIS_RELATIVE_X = 27; // 0x1b
+ field public static final int AXIS_RELATIVE_Y = 28; // 0x1c
+ field @Deprecated public static final int AXIS_RTRIGGER = 18; // 0x12
+ field @Deprecated public static final int AXIS_RUDDER = 20; // 0x14
+ field @Deprecated public static final int AXIS_RX = 12; // 0xc
+ field @Deprecated public static final int AXIS_RY = 13; // 0xd
+ field @Deprecated public static final int AXIS_RZ = 14; // 0xe
+ field public static final int AXIS_SCROLL = 26; // 0x1a
+ field @Deprecated public static final int AXIS_SIZE = 3; // 0x3
+ field @Deprecated public static final int AXIS_THROTTLE = 19; // 0x13
+ field @Deprecated public static final int AXIS_TILT = 25; // 0x19
+ field @Deprecated public static final int AXIS_TOOL_MAJOR = 6; // 0x6
+ field @Deprecated public static final int AXIS_TOOL_MINOR = 7; // 0x7
+ field @Deprecated public static final int AXIS_TOUCH_MAJOR = 4; // 0x4
+ field @Deprecated public static final int AXIS_TOUCH_MINOR = 5; // 0x5
+ field @Deprecated public static final int AXIS_VSCROLL = 9; // 0x9
+ field @Deprecated public static final int AXIS_WHEEL = 21; // 0x15
+ field @Deprecated public static final int AXIS_X = 0; // 0x0
+ field @Deprecated public static final int AXIS_Y = 1; // 0x1
+ field @Deprecated public static final int AXIS_Z = 11; // 0xb
+ field @Deprecated public static final int BUTTON_PRIMARY = 1; // 0x1
+ }
+
+ public interface NestedScrollingChild {
+ method public boolean dispatchNestedFling(float, float, boolean);
+ method public boolean dispatchNestedPreFling(float, float);
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?);
+ method public boolean hasNestedScrollingParent();
+ method public boolean isNestedScrollingEnabled();
+ method public void setNestedScrollingEnabled(boolean);
+ method public boolean startNestedScroll(@androidx.core.view.ViewCompat.ScrollAxis int);
+ method public void stopNestedScroll();
+ }
+
+ public interface NestedScrollingChild2 extends androidx.core.view.NestedScrollingChild {
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public boolean hasNestedScrollingParent(@androidx.core.view.ViewCompat.NestedScrollType int);
+ method public boolean startNestedScroll(@androidx.core.view.ViewCompat.ScrollAxis int, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public void stopNestedScroll(@androidx.core.view.ViewCompat.NestedScrollType int);
+ }
+
+ public interface NestedScrollingChild3 extends androidx.core.view.NestedScrollingChild2 {
+ method public void dispatchNestedScroll(int, int, int, int, int[]?, @androidx.core.view.ViewCompat.NestedScrollType int, int[]);
+ }
+
+ public class NestedScrollingChildHelper {
+ ctor public NestedScrollingChildHelper(android.view.View);
+ method public boolean dispatchNestedFling(float, float, boolean);
+ method public boolean dispatchNestedPreFling(float, float);
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?);
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public void dispatchNestedScroll(int, int, int, int, int[]?, @androidx.core.view.ViewCompat.NestedScrollType int, int[]?);
+ method public boolean hasNestedScrollingParent();
+ method public boolean hasNestedScrollingParent(@androidx.core.view.ViewCompat.NestedScrollType int);
+ method public boolean isNestedScrollingEnabled();
+ method public void onDetachedFromWindow();
+ method public void onStopNestedScroll(android.view.View);
+ method public void setNestedScrollingEnabled(boolean);
+ method public boolean startNestedScroll(@androidx.core.view.ViewCompat.ScrollAxis int);
+ method public boolean startNestedScroll(@androidx.core.view.ViewCompat.ScrollAxis int, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public void stopNestedScroll();
+ method public void stopNestedScroll(@androidx.core.view.ViewCompat.NestedScrollType int);
+ }
+
+ public interface NestedScrollingParent {
+ method @androidx.core.view.ViewCompat.ScrollAxis public int getNestedScrollAxes();
+ method public boolean onNestedFling(android.view.View, float, float, boolean);
+ method public boolean onNestedPreFling(android.view.View, float, float);
+ method public void onNestedPreScroll(android.view.View, int, int, int[]);
+ method public void onNestedScroll(android.view.View, int, int, int, int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int);
+ method public void onStopNestedScroll(android.view.View);
+ }
+
+ public interface NestedScrollingParent2 extends androidx.core.view.NestedScrollingParent {
+ method public void onNestedPreScroll(android.view.View, int, int, int[], @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public void onStopNestedScroll(android.view.View, @androidx.core.view.ViewCompat.NestedScrollType int);
+ }
+
+ public interface NestedScrollingParent3 extends androidx.core.view.NestedScrollingParent2 {
+ method public void onNestedScroll(android.view.View, int, int, int, int, @androidx.core.view.ViewCompat.NestedScrollType int, int[]);
+ }
+
+ public class NestedScrollingParentHelper {
+ ctor public NestedScrollingParentHelper(android.view.ViewGroup);
+ method @androidx.core.view.ViewCompat.ScrollAxis public int getNestedScrollAxes();
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public void onStopNestedScroll(android.view.View);
+ method public void onStopNestedScroll(android.view.View, @androidx.core.view.ViewCompat.NestedScrollType int);
+ }
+
+ public interface OnApplyWindowInsetsListener {
+ method public androidx.core.view.WindowInsetsCompat onApplyWindowInsets(android.view.View, androidx.core.view.WindowInsetsCompat);
+ }
+
+ public interface OnReceiveContentListener {
+ method public androidx.core.view.ContentInfoCompat? onReceiveContent(android.view.View, androidx.core.view.ContentInfoCompat);
+ }
+
+ public interface OnReceiveContentViewBehavior {
+ method public androidx.core.view.ContentInfoCompat? onReceiveContent(androidx.core.view.ContentInfoCompat);
+ }
+
+ public final class OneShotPreDrawListener implements android.view.View.OnAttachStateChangeListener android.view.ViewTreeObserver.OnPreDrawListener {
+ method public static androidx.core.view.OneShotPreDrawListener add(android.view.View, Runnable);
+ method public boolean onPreDraw();
+ method public void onViewAttachedToWindow(android.view.View);
+ method public void onViewDetachedFromWindow(android.view.View);
+ method public void removeListener();
+ }
+
+ public final class PointerIconCompat {
+ method public static androidx.core.view.PointerIconCompat create(android.graphics.Bitmap, float, float);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public Object? getPointerIcon();
+ method public static androidx.core.view.PointerIconCompat getSystemIcon(android.content.Context, int);
+ method public static androidx.core.view.PointerIconCompat load(android.content.res.Resources, int);
+ field public static final int TYPE_ALIAS = 1010; // 0x3f2
+ field public static final int TYPE_ALL_SCROLL = 1013; // 0x3f5
+ field public static final int TYPE_ARROW = 1000; // 0x3e8
+ field public static final int TYPE_CELL = 1006; // 0x3ee
+ field public static final int TYPE_CONTEXT_MENU = 1001; // 0x3e9
+ field public static final int TYPE_COPY = 1011; // 0x3f3
+ field public static final int TYPE_CROSSHAIR = 1007; // 0x3ef
+ field public static final int TYPE_DEFAULT = 1000; // 0x3e8
+ field public static final int TYPE_GRAB = 1020; // 0x3fc
+ field public static final int TYPE_GRABBING = 1021; // 0x3fd
+ field public static final int TYPE_HAND = 1002; // 0x3ea
+ field public static final int TYPE_HELP = 1003; // 0x3eb
+ field public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = 1014; // 0x3f6
+ field public static final int TYPE_NO_DROP = 1012; // 0x3f4
+ field public static final int TYPE_NULL = 0; // 0x0
+ field public static final int TYPE_TEXT = 1008; // 0x3f0
+ field public static final int TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; // 0x3f9
+ field public static final int TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; // 0x3f8
+ field public static final int TYPE_VERTICAL_DOUBLE_ARROW = 1015; // 0x3f7
+ field public static final int TYPE_VERTICAL_TEXT = 1009; // 0x3f1
+ field public static final int TYPE_WAIT = 1004; // 0x3ec
+ field public static final int TYPE_ZOOM_IN = 1018; // 0x3fa
+ field public static final int TYPE_ZOOM_OUT = 1019; // 0x3fb
+ }
+
+ public final class ScaleGestureDetectorCompat {
+ method public static boolean isQuickScaleEnabled(android.view.ScaleGestureDetector);
+ method @Deprecated public static boolean isQuickScaleEnabled(Object!);
+ method public static void setQuickScaleEnabled(android.view.ScaleGestureDetector, boolean);
+ method @Deprecated public static void setQuickScaleEnabled(Object!, boolean);
+ }
+
+ public interface ScrollingView {
+ method public int computeHorizontalScrollExtent();
+ method public int computeHorizontalScrollOffset();
+ method public int computeHorizontalScrollRange();
+ method public int computeVerticalScrollExtent();
+ method public int computeVerticalScrollOffset();
+ method public int computeVerticalScrollRange();
+ }
+
+ public final class SoftwareKeyboardControllerCompat {
+ ctor public SoftwareKeyboardControllerCompat(android.view.View);
+ method public void hide();
+ method public void show();
+ }
+
+ public interface TintableBackgroundView {
+ method public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public final class VelocityTrackerCompat {
+ method public static void addMovement(android.view.VelocityTracker, android.view.MotionEvent);
+ method public static void clear(android.view.VelocityTracker);
+ method public static void computeCurrentVelocity(android.view.VelocityTracker, int);
+ method public static void computeCurrentVelocity(android.view.VelocityTracker, int, float);
+ method public static float getAxisVelocity(android.view.VelocityTracker, @androidx.core.view.VelocityTrackerCompat.VelocityTrackableMotionEventAxis int);
+ method public static float getAxisVelocity(android.view.VelocityTracker, @androidx.core.view.VelocityTrackerCompat.VelocityTrackableMotionEventAxis int, int);
+ method @Deprecated public static float getXVelocity(android.view.VelocityTracker!, int);
+ method @Deprecated public static float getYVelocity(android.view.VelocityTracker!, int);
+ method public static boolean isAxisSupported(android.view.VelocityTracker, @androidx.core.view.VelocityTrackerCompat.VelocityTrackableMotionEventAxis int);
+ method public static void recycle(android.view.VelocityTracker);
+ }
+
+ @IntDef({android.view.MotionEvent.AXIS_X, android.view.MotionEvent.AXIS_Y, android.view.MotionEvent.AXIS_SCROLL}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface VelocityTrackerCompat.VelocityTrackableMotionEventAxis {
+ }
+
+ public class ViewCompat {
+ ctor @Deprecated protected ViewCompat();
+ method public static int addAccessibilityAction(android.view.View, CharSequence, androidx.core.view.accessibility.AccessibilityViewCommand);
+ method public static void addKeyboardNavigationClusters(android.view.View, java.util.Collection<android.view.View!>, int);
+ method public static void addOnUnhandledKeyEventListener(android.view.View, androidx.core.view.ViewCompat.OnUnhandledKeyEventListenerCompat);
+ method @Deprecated public static androidx.core.view.ViewPropertyAnimatorCompat animate(android.view.View);
+ method @Deprecated public static boolean canScrollHorizontally(android.view.View!, int);
+ method @Deprecated public static boolean canScrollVertically(android.view.View!, int);
+ method public static void cancelDragAndDrop(android.view.View);
+ method @Deprecated public static int combineMeasuredStates(int, int);
+ method public static androidx.core.view.WindowInsetsCompat computeSystemWindowInsets(android.view.View, androidx.core.view.WindowInsetsCompat, android.graphics.Rect);
+ method public static androidx.core.view.WindowInsetsCompat dispatchApplyWindowInsets(android.view.View, androidx.core.view.WindowInsetsCompat);
+ method public static void dispatchFinishTemporaryDetach(android.view.View);
+ method public static boolean dispatchNestedFling(android.view.View, float, float, boolean);
+ method public static boolean dispatchNestedPreFling(android.view.View, float, float);
+ method public static boolean dispatchNestedPreScroll(android.view.View, int, int, int[]?, int[]?);
+ method public static boolean dispatchNestedPreScroll(android.view.View, int, int, int[]?, int[]?, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public static boolean dispatchNestedScroll(android.view.View, int, int, int, int, int[]?);
+ method public static boolean dispatchNestedScroll(android.view.View, int, int, int, int, int[]?, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public static void dispatchNestedScroll(android.view.View, int, int, int, int, int[]?, @androidx.core.view.ViewCompat.NestedScrollType int, int[]);
+ method public static void dispatchStartTemporaryDetach(android.view.View);
+ method public static void enableAccessibleClickableSpanSupport(android.view.View);
+ method @Deprecated public static int generateViewId();
+ method public static androidx.core.view.AccessibilityDelegateCompat? getAccessibilityDelegate(android.view.View);
+ method @Deprecated public static int getAccessibilityLiveRegion(android.view.View);
+ method public static androidx.core.view.accessibility.AccessibilityNodeProviderCompat? getAccessibilityNodeProvider(android.view.View);
+ method @UiThread public static CharSequence? getAccessibilityPaneTitle(android.view.View);
+ method @Deprecated public static float getAlpha(android.view.View!);
+ method public static androidx.core.view.autofill.AutofillIdCompat? getAutofillId(android.view.View);
+ method public static android.content.res.ColorStateList? getBackgroundTintList(android.view.View);
+ method public static android.graphics.PorterDuff.Mode? getBackgroundTintMode(android.view.View);
+ method @Deprecated public static android.graphics.Rect? getClipBounds(android.view.View);
+ method public static androidx.core.view.contentcapture.ContentCaptureSessionCompat? getContentCaptureSession(android.view.View);
+ method @Deprecated public static android.view.Display? getDisplay(android.view.View);
+ method public static float getElevation(android.view.View);
+ method @Deprecated public static boolean getFitsSystemWindows(android.view.View);
+ method @Deprecated public static int getImportantForAccessibility(android.view.View);
+ method public static int getImportantForAutofill(android.view.View);
+ method public static int getImportantForContentCapture(android.view.View);
+ method @Deprecated public static int getLabelFor(android.view.View);
+ method @Deprecated public static int getLayerType(android.view.View!);
+ method @Deprecated public static int getLayoutDirection(android.view.View);
+ method @Deprecated public static android.graphics.Matrix? getMatrix(android.view.View!);
+ method @Deprecated public static int getMeasuredHeightAndState(android.view.View!);
+ method @Deprecated public static int getMeasuredState(android.view.View!);
+ method @Deprecated public static int getMeasuredWidthAndState(android.view.View!);
+ method @Deprecated public static int getMinimumHeight(android.view.View);
+ method @Deprecated public static int getMinimumWidth(android.view.View);
+ method public static int getNextClusterForwardId(android.view.View);
+ method public static String![]? getOnReceiveContentMimeTypes(android.view.View);
+ method @Deprecated public static int getOverScrollMode(android.view.View!);
+ method @Deprecated @Px public static int getPaddingEnd(android.view.View);
+ method @Deprecated @Px public static int getPaddingStart(android.view.View);
+ method @Deprecated public static android.view.ViewParent? getParentForAccessibility(android.view.View);
+ method @Deprecated public static float getPivotX(android.view.View!);
+ method @Deprecated public static float getPivotY(android.view.View!);
+ method public static androidx.core.view.WindowInsetsCompat? getRootWindowInsets(android.view.View);
+ method @Deprecated public static float getRotation(android.view.View!);
+ method @Deprecated public static float getRotationX(android.view.View!);
+ method @Deprecated public static float getRotationY(android.view.View!);
+ method @Deprecated public static float getScaleX(android.view.View!);
+ method @Deprecated public static float getScaleY(android.view.View!);
+ method public static int getScrollIndicators(android.view.View);
+ method @UiThread public static CharSequence? getStateDescription(android.view.View);
+ method public static java.util.List<android.graphics.Rect!> getSystemGestureExclusionRects(android.view.View);
+ method public static String? getTransitionName(android.view.View);
+ method @Deprecated public static float getTranslationX(android.view.View!);
+ method @Deprecated public static float getTranslationY(android.view.View!);
+ method public static float getTranslationZ(android.view.View);
+ method @Deprecated public static androidx.core.view.WindowInsetsControllerCompat? getWindowInsetsController(android.view.View);
+ method @Deprecated public static int getWindowSystemUiVisibility(android.view.View);
+ method @Deprecated public static float getX(android.view.View!);
+ method @Deprecated public static float getY(android.view.View!);
+ method public static float getZ(android.view.View);
+ method public static boolean hasAccessibilityDelegate(android.view.View);
+ method public static boolean hasExplicitFocusable(android.view.View);
+ method public static boolean hasNestedScrollingParent(android.view.View);
+ method public static boolean hasNestedScrollingParent(android.view.View, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method @Deprecated public static boolean hasOnClickListeners(android.view.View);
+ method @Deprecated public static boolean hasOverlappingRendering(android.view.View);
+ method @Deprecated public static boolean hasTransientState(android.view.View);
+ method @UiThread public static boolean isAccessibilityHeading(android.view.View);
+ method @Deprecated public static boolean isAttachedToWindow(android.view.View);
+ method public static boolean isFocusedByDefault(android.view.View);
+ method public static boolean isImportantForAccessibility(android.view.View);
+ method public static boolean isImportantForAutofill(android.view.View);
+ method public static boolean isImportantForContentCapture(android.view.View);
+ method @Deprecated public static boolean isInLayout(android.view.View);
+ method public static boolean isKeyboardNavigationCluster(android.view.View);
+ method @Deprecated public static boolean isLaidOut(android.view.View);
+ method @Deprecated public static boolean isLayoutDirectionResolved(android.view.View);
+ method public static boolean isNestedScrollingEnabled(android.view.View);
+ method @Deprecated public static boolean isOpaque(android.view.View!);
+ method @Deprecated public static boolean isPaddingRelative(android.view.View);
+ method @UiThread public static boolean isScreenReaderFocusable(android.view.View);
+ method @Deprecated public static void jumpDrawablesToCurrentState(android.view.View!);
+ method public static android.view.View? keyboardNavigationClusterSearch(android.view.View, android.view.View?, @androidx.core.view.ViewCompat.FocusDirection int);
+ method public static void offsetLeftAndRight(android.view.View, int);
+ method public static void offsetTopAndBottom(android.view.View, int);
+ method public static androidx.core.view.WindowInsetsCompat onApplyWindowInsets(android.view.View, androidx.core.view.WindowInsetsCompat);
+ method @Deprecated public static void onInitializeAccessibilityEvent(android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method @Deprecated public static void onInitializeAccessibilityNodeInfo(android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat);
+ method @Deprecated public static void onPopulateAccessibilityEvent(android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method @Deprecated public static boolean performAccessibilityAction(android.view.View, int, android.os.Bundle?);
+ method public static boolean performHapticFeedback(android.view.View, int);
+ method public static boolean performHapticFeedback(android.view.View, int, int);
+ method public static androidx.core.view.ContentInfoCompat? performReceiveContent(android.view.View, androidx.core.view.ContentInfoCompat);
+ method @Deprecated public static void postInvalidateOnAnimation(android.view.View);
+ method @Deprecated public static void postInvalidateOnAnimation(android.view.View, int, int, int, int);
+ method @Deprecated public static void postOnAnimation(android.view.View, Runnable);
+ method @Deprecated public static void postOnAnimationDelayed(android.view.View, Runnable, long);
+ method public static void removeAccessibilityAction(android.view.View, int);
+ method public static void removeOnUnhandledKeyEventListener(android.view.View, androidx.core.view.ViewCompat.OnUnhandledKeyEventListenerCompat);
+ method public static void replaceAccessibilityAction(android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat, CharSequence?, androidx.core.view.accessibility.AccessibilityViewCommand?);
+ method public static void requestApplyInsets(android.view.View);
+ method public static <T extends android.view.View> T requireViewById(android.view.View, @IdRes int);
+ method @Deprecated public static int resolveSizeAndState(int, int, int);
+ method public static boolean restoreDefaultFocus(android.view.View);
+ method public static void saveAttributeDataForStyleable(android.view.View, android.content.Context, int[], android.util.AttributeSet?, android.content.res.TypedArray, int, int);
+ method public static void setAccessibilityDelegate(android.view.View, androidx.core.view.AccessibilityDelegateCompat?);
+ method @UiThread public static void setAccessibilityHeading(android.view.View, boolean);
+ method @Deprecated public static void setAccessibilityLiveRegion(android.view.View, int);
+ method @UiThread public static void setAccessibilityPaneTitle(android.view.View, CharSequence?);
+ method @Deprecated public static void setActivated(android.view.View!, boolean);
+ method @Deprecated public static void setAlpha(android.view.View!, @FloatRange(from=0.0, to=1.0) float);
+ method public static void setAutofillHints(android.view.View, java.lang.String!...?);
+ method public static void setAutofillId(android.view.View, androidx.core.view.autofill.AutofillIdCompat?);
+ method @Deprecated public static void setBackground(android.view.View, android.graphics.drawable.Drawable?);
+ method public static void setBackgroundTintList(android.view.View, android.content.res.ColorStateList?);
+ method public static void setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode?);
+ method @Deprecated public static void setChildrenDrawingOrderEnabled(android.view.ViewGroup!, boolean);
+ method @Deprecated public static void setClipBounds(android.view.View, android.graphics.Rect?);
+ method public static void setContentCaptureSession(android.view.View, androidx.core.view.contentcapture.ContentCaptureSessionCompat?);
+ method public static void setElevation(android.view.View, float);
+ method @Deprecated public static void setFitsSystemWindows(android.view.View!, boolean);
+ method public static void setFocusedByDefault(android.view.View, boolean);
+ method @Deprecated public static void setHasTransientState(android.view.View, boolean);
+ method @Deprecated @UiThread public static void setImportantForAccessibility(android.view.View, int);
+ method public static void setImportantForAutofill(android.view.View, int);
+ method public static void setImportantForContentCapture(android.view.View, int);
+ method public static void setKeyboardNavigationCluster(android.view.View, boolean);
+ method @Deprecated public static void setLabelFor(android.view.View, @IdRes int);
+ method @Deprecated public static void setLayerPaint(android.view.View, android.graphics.Paint?);
+ method @Deprecated public static void setLayerType(android.view.View!, int, android.graphics.Paint!);
+ method @Deprecated public static void setLayoutDirection(android.view.View, int);
+ method public static void setNestedScrollingEnabled(android.view.View, boolean);
+ method public static void setNextClusterForwardId(android.view.View, int);
+ method public static void setOnApplyWindowInsetsListener(android.view.View, androidx.core.view.OnApplyWindowInsetsListener?);
+ method public static void setOnReceiveContentListener(android.view.View, String![]?, androidx.core.view.OnReceiveContentListener?);
+ method @Deprecated public static void setOverScrollMode(android.view.View!, int);
+ method @Deprecated public static void setPaddingRelative(android.view.View, @Px int, @Px int, @Px int, @Px int);
+ method @Deprecated public static void setPivotX(android.view.View!, float);
+ method @Deprecated public static void setPivotY(android.view.View!, float);
+ method public static void setPointerIcon(android.view.View, androidx.core.view.PointerIconCompat?);
+ method @Deprecated public static void setRotation(android.view.View!, float);
+ method @Deprecated public static void setRotationX(android.view.View!, float);
+ method @Deprecated public static void setRotationY(android.view.View!, float);
+ method @Deprecated public static void setSaveFromParentEnabled(android.view.View!, boolean);
+ method @Deprecated public static void setScaleX(android.view.View!, float);
+ method @Deprecated public static void setScaleY(android.view.View!, float);
+ method @UiThread public static void setScreenReaderFocusable(android.view.View, boolean);
+ method public static void setScrollIndicators(android.view.View, @androidx.core.view.ViewCompat.ScrollIndicators int);
+ method public static void setScrollIndicators(android.view.View, @androidx.core.view.ViewCompat.ScrollIndicators int, @androidx.core.view.ViewCompat.ScrollIndicators int);
+ method @UiThread public static void setStateDescription(android.view.View, CharSequence?);
+ method public static void setSystemGestureExclusionRects(android.view.View, java.util.List<android.graphics.Rect!>);
+ method public static void setTooltipText(android.view.View, CharSequence?);
+ method public static void setTransitionName(android.view.View, String?);
+ method @Deprecated public static void setTranslationX(android.view.View!, float);
+ method @Deprecated public static void setTranslationY(android.view.View!, float);
+ method public static void setTranslationZ(android.view.View, float);
+ method public static void setWindowInsetsAnimationCallback(android.view.View, androidx.core.view.WindowInsetsAnimationCompat.Callback?);
+ method @Deprecated public static void setX(android.view.View!, float);
+ method @Deprecated public static void setY(android.view.View!, float);
+ method public static void setZ(android.view.View, float);
+ method public static boolean startDragAndDrop(android.view.View, android.content.ClipData?, android.view.View.DragShadowBuilder, Object?, int);
+ method public static boolean startNestedScroll(android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int);
+ method public static boolean startNestedScroll(android.view.View, @androidx.core.view.ViewCompat.ScrollAxis int, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public static void stopNestedScroll(android.view.View);
+ method public static void stopNestedScroll(android.view.View, @androidx.core.view.ViewCompat.NestedScrollType int);
+ method public static void updateDragShadow(android.view.View, android.view.View.DragShadowBuilder);
+ field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2
+ field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
+ field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
+ field @Deprecated public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0; // 0x0
+ field @Deprecated public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 2; // 0x2
+ field @Deprecated public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 4; // 0x4
+ field @Deprecated public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
+ field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_AUTO = 0; // 0x0
+ field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO = 2; // 0x2
+ field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS = 8; // 0x8
+ field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_YES = 1; // 0x1
+ field public static final int IMPORTANT_FOR_CONTENT_CAPTURE_YES_EXCLUDE_DESCENDANTS = 4; // 0x4
+ field @Deprecated public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
+ field @Deprecated public static final int LAYER_TYPE_NONE = 0; // 0x0
+ field @Deprecated public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
+ field @Deprecated public static final int LAYOUT_DIRECTION_INHERIT = 2; // 0x2
+ field @Deprecated public static final int LAYOUT_DIRECTION_LOCALE = 3; // 0x3
+ field @Deprecated public static final int LAYOUT_DIRECTION_LTR = 0; // 0x0
+ field @Deprecated public static final int LAYOUT_DIRECTION_RTL = 1; // 0x1
+ field @Deprecated public static final int MEASURED_HEIGHT_STATE_SHIFT = 16; // 0x10
+ field @Deprecated public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
+ field @Deprecated public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
+ field @Deprecated public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+ field @Deprecated public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
+ field @Deprecated public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
+ field @Deprecated public static final int OVER_SCROLL_NEVER = 2; // 0x2
+ field public static final int SCROLL_AXIS_HORIZONTAL = 1; // 0x1
+ field public static final int SCROLL_AXIS_NONE = 0; // 0x0
+ field public static final int SCROLL_AXIS_VERTICAL = 2; // 0x2
+ field public static final int SCROLL_INDICATOR_BOTTOM = 2; // 0x2
+ field public static final int SCROLL_INDICATOR_END = 32; // 0x20
+ field public static final int SCROLL_INDICATOR_LEFT = 4; // 0x4
+ field public static final int SCROLL_INDICATOR_RIGHT = 8; // 0x8
+ field public static final int SCROLL_INDICATOR_START = 16; // 0x10
+ field public static final int SCROLL_INDICATOR_TOP = 1; // 0x1
+ field public static final int TYPE_NON_TOUCH = 1; // 0x1
+ field public static final int TYPE_TOUCH = 0; // 0x0
+ }
+
+ @IntDef({android.view.View.FOCUS_LEFT, android.view.View.FOCUS_UP, android.view.View.FOCUS_RIGHT, android.view.View.FOCUS_DOWN, android.view.View.FOCUS_FORWARD, android.view.View.FOCUS_BACKWARD}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.FocusDirection {
+ }
+
+ @IntDef({android.view.View.FOCUS_LEFT, android.view.View.FOCUS_UP, android.view.View.FOCUS_RIGHT, android.view.View.FOCUS_DOWN}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.FocusRealDirection {
+ }
+
+ @IntDef({android.view.View.FOCUS_FORWARD, android.view.View.FOCUS_BACKWARD}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.FocusRelativeDirection {
+ }
+
+ @IntDef({androidx.core.view.ViewCompat.TYPE_TOUCH, androidx.core.view.ViewCompat.TYPE_NON_TOUCH}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.NestedScrollType {
+ }
+
+ public static interface ViewCompat.OnUnhandledKeyEventListenerCompat {
+ method public boolean onUnhandledKeyEvent(android.view.View, android.view.KeyEvent);
+ }
+
+ @IntDef(value={androidx.core.view.ViewCompat.SCROLL_AXIS_NONE, androidx.core.view.ViewCompat.SCROLL_AXIS_HORIZONTAL, androidx.core.view.ViewCompat.SCROLL_AXIS_VERTICAL}, flag=true) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.ScrollAxis {
+ }
+
+ @IntDef(flag=true, value={androidx.core.view.ViewCompat.SCROLL_INDICATOR_TOP, androidx.core.view.ViewCompat.SCROLL_INDICATOR_BOTTOM, androidx.core.view.ViewCompat.SCROLL_INDICATOR_LEFT, androidx.core.view.ViewCompat.SCROLL_INDICATOR_RIGHT, androidx.core.view.ViewCompat.SCROLL_INDICATOR_START, androidx.core.view.ViewCompat.SCROLL_INDICATOR_END}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.ScrollIndicators {
+ }
+
+ public final class ViewConfigurationCompat {
+ method public static float getScaledHorizontalScrollFactor(android.view.ViewConfiguration, android.content.Context);
+ method public static int getScaledHoverSlop(android.view.ViewConfiguration);
+ method public static int getScaledMaximumFlingVelocity(android.content.Context, android.view.ViewConfiguration, int, int, int);
+ method public static int getScaledMinimumFlingVelocity(android.content.Context, android.view.ViewConfiguration, int, int, int);
+ method @Deprecated public static int getScaledPagingTouchSlop(android.view.ViewConfiguration!);
+ method public static float getScaledVerticalScrollFactor(android.view.ViewConfiguration, android.content.Context);
+ method @Deprecated public static boolean hasPermanentMenuKey(android.view.ViewConfiguration!);
+ method public static boolean shouldShowMenuShortcutsWhenKeyboardPresent(android.view.ViewConfiguration, android.content.Context);
+ }
+
+ public final class ViewGroupCompat {
+ method public static int getLayoutMode(android.view.ViewGroup);
+ method @androidx.core.view.ViewCompat.ScrollAxis public static int getNestedScrollAxes(android.view.ViewGroup);
+ method public static boolean isTransitionGroup(android.view.ViewGroup);
+ method @Deprecated public static boolean onRequestSendAccessibilityEvent(android.view.ViewGroup!, android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method public static void setLayoutMode(android.view.ViewGroup, int);
+ method @Deprecated public static void setMotionEventSplittingEnabled(android.view.ViewGroup!, boolean);
+ method public static void setTransitionGroup(android.view.ViewGroup, boolean);
+ field public static final int LAYOUT_MODE_CLIP_BOUNDS = 0; // 0x0
+ field public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1; // 0x1
+ }
+
+ public final class ViewParentCompat {
+ method public static void notifySubtreeAccessibilityStateChanged(android.view.ViewParent, android.view.View, android.view.View, int);
+ method public static boolean onNestedFling(android.view.ViewParent, android.view.View, float, float, boolean);
+ method public static boolean onNestedPreFling(android.view.ViewParent, android.view.View, float, float);
+ method public static void onNestedPreScroll(android.view.ViewParent, android.view.View, int, int, int[]);
+ method public static void onNestedPreScroll(android.view.ViewParent, android.view.View, int, int, int[], int);
+ method public static void onNestedScroll(android.view.ViewParent, android.view.View, int, int, int, int);
+ method public static void onNestedScroll(android.view.ViewParent, android.view.View, int, int, int, int, int);
+ method public static void onNestedScroll(android.view.ViewParent, android.view.View, int, int, int, int, int, int[]);
+ method public static void onNestedScrollAccepted(android.view.ViewParent, android.view.View, android.view.View, int);
+ method public static void onNestedScrollAccepted(android.view.ViewParent, android.view.View, android.view.View, int, int);
+ method public static boolean onStartNestedScroll(android.view.ViewParent, android.view.View, android.view.View, int);
+ method public static boolean onStartNestedScroll(android.view.ViewParent, android.view.View, android.view.View, int, int);
+ method public static void onStopNestedScroll(android.view.ViewParent, android.view.View);
+ method public static void onStopNestedScroll(android.view.ViewParent, android.view.View, int);
+ method @Deprecated public static boolean requestSendAccessibilityEvent(android.view.ViewParent!, android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ }
+
+ public final class ViewPropertyAnimatorCompat {
+ method public androidx.core.view.ViewPropertyAnimatorCompat alpha(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat alphaBy(float);
+ method public void cancel();
+ method public long getDuration();
+ method public android.view.animation.Interpolator? getInterpolator();
+ method public long getStartDelay();
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotation(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotationBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotationX(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotationXBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotationY(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat rotationYBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat scaleX(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat scaleXBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat scaleY(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat scaleYBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat setDuration(long);
+ method public androidx.core.view.ViewPropertyAnimatorCompat setInterpolator(android.view.animation.Interpolator?);
+ method public androidx.core.view.ViewPropertyAnimatorCompat setListener(androidx.core.view.ViewPropertyAnimatorListener?);
+ method public androidx.core.view.ViewPropertyAnimatorCompat setStartDelay(long);
+ method public androidx.core.view.ViewPropertyAnimatorCompat setUpdateListener(androidx.core.view.ViewPropertyAnimatorUpdateListener?);
+ method public void start();
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationX(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationXBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationY(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationYBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationZ(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat translationZBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat withEndAction(Runnable);
+ method public androidx.core.view.ViewPropertyAnimatorCompat withLayer();
+ method public androidx.core.view.ViewPropertyAnimatorCompat withStartAction(Runnable);
+ method public androidx.core.view.ViewPropertyAnimatorCompat x(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat xBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat y(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat yBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat z(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat zBy(float);
+ }
+
+ public interface ViewPropertyAnimatorListener {
+ method public void onAnimationCancel(android.view.View);
+ method public void onAnimationEnd(android.view.View);
+ method public void onAnimationStart(android.view.View);
+ }
+
+ public class ViewPropertyAnimatorListenerAdapter implements androidx.core.view.ViewPropertyAnimatorListener {
+ ctor public ViewPropertyAnimatorListenerAdapter();
+ method public void onAnimationCancel(android.view.View);
+ method public void onAnimationEnd(android.view.View);
+ method public void onAnimationStart(android.view.View);
+ }
+
+ public interface ViewPropertyAnimatorUpdateListener {
+ method public void onAnimationUpdate(android.view.View);
+ }
+
+ public class ViewStructureCompat {
+ method public void setClassName(String);
+ method public void setContentDescription(CharSequence);
+ method public void setDimens(int, int, int, int, int, int);
+ method public void setText(CharSequence);
+ method @RequiresApi(23) public android.view.ViewStructure toViewStructure();
+ method @RequiresApi(23) public static androidx.core.view.ViewStructureCompat toViewStructureCompat(android.view.ViewStructure);
+ }
+
+ public final class WindowCompat {
+ method public static androidx.core.view.WindowInsetsControllerCompat getInsetsController(android.view.Window, android.view.View);
+ method public static <T extends android.view.View> T requireViewById(android.view.Window, @IdRes int);
+ method public static void setDecorFitsSystemWindows(android.view.Window, boolean);
+ field public static final int FEATURE_ACTION_BAR = 8; // 0x8
+ field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9
+ field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
+ }
+
+ public final class WindowInsetsAnimationCompat {
+ ctor public WindowInsetsAnimationCompat(@androidx.core.view.WindowInsetsCompat.Type.InsetsType int, android.view.animation.Interpolator?, long);
+ method @FloatRange(from=0.0f, to=1.0f) public float getAlpha();
+ method public long getDurationMillis();
+ method @FloatRange(from=0.0f, to=1.0f) public float getFraction();
+ method public float getInterpolatedFraction();
+ method public android.view.animation.Interpolator? getInterpolator();
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public int getTypeMask();
+ method public void setAlpha(@FloatRange(from=0.0f, to=1.0f) float);
+ method public void setFraction(@FloatRange(from=0.0f, to=1.0f) float);
+ }
+
+ public static final class WindowInsetsAnimationCompat.BoundsCompat {
+ ctor public WindowInsetsAnimationCompat.BoundsCompat(androidx.core.graphics.Insets, androidx.core.graphics.Insets);
+ method public androidx.core.graphics.Insets getLowerBound();
+ method public androidx.core.graphics.Insets getUpperBound();
+ method public androidx.core.view.WindowInsetsAnimationCompat.BoundsCompat inset(androidx.core.graphics.Insets);
+ method @RequiresApi(30) public android.view.WindowInsetsAnimation.Bounds toBounds();
+ method @RequiresApi(30) public static androidx.core.view.WindowInsetsAnimationCompat.BoundsCompat toBoundsCompat(android.view.WindowInsetsAnimation.Bounds);
+ }
+
+ public abstract static class WindowInsetsAnimationCompat.Callback {
+ ctor public WindowInsetsAnimationCompat.Callback(@androidx.core.view.WindowInsetsAnimationCompat.Callback.DispatchMode int);
+ method @androidx.core.view.WindowInsetsAnimationCompat.Callback.DispatchMode public final int getDispatchMode();
+ method public void onEnd(androidx.core.view.WindowInsetsAnimationCompat);
+ method public void onPrepare(androidx.core.view.WindowInsetsAnimationCompat);
+ method public abstract androidx.core.view.WindowInsetsCompat onProgress(androidx.core.view.WindowInsetsCompat, java.util.List<androidx.core.view.WindowInsetsAnimationCompat!>);
+ method public androidx.core.view.WindowInsetsAnimationCompat.BoundsCompat onStart(androidx.core.view.WindowInsetsAnimationCompat, androidx.core.view.WindowInsetsAnimationCompat.BoundsCompat);
+ field public static final int DISPATCH_MODE_CONTINUE_ON_SUBTREE = 1; // 0x1
+ field public static final int DISPATCH_MODE_STOP = 0; // 0x0
+ }
+
+ @IntDef({androidx.core.view.WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP, androidx.core.view.WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowInsetsAnimationCompat.Callback.DispatchMode {
+ }
+
+ public interface WindowInsetsAnimationControlListenerCompat {
+ method public void onCancelled(androidx.core.view.WindowInsetsAnimationControllerCompat?);
+ method public void onFinished(androidx.core.view.WindowInsetsAnimationControllerCompat);
+ method public void onReady(androidx.core.view.WindowInsetsAnimationControllerCompat, @androidx.core.view.WindowInsetsCompat.Type.InsetsType int);
+ }
+
+ public final class WindowInsetsAnimationControllerCompat {
+ method public void finish(boolean);
+ method public float getCurrentAlpha();
+ method @FloatRange(from=0.0f, to=1.0f) public float getCurrentFraction();
+ method public androidx.core.graphics.Insets getCurrentInsets();
+ method public androidx.core.graphics.Insets getHiddenStateInsets();
+ method public androidx.core.graphics.Insets getShownStateInsets();
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public int getTypes();
+ method public boolean isCancelled();
+ method public boolean isFinished();
+ method public boolean isReady();
+ method public void setInsetsAndAlpha(androidx.core.graphics.Insets?, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+ }
+
+ public class WindowInsetsCompat {
+ ctor public WindowInsetsCompat(androidx.core.view.WindowInsetsCompat?);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat consumeDisplayCutout();
+ method @Deprecated public androidx.core.view.WindowInsetsCompat consumeStableInsets();
+ method @Deprecated public androidx.core.view.WindowInsetsCompat consumeSystemWindowInsets();
+ method public androidx.core.view.DisplayCutoutCompat? getDisplayCutout();
+ method public androidx.core.graphics.Insets getInsets(@androidx.core.view.WindowInsetsCompat.Type.InsetsType int);
+ method public androidx.core.graphics.Insets getInsetsIgnoringVisibility(@androidx.core.view.WindowInsetsCompat.Type.InsetsType int);
+ method @Deprecated public androidx.core.graphics.Insets getMandatorySystemGestureInsets();
+ method @Deprecated public int getStableInsetBottom();
+ method @Deprecated public int getStableInsetLeft();
+ method @Deprecated public int getStableInsetRight();
+ method @Deprecated public int getStableInsetTop();
+ method @Deprecated public androidx.core.graphics.Insets getStableInsets();
+ method @Deprecated public androidx.core.graphics.Insets getSystemGestureInsets();
+ method @Deprecated public int getSystemWindowInsetBottom();
+ method @Deprecated public int getSystemWindowInsetLeft();
+ method @Deprecated public int getSystemWindowInsetRight();
+ method @Deprecated public int getSystemWindowInsetTop();
+ method @Deprecated public androidx.core.graphics.Insets getSystemWindowInsets();
+ method @Deprecated public androidx.core.graphics.Insets getTappableElementInsets();
+ method public boolean hasInsets();
+ method @Deprecated public boolean hasStableInsets();
+ method @Deprecated public boolean hasSystemWindowInsets();
+ method public androidx.core.view.WindowInsetsCompat inset(androidx.core.graphics.Insets);
+ method public androidx.core.view.WindowInsetsCompat inset(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int);
+ method public boolean isConsumed();
+ method public boolean isRound();
+ method public boolean isVisible(@androidx.core.view.WindowInsetsCompat.Type.InsetsType int);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat replaceSystemWindowInsets(android.graphics.Rect);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat replaceSystemWindowInsets(int, int, int, int);
+ method @RequiresApi(20) public android.view.WindowInsets? toWindowInsets();
+ method @RequiresApi(20) public static androidx.core.view.WindowInsetsCompat toWindowInsetsCompat(android.view.WindowInsets);
+ method @RequiresApi(20) public static androidx.core.view.WindowInsetsCompat toWindowInsetsCompat(android.view.WindowInsets, android.view.View?);
+ field public static final androidx.core.view.WindowInsetsCompat CONSUMED;
+ }
+
+ public static final class WindowInsetsCompat.Builder {
+ ctor public WindowInsetsCompat.Builder();
+ ctor public WindowInsetsCompat.Builder(androidx.core.view.WindowInsetsCompat);
+ method public androidx.core.view.WindowInsetsCompat build();
+ method public androidx.core.view.WindowInsetsCompat.Builder setDisplayCutout(androidx.core.view.DisplayCutoutCompat?);
+ method public androidx.core.view.WindowInsetsCompat.Builder setInsets(@androidx.core.view.WindowInsetsCompat.Type.InsetsType int, androidx.core.graphics.Insets);
+ method public androidx.core.view.WindowInsetsCompat.Builder setInsetsIgnoringVisibility(@androidx.core.view.WindowInsetsCompat.Type.InsetsType int, androidx.core.graphics.Insets);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat.Builder setMandatorySystemGestureInsets(androidx.core.graphics.Insets);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat.Builder setStableInsets(androidx.core.graphics.Insets);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat.Builder setSystemGestureInsets(androidx.core.graphics.Insets);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat.Builder setSystemWindowInsets(androidx.core.graphics.Insets);
+ method @Deprecated public androidx.core.view.WindowInsetsCompat.Builder setTappableElementInsets(androidx.core.graphics.Insets);
+ method public androidx.core.view.WindowInsetsCompat.Builder setVisible(@androidx.core.view.WindowInsetsCompat.Type.InsetsType int, boolean);
+ }
+
+ public static final class WindowInsetsCompat.Type {
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public static int captionBar();
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public static int displayCutout();
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public static int ime();
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public static int mandatorySystemGestures();
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public static int navigationBars();
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public static int statusBars();
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public static int systemBars();
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public static int systemGestures();
+ method @androidx.core.view.WindowInsetsCompat.Type.InsetsType public static int tappableElement();
+ }
+
+ @IntDef(flag=true, value={0x1, 0x2, 0x4, 0x8, 0x100, 0x10, 0x20, 0x40, 0x80}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowInsetsCompat.Type.InsetsType {
+ }
+
+ public final class WindowInsetsControllerCompat {
+ ctor public WindowInsetsControllerCompat(android.view.Window, android.view.View);
+ method public void addOnControllableInsetsChangedListener(androidx.core.view.WindowInsetsControllerCompat.OnControllableInsetsChangedListener);
+ method public void controlWindowInsetsAnimation(@androidx.core.view.WindowInsetsCompat.Type.InsetsType int, long, android.view.animation.Interpolator?, android.os.CancellationSignal?, androidx.core.view.WindowInsetsAnimationControlListenerCompat);
+ method public int getSystemBarsBehavior();
+ method public void hide(@androidx.core.view.WindowInsetsCompat.Type.InsetsType int);
+ method public boolean isAppearanceLightNavigationBars();
+ method public boolean isAppearanceLightStatusBars();
+ method public void removeOnControllableInsetsChangedListener(androidx.core.view.WindowInsetsControllerCompat.OnControllableInsetsChangedListener);
+ method public void setAppearanceLightNavigationBars(boolean);
+ method public void setAppearanceLightStatusBars(boolean);
+ method public void setSystemBarsBehavior(int);
+ method public void show(@androidx.core.view.WindowInsetsCompat.Type.InsetsType int);
+ method @Deprecated @RequiresApi(30) public static androidx.core.view.WindowInsetsControllerCompat toWindowInsetsControllerCompat(android.view.WindowInsetsController);
+ field public static final int BEHAVIOR_DEFAULT = 1; // 0x1
+ field @Deprecated public static final int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1; // 0x1
+ field @Deprecated public static final int BEHAVIOR_SHOW_BARS_BY_TOUCH = 0; // 0x0
+ field public static final int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2; // 0x2
+ }
+
+ public static interface WindowInsetsControllerCompat.OnControllableInsetsChangedListener {
+ method public void onControllableInsetsChanged(androidx.core.view.WindowInsetsControllerCompat, @androidx.core.view.WindowInsetsCompat.Type.InsetsType int);
+ }
+
+}
+
+package androidx.core.view.accessibility {
+
+ public final class AccessibilityClickableSpanCompat extends android.text.style.ClickableSpan {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public AccessibilityClickableSpanCompat(int, androidx.core.view.accessibility.AccessibilityNodeInfoCompat, int);
+ method public void onClick(android.view.View);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String SPAN_ID = "ACCESSIBILITY_CLICKABLE_SPAN_ID";
+ }
+
+ public final class AccessibilityEventCompat {
+ method @Deprecated public static void appendRecord(android.view.accessibility.AccessibilityEvent!, androidx.core.view.accessibility.AccessibilityRecordCompat!);
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! asRecord(android.view.accessibility.AccessibilityEvent!);
+ method public static int getAction(android.view.accessibility.AccessibilityEvent);
+ method @androidx.core.view.accessibility.AccessibilityEventCompat.ContentChangeType public static int getContentChangeTypes(android.view.accessibility.AccessibilityEvent);
+ method public static int getMovementGranularity(android.view.accessibility.AccessibilityEvent);
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! getRecord(android.view.accessibility.AccessibilityEvent!, int);
+ method @Deprecated public static int getRecordCount(android.view.accessibility.AccessibilityEvent!);
+ method public static boolean isAccessibilityDataSensitive(android.view.accessibility.AccessibilityEvent);
+ method public static void setAccessibilityDataSensitive(android.view.accessibility.AccessibilityEvent, boolean);
+ method public static void setAction(android.view.accessibility.AccessibilityEvent, int);
+ method public static void setContentChangeTypes(android.view.accessibility.AccessibilityEvent, @androidx.core.view.accessibility.AccessibilityEventCompat.ContentChangeType int);
+ method public static void setMovementGranularity(android.view.accessibility.AccessibilityEvent, int);
+ field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+ field public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 1024; // 0x400
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
+ field public static final int CONTENT_CHANGE_TYPE_ENABLED = 4096; // 0x1000
+ field public static final int CONTENT_CHANGE_TYPE_ERROR = 2048; // 0x800
+ field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
+ field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
+ field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
+ field public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 64; // 0x40
+ field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1
+ field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2
+ field public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0; // 0x0
+ field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
+ field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
+ field public static final int TYPE_ASSIST_READING_CONTEXT = 16777216; // 0x1000000
+ field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000
+ field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000
+ field @Deprecated public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 1024; // 0x400
+ field @Deprecated public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 512; // 0x200
+ field public static final int TYPE_TOUCH_INTERACTION_END = 2097152; // 0x200000
+ field public static final int TYPE_TOUCH_INTERACTION_START = 1048576; // 0x100000
+ field public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 32768; // 0x8000
+ field public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 65536; // 0x10000
+ field public static final int TYPE_VIEW_CONTEXT_CLICKED = 8388608; // 0x800000
+ field @Deprecated public static final int TYPE_VIEW_HOVER_ENTER = 128; // 0x80
+ field @Deprecated public static final int TYPE_VIEW_HOVER_EXIT = 256; // 0x100
+ field @Deprecated public static final int TYPE_VIEW_SCROLLED = 4096; // 0x1000
+ field public static final int TYPE_VIEW_TARGETED_BY_SCROLL = 67108864; // 0x4000000
+ field @Deprecated public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192; // 0x2000
+ field public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 131072; // 0x20000
+ field public static final int TYPE_WINDOWS_CHANGED = 4194304; // 0x400000
+ field @Deprecated public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800
+ }
+
+ @IntDef(flag=true, value={androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION, androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION, androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_SUBTREE, androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_TEXT, androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_UNDEFINED, androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_DRAG_STARTED, androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_DRAG_DROPPED, androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_DRAG_CANCELLED, androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_CONTENT_INVALID, androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_ERROR, androidx.core.view.accessibility.AccessibilityEventCompat.CONTENT_CHANGE_TYPE_ENABLED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface AccessibilityEventCompat.ContentChangeType {
+ }
+
+ public final class AccessibilityManagerCompat {
+ method @Deprecated public static boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager!, androidx.core.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListener!);
+ method public static boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager, androidx.core.view.accessibility.AccessibilityManagerCompat.TouchExplorationStateChangeListener);
+ method @Deprecated public static java.util.List<android.accessibilityservice.AccessibilityServiceInfo!>! getEnabledAccessibilityServiceList(android.view.accessibility.AccessibilityManager!, int);
+ method @Deprecated public static java.util.List<android.accessibilityservice.AccessibilityServiceInfo!>! getInstalledAccessibilityServiceList(android.view.accessibility.AccessibilityManager!);
+ method public static boolean isRequestFromAccessibilityTool(android.view.accessibility.AccessibilityManager);
+ method @Deprecated public static boolean isTouchExplorationEnabled(android.view.accessibility.AccessibilityManager!);
+ method @Deprecated public static boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager!, androidx.core.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListener!);
+ method public static boolean removeTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager, androidx.core.view.accessibility.AccessibilityManagerCompat.TouchExplorationStateChangeListener);
+ }
+
+ @Deprecated public static interface AccessibilityManagerCompat.AccessibilityStateChangeListener {
+ method @Deprecated public void onAccessibilityStateChanged(boolean);
+ }
+
+ @Deprecated public abstract static class AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat implements androidx.core.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListener {
+ ctor @Deprecated public AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat();
+ }
+
+ public static interface AccessibilityManagerCompat.TouchExplorationStateChangeListener {
+ method public void onTouchExplorationStateChanged(boolean);
+ }
+
+ public class AccessibilityNodeInfoCompat {
+ ctor @Deprecated public AccessibilityNodeInfoCompat(Object!);
+ method public void addAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
+ method public void addAction(int);
+ method public void addChild(android.view.View!);
+ method public void addChild(android.view.View!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addSpansToExtras(CharSequence!, android.view.View!);
+ method public boolean canOpenPopup();
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat!>! findAccessibilityNodeInfosByText(String!);
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat!>! findAccessibilityNodeInfosByViewId(String!);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! findFocus(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! focusSearch(int);
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!>! getActionList();
+ method @Deprecated public int getActions();
+ method public java.util.List<java.lang.String!> getAvailableExtraData();
+ method @Deprecated public void getBoundsInParent(android.graphics.Rect!);
+ method public void getBoundsInScreen(android.graphics.Rect!);
+ method public void getBoundsInWindow(android.graphics.Rect);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getChild(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getChild(int, int);
+ method public int getChildCount();
+ method public CharSequence! getClassName();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.text.style.ClickableSpan![]! getClickableSpans(CharSequence!);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat! getCollectionInfo();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat! getCollectionItemInfo();
+ method public CharSequence? getContainerTitle();
+ method public CharSequence! getContentDescription();
+ method public int getDrawingOrder();
+ method public CharSequence! getError();
+ method public android.view.accessibility.AccessibilityNodeInfo.ExtraRenderingInfo? getExtraRenderingInfo();
+ method public android.os.Bundle! getExtras();
+ method public CharSequence? getHintText();
+ method @Deprecated public Object! getInfo();
+ method public int getInputType();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getLabelFor();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getLabeledBy();
+ method public int getLiveRegion();
+ method public int getMaxTextLength();
+ method public long getMinDurationBetweenContentChangesMillis();
+ method public int getMovementGranularities();
+ method public CharSequence! getPackageName();
+ method public CharSequence? getPaneTitle();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getParent();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getParent(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat! getRangeInfo();
+ method public CharSequence? getRoleDescription();
+ method public CharSequence? getStateDescription();
+ method public CharSequence! getText();
+ method public int getTextSelectionEnd();
+ method public int getTextSelectionStart();
+ method public CharSequence? getTooltipText();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.TouchDelegateInfoCompat? getTouchDelegateInfo();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getTraversalAfter();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getTraversalBefore();
+ method public String? getUniqueId();
+ method public String! getViewIdResourceName();
+ method public androidx.core.view.accessibility.AccessibilityWindowInfoCompat! getWindow();
+ method public int getWindowId();
+ method public boolean hasRequestInitialAccessibilityFocus();
+ method public boolean isAccessibilityDataSensitive();
+ method public boolean isAccessibilityFocused();
+ method public boolean isCheckable();
+ method public boolean isChecked();
+ method public boolean isClickable();
+ method public boolean isContentInvalid();
+ method public boolean isContextClickable();
+ method public boolean isDismissable();
+ method public boolean isEditable();
+ method public boolean isEnabled();
+ method public boolean isFocusable();
+ method public boolean isFocused();
+ method public boolean isGranularScrollingSupported();
+ method public boolean isHeading();
+ method public boolean isImportantForAccessibility();
+ method public boolean isLongClickable();
+ method public boolean isMultiLine();
+ method public boolean isPassword();
+ method public boolean isScreenReaderFocusable();
+ method public boolean isScrollable();
+ method public boolean isSelected();
+ method public boolean isShowingHintText();
+ method public boolean isTextEntryKey();
+ method public boolean isTextSelectable();
+ method public boolean isVisibleToUser();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain(android.view.View!);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain(android.view.View!, int);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain(androidx.core.view.accessibility.AccessibilityNodeInfoCompat!);
+ method public boolean performAction(int);
+ method public boolean performAction(int, android.os.Bundle!);
+ method @Deprecated public void recycle();
+ method public boolean refresh();
+ method public boolean removeAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
+ method public boolean removeChild(android.view.View!);
+ method public boolean removeChild(android.view.View!, int);
+ method public void setAccessibilityDataSensitive(boolean);
+ method public void setAccessibilityFocused(boolean);
+ method public void setAvailableExtraData(java.util.List<java.lang.String!>);
+ method @Deprecated public void setBoundsInParent(android.graphics.Rect!);
+ method public void setBoundsInScreen(android.graphics.Rect!);
+ method public void setBoundsInWindow(android.graphics.Rect);
+ method public void setCanOpenPopup(boolean);
+ method public void setCheckable(boolean);
+ method public void setChecked(boolean);
+ method public void setClassName(CharSequence!);
+ method public void setClickable(boolean);
+ method public void setCollectionInfo(Object!);
+ method public void setCollectionItemInfo(Object!);
+ method public void setContainerTitle(CharSequence?);
+ method public void setContentDescription(CharSequence!);
+ method public void setContentInvalid(boolean);
+ method public void setContextClickable(boolean);
+ method public void setDismissable(boolean);
+ method public void setDrawingOrder(int);
+ method public void setEditable(boolean);
+ method public void setEnabled(boolean);
+ method public void setError(CharSequence!);
+ method public void setFocusable(boolean);
+ method public void setFocused(boolean);
+ method public void setGranularScrollingSupported(boolean);
+ method public void setHeading(boolean);
+ method public void setHintText(CharSequence?);
+ method public void setImportantForAccessibility(boolean);
+ method public void setInputType(int);
+ method public void setLabelFor(android.view.View!);
+ method public void setLabelFor(android.view.View!, int);
+ method public void setLabeledBy(android.view.View!);
+ method public void setLabeledBy(android.view.View!, int);
+ method public void setLiveRegion(int);
+ method public void setLongClickable(boolean);
+ method public void setMaxTextLength(int);
+ method public void setMinDurationBetweenContentChangesMillis(long);
+ method public void setMovementGranularities(int);
+ method public void setMultiLine(boolean);
+ method public void setPackageName(CharSequence!);
+ method public void setPaneTitle(CharSequence?);
+ method public void setParent(android.view.View!);
+ method public void setParent(android.view.View!, int);
+ method public void setPassword(boolean);
+ method public void setQueryFromAppProcessEnabled(android.view.View, boolean);
+ method public void setRangeInfo(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat!);
+ method public void setRequestInitialAccessibilityFocus(boolean);
+ method public void setRoleDescription(CharSequence?);
+ method public void setScreenReaderFocusable(boolean);
+ method public void setScrollable(boolean);
+ method public void setSelected(boolean);
+ method public void setShowingHintText(boolean);
+ method public void setSource(android.view.View!);
+ method public void setSource(android.view.View!, int);
+ method public void setStateDescription(CharSequence?);
+ method public void setText(CharSequence!);
+ method public void setTextEntryKey(boolean);
+ method public void setTextSelectable(boolean);
+ method public void setTextSelection(int, int);
+ method public void setTooltipText(CharSequence?);
+ method public void setTouchDelegateInfo(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.TouchDelegateInfoCompat);
+ method public void setTraversalAfter(android.view.View!);
+ method public void setTraversalAfter(android.view.View!, int);
+ method public void setTraversalBefore(android.view.View!);
+ method public void setTraversalBefore(android.view.View!, int);
+ method public void setUniqueId(String?);
+ method public void setViewIdResourceName(String!);
+ method public void setVisibleToUser(boolean);
+ method public android.view.accessibility.AccessibilityNodeInfo! unwrap();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! wrap(android.view.accessibility.AccessibilityNodeInfo);
+ field public static final int ACTION_ACCESSIBILITY_FOCUS = 64; // 0x40
+ field public static final String ACTION_ARGUMENT_COLUMN_INT = "android.view.accessibility.action.ARGUMENT_COLUMN_INT";
+ field public static final String ACTION_ARGUMENT_DIRECTION_INT = "androidx.core.view.accessibility.action.ARGUMENT_DIRECTION_INT";
+ field public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN = "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
+ field public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING = "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+ field public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
+ field public static final String ACTION_ARGUMENT_MOVE_WINDOW_X = "ACTION_ARGUMENT_MOVE_WINDOW_X";
+ field public static final String ACTION_ARGUMENT_MOVE_WINDOW_Y = "ACTION_ARGUMENT_MOVE_WINDOW_Y";
+ field public static final String ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT = "android.view.accessibility.action.ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT";
+ field public static final String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
+ field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";
+ field public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "androidx.core.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
+ field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
+ field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
+ field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+ field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80
+ field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2
+ field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8
+ field public static final int ACTION_CLICK = 16; // 0x10
+ field public static final int ACTION_COLLAPSE = 524288; // 0x80000
+ field public static final int ACTION_COPY = 16384; // 0x4000
+ field public static final int ACTION_CUT = 65536; // 0x10000
+ field public static final int ACTION_DISMISS = 1048576; // 0x100000
+ field public static final int ACTION_EXPAND = 262144; // 0x40000
+ field public static final int ACTION_FOCUS = 1; // 0x1
+ field public static final int ACTION_LONG_CLICK = 32; // 0x20
+ field public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 256; // 0x100
+ field public static final int ACTION_NEXT_HTML_ELEMENT = 1024; // 0x400
+ field public static final int ACTION_PASTE = 32768; // 0x8000
+ field public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 512; // 0x200
+ field public static final int ACTION_PREVIOUS_HTML_ELEMENT = 2048; // 0x800
+ field public static final int ACTION_SCROLL_BACKWARD = 8192; // 0x2000
+ field public static final int ACTION_SCROLL_FORWARD = 4096; // 0x1000
+ field public static final int ACTION_SELECT = 4; // 0x4
+ field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
+ field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+ field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.core.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+ field public static final int EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH = 20000; // 0x4e20
+ field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.core.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+ field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.core.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
+ field public static final int FLAG_PREFETCH_ANCESTORS = 1; // 0x1
+ field public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 16; // 0x10
+ field public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 8; // 0x8
+ field public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 4; // 0x4
+ field public static final int FLAG_PREFETCH_SIBLINGS = 2; // 0x2
+ field public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 32; // 0x20
+ field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
+ field public static final int FOCUS_INPUT = 1; // 0x1
+ field public static final int MAX_NUMBER_OF_PREFETCHED_NODES = 50; // 0x32
+ field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
+ field public static final int MOVEMENT_GRANULARITY_LINE = 4; // 0x4
+ field public static final int MOVEMENT_GRANULARITY_PAGE = 16; // 0x10
+ field public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 8; // 0x8
+ field public static final int MOVEMENT_GRANULARITY_WORD = 2; // 0x2
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int mParentVirtualDescendantId;
+ }
+
+ public static class AccessibilityNodeInfoCompat.AccessibilityActionCompat {
+ ctor public AccessibilityNodeInfoCompat.AccessibilityActionCompat(int, CharSequence!);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public AccessibilityNodeInfoCompat.AccessibilityActionCompat(int, CharSequence!, androidx.core.view.accessibility.AccessibilityViewCommand!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! createReplacementAction(CharSequence!, androidx.core.view.accessibility.AccessibilityViewCommand!);
+ method public int getId();
+ method public CharSequence! getLabel();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean perform(android.view.View!, android.os.Bundle!);
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_ACCESSIBILITY_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLEAR_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLEAR_SELECTION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLICK;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_COLLAPSE;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CONTEXT_CLICK;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_COPY;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CUT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_DISMISS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_DRAG_CANCEL;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_DRAG_DROP;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_DRAG_START;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_EXPAND;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_HIDE_TOOLTIP;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_IME_ENTER;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_LONG_CLICK;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_MOVE_WINDOW;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_NEXT_AT_MOVEMENT_GRANULARITY;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_NEXT_HTML_ELEMENT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PAGE_DOWN;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PAGE_LEFT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PAGE_RIGHT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PAGE_UP;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_PASTE;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PRESS_AND_HOLD;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_PREVIOUS_HTML_ELEMENT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_BACKWARD;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_DOWN;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_FORWARD;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_SCROLL_IN_DIRECTION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_LEFT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_RIGHT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_TO_POSITION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_UP;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SELECT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SET_PROGRESS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SET_SELECTION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SET_TEXT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SHOW_ON_SCREEN;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_SHOW_TEXT_SUGGESTIONS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SHOW_TOOLTIP;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected final androidx.core.view.accessibility.AccessibilityViewCommand! mCommand;
+ }
+
+ public static class AccessibilityNodeInfoCompat.CollectionInfoCompat {
+ method public int getColumnCount();
+ method public int getRowCount();
+ method public int getSelectionMode();
+ method public boolean isHierarchical();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat! obtain(int, int, boolean);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat! obtain(int, int, boolean, int);
+ field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
+ field public static final int SELECTION_MODE_NONE = 0; // 0x0
+ field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
+ }
+
+ public static class AccessibilityNodeInfoCompat.CollectionItemInfoCompat {
+ method public int getColumnIndex();
+ method public int getColumnSpan();
+ method public String? getColumnTitle();
+ method public int getRowIndex();
+ method public int getRowSpan();
+ method public String? getRowTitle();
+ method @Deprecated public boolean isHeading();
+ method public boolean isSelected();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat! obtain(int, int, int, int, boolean);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat! obtain(int, int, int, int, boolean, boolean);
+ }
+
+ public static final class AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder {
+ ctor public AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat build();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setColumnIndex(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setColumnSpan(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setColumnTitle(String?);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setHeading(boolean);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setRowIndex(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setRowSpan(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setRowTitle(String?);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat.Builder setSelected(boolean);
+ }
+
+ public static class AccessibilityNodeInfoCompat.RangeInfoCompat {
+ ctor public AccessibilityNodeInfoCompat.RangeInfoCompat(int, float, float, float);
+ method public float getCurrent();
+ method public float getMax();
+ method public float getMin();
+ method public int getType();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat! obtain(int, float, float, float);
+ field public static final int RANGE_TYPE_FLOAT = 1; // 0x1
+ field public static final int RANGE_TYPE_INT = 0; // 0x0
+ field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
+ }
+
+ public static final class AccessibilityNodeInfoCompat.TouchDelegateInfoCompat {
+ ctor public AccessibilityNodeInfoCompat.TouchDelegateInfoCompat(java.util.Map<android.graphics.Region!,android.view.View!>);
+ method public android.graphics.Region? getRegionAt(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getRegionCount();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getTargetForRegion(android.graphics.Region);
+ }
+
+ public class AccessibilityNodeProviderCompat {
+ ctor public AccessibilityNodeProviderCompat();
+ ctor public AccessibilityNodeProviderCompat(Object?);
+ method public void addExtraDataToAccessibilityNodeInfo(int, androidx.core.view.accessibility.AccessibilityNodeInfoCompat, String, android.os.Bundle?);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? createAccessibilityNodeInfo(int);
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat!>? findAccessibilityNodeInfosByText(String, int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? findFocus(int);
+ method public Object? getProvider();
+ method public boolean performAction(int, int, android.os.Bundle?);
+ field public static final int HOST_VIEW_ID = -1; // 0xffffffff
+ }
+
+ public class AccessibilityRecordCompat {
+ ctor @Deprecated public AccessibilityRecordCompat(Object!);
+ method @Deprecated public boolean equals(Object?);
+ method @Deprecated public int getAddedCount();
+ method @Deprecated public CharSequence! getBeforeText();
+ method @Deprecated public CharSequence! getClassName();
+ method @Deprecated public CharSequence! getContentDescription();
+ method @Deprecated public int getCurrentItemIndex();
+ method @Deprecated public int getFromIndex();
+ method @Deprecated public Object! getImpl();
+ method @Deprecated public int getItemCount();
+ method @Deprecated public int getMaxScrollX();
+ method public static int getMaxScrollX(android.view.accessibility.AccessibilityRecord);
+ method @Deprecated public int getMaxScrollY();
+ method public static int getMaxScrollY(android.view.accessibility.AccessibilityRecord);
+ method @Deprecated public android.os.Parcelable! getParcelableData();
+ method @Deprecated public int getRemovedCount();
+ method @Deprecated public int getScrollX();
+ method @Deprecated public int getScrollY();
+ method @Deprecated public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getSource();
+ method @Deprecated public java.util.List<java.lang.CharSequence!>! getText();
+ method @Deprecated public int getToIndex();
+ method @Deprecated public int getWindowId();
+ method @Deprecated public int hashCode();
+ method @Deprecated public boolean isChecked();
+ method @Deprecated public boolean isEnabled();
+ method @Deprecated public boolean isFullScreen();
+ method @Deprecated public boolean isPassword();
+ method @Deprecated public boolean isScrollable();
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! obtain();
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! obtain(androidx.core.view.accessibility.AccessibilityRecordCompat!);
+ method @Deprecated public void recycle();
+ method @Deprecated public void setAddedCount(int);
+ method @Deprecated public void setBeforeText(CharSequence!);
+ method @Deprecated public void setChecked(boolean);
+ method @Deprecated public void setClassName(CharSequence!);
+ method @Deprecated public void setContentDescription(CharSequence!);
+ method @Deprecated public void setCurrentItemIndex(int);
+ method @Deprecated public void setEnabled(boolean);
+ method @Deprecated public void setFromIndex(int);
+ method @Deprecated public void setFullScreen(boolean);
+ method @Deprecated public void setItemCount(int);
+ method public static void setMaxScrollX(android.view.accessibility.AccessibilityRecord, int);
+ method @Deprecated public void setMaxScrollX(int);
+ method public static void setMaxScrollY(android.view.accessibility.AccessibilityRecord, int);
+ method @Deprecated public void setMaxScrollY(int);
+ method @Deprecated public void setParcelableData(android.os.Parcelable!);
+ method @Deprecated public void setPassword(boolean);
+ method @Deprecated public void setRemovedCount(int);
+ method @Deprecated public void setScrollX(int);
+ method @Deprecated public void setScrollY(int);
+ method @Deprecated public void setScrollable(boolean);
+ method public static void setSource(android.view.accessibility.AccessibilityRecord, android.view.View?, int);
+ method @Deprecated public void setSource(android.view.View!);
+ method @Deprecated public void setSource(android.view.View!, int);
+ method @Deprecated public void setToIndex(int);
+ }
+
+ public interface AccessibilityViewCommand {
+ method public boolean perform(android.view.View, androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments?);
+ }
+
+ public abstract static class AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.CommandArguments();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setBundle(android.os.Bundle?);
+ }
+
+ public static final class AccessibilityViewCommand.MoveAtGranularityArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.MoveAtGranularityArguments();
+ method public boolean getExtendSelection();
+ method public int getGranularity();
+ }
+
+ public static final class AccessibilityViewCommand.MoveHtmlArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.MoveHtmlArguments();
+ method public String? getHTMLElement();
+ }
+
+ public static final class AccessibilityViewCommand.MoveWindowArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.MoveWindowArguments();
+ method public int getX();
+ method public int getY();
+ }
+
+ public static final class AccessibilityViewCommand.ScrollToPositionArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.ScrollToPositionArguments();
+ method public int getColumn();
+ method public int getRow();
+ }
+
+ public static final class AccessibilityViewCommand.SetProgressArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.SetProgressArguments();
+ method public float getProgress();
+ }
+
+ public static final class AccessibilityViewCommand.SetSelectionArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.SetSelectionArguments();
+ method public int getEnd();
+ method public int getStart();
+ }
+
+ public static final class AccessibilityViewCommand.SetTextArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.SetTextArguments();
+ method public CharSequence? getText();
+ }
+
+ public class AccessibilityWindowInfoCompat {
+ ctor public AccessibilityWindowInfoCompat();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getAnchor();
+ method public void getBoundsInScreen(android.graphics.Rect);
+ method public androidx.core.view.accessibility.AccessibilityWindowInfoCompat? getChild(int);
+ method public int getChildCount();
+ method public int getDisplayId();
+ method public int getId();
+ method public int getLayer();
+ method public androidx.core.os.LocaleListCompat getLocales();
+ method public androidx.core.view.accessibility.AccessibilityWindowInfoCompat? getParent();
+ method public void getRegionInScreen(android.graphics.Region);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getRoot();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? getRoot(int);
+ method public CharSequence? getTitle();
+ method public long getTransitionTimeMillis();
+ method public int getType();
+ method public boolean isAccessibilityFocused();
+ method public boolean isActive();
+ method public boolean isFocused();
+ method public boolean isInPictureInPictureMode();
+ method public static androidx.core.view.accessibility.AccessibilityWindowInfoCompat? obtain();
+ method public static androidx.core.view.accessibility.AccessibilityWindowInfoCompat? obtain(androidx.core.view.accessibility.AccessibilityWindowInfoCompat?);
+ method @Deprecated public void recycle();
+ method public android.view.accessibility.AccessibilityWindowInfo? unwrap();
+ field public static final int TYPE_ACCESSIBILITY_OVERLAY = 4; // 0x4
+ field public static final int TYPE_APPLICATION = 1; // 0x1
+ field public static final int TYPE_INPUT_METHOD = 2; // 0x2
+ field public static final int TYPE_MAGNIFICATION_OVERLAY = 6; // 0x6
+ field public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5; // 0x5
+ field public static final int TYPE_SYSTEM = 3; // 0x3
+ }
+
+}
+
+package androidx.core.view.animation {
+
+ public final class PathInterpolatorCompat {
+ method public static android.view.animation.Interpolator create(android.graphics.Path);
+ method public static android.view.animation.Interpolator create(float, float);
+ method public static android.view.animation.Interpolator create(float, float, float, float);
+ }
+
+}
+
+package androidx.core.view.autofill {
+
+ public class AutofillIdCompat {
+ method @RequiresApi(26) public android.view.autofill.AutofillId toAutofillId();
+ method @RequiresApi(26) public static androidx.core.view.autofill.AutofillIdCompat toAutofillIdCompat(android.view.autofill.AutofillId);
+ }
+
+}
+
+package androidx.core.view.contentcapture {
+
+ public class ContentCaptureSessionCompat {
+ method public android.view.autofill.AutofillId? newAutofillId(long);
+ method public androidx.core.view.ViewStructureCompat? newVirtualViewStructure(android.view.autofill.AutofillId, long);
+ method public void notifyViewTextChanged(android.view.autofill.AutofillId, CharSequence?);
+ method public void notifyViewsAppeared(java.util.List<android.view.ViewStructure!>);
+ method public void notifyViewsDisappeared(long[]);
+ method @RequiresApi(29) public android.view.contentcapture.ContentCaptureSession toContentCaptureSession();
+ method @RequiresApi(29) public static androidx.core.view.contentcapture.ContentCaptureSessionCompat toContentCaptureSessionCompat(android.view.contentcapture.ContentCaptureSession, android.view.View);
+ }
+
+}
+
+package androidx.core.view.inputmethod {
+
+ public final class EditorInfoCompat {
+ ctor @Deprecated public EditorInfoCompat();
+ method public static String![] getContentMimeTypes(android.view.inputmethod.EditorInfo);
+ method public static CharSequence? getInitialSelectedText(android.view.inputmethod.EditorInfo, int);
+ method public static CharSequence? getInitialTextAfterCursor(android.view.inputmethod.EditorInfo, int, int);
+ method public static CharSequence? getInitialTextBeforeCursor(android.view.inputmethod.EditorInfo, int, int);
+ method public static boolean isStylusHandwritingEnabled(android.view.inputmethod.EditorInfo);
+ method public static void setContentMimeTypes(android.view.inputmethod.EditorInfo, String![]?);
+ method public static void setInitialSurroundingSubText(android.view.inputmethod.EditorInfo, CharSequence, int);
+ method public static void setInitialSurroundingText(android.view.inputmethod.EditorInfo, CharSequence);
+ method public static void setStylusHandwritingEnabled(android.view.inputmethod.EditorInfo, boolean);
+ field public static final int IME_FLAG_FORCE_ASCII = -2147483648; // 0x80000000
+ field public static final int IME_FLAG_NO_PERSONALIZED_LEARNING = 16777216; // 0x1000000
+ }
+
+ public final class InputConnectionCompat {
+ ctor @Deprecated public InputConnectionCompat();
+ method public static boolean commitContent(android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo, androidx.core.view.inputmethod.InputContentInfoCompat, int, android.os.Bundle?);
+ method @Deprecated public static android.view.inputmethod.InputConnection createWrapper(android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo, androidx.core.view.inputmethod.InputConnectionCompat.OnCommitContentListener);
+ method public static android.view.inputmethod.InputConnection createWrapper(android.view.View, android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo);
+ field public static final int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = 1; // 0x1
+ }
+
+ public static interface InputConnectionCompat.OnCommitContentListener {
+ method public boolean onCommitContent(androidx.core.view.inputmethod.InputContentInfoCompat, int, android.os.Bundle?);
+ }
+
+ public final class InputContentInfoCompat {
+ ctor public InputContentInfoCompat(android.net.Uri, android.content.ClipDescription, android.net.Uri?);
+ method public android.net.Uri getContentUri();
+ method public android.content.ClipDescription getDescription();
+ method public android.net.Uri? getLinkUri();
+ method public void releasePermission();
+ method public void requestPermission();
+ method public Object? unwrap();
+ method public static androidx.core.view.inputmethod.InputContentInfoCompat? wrap(Object?);
+ }
+
+}
+
+package androidx.core.widget {
+
+ public abstract class AutoScrollHelper implements android.view.View.OnTouchListener {
+ ctor public AutoScrollHelper(android.view.View);
+ method public abstract boolean canTargetScrollHorizontally(int);
+ method public abstract boolean canTargetScrollVertically(int);
+ method public boolean isEnabled();
+ method public boolean isExclusive();
+ method public boolean onTouch(android.view.View!, android.view.MotionEvent!);
+ method public abstract void scrollTargetBy(int, int);
+ method public androidx.core.widget.AutoScrollHelper setActivationDelay(int);
+ method public androidx.core.widget.AutoScrollHelper setEdgeType(int);
+ method public androidx.core.widget.AutoScrollHelper! setEnabled(boolean);
+ method public androidx.core.widget.AutoScrollHelper! setExclusive(boolean);
+ method public androidx.core.widget.AutoScrollHelper setMaximumEdges(float, float);
+ method public androidx.core.widget.AutoScrollHelper setMaximumVelocity(float, float);
+ method public androidx.core.widget.AutoScrollHelper setMinimumVelocity(float, float);
+ method public androidx.core.widget.AutoScrollHelper setRampDownDuration(int);
+ method public androidx.core.widget.AutoScrollHelper setRampUpDuration(int);
+ method public androidx.core.widget.AutoScrollHelper setRelativeEdges(float, float);
+ method public androidx.core.widget.AutoScrollHelper setRelativeVelocity(float, float);
+ field public static final int EDGE_TYPE_INSIDE = 0; // 0x0
+ field public static final int EDGE_TYPE_INSIDE_EXTEND = 1; // 0x1
+ field public static final int EDGE_TYPE_OUTSIDE = 2; // 0x2
+ field public static final float NO_MAX = 3.4028235E38f;
+ field public static final float NO_MIN = 0.0f;
+ field public static final float RELATIVE_UNSPECIFIED = 0.0f;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface AutoSizeableTextView {
+ method public int getAutoSizeMaxTextSize();
+ method public int getAutoSizeMinTextSize();
+ method public int getAutoSizeStepGranularity();
+ method public int[]! getAutoSizeTextAvailableSizes();
+ method @androidx.core.widget.TextViewCompat.AutoSizeTextType public int getAutoSizeTextType();
+ method public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
+ method public void setAutoSizeTextTypeUniformWithPresetSizes(int[], int) throws java.lang.IllegalArgumentException;
+ method public void setAutoSizeTextTypeWithDefaults(@androidx.core.widget.TextViewCompat.AutoSizeTextType int);
+ field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final boolean PLATFORM_SUPPORTS_AUTOSIZE;
+ }
+
+ public final class CheckedTextViewCompat {
+ method public static android.graphics.drawable.Drawable? getCheckMarkDrawable(android.widget.CheckedTextView);
+ method public static android.content.res.ColorStateList? getCheckMarkTintList(android.widget.CheckedTextView);
+ method public static android.graphics.PorterDuff.Mode? getCheckMarkTintMode(android.widget.CheckedTextView);
+ method public static void setCheckMarkTintList(android.widget.CheckedTextView, android.content.res.ColorStateList?);
+ method public static void setCheckMarkTintMode(android.widget.CheckedTextView, android.graphics.PorterDuff.Mode?);
+ }
+
+ public final class CompoundButtonCompat {
+ method public static android.graphics.drawable.Drawable? getButtonDrawable(android.widget.CompoundButton);
+ method public static android.content.res.ColorStateList? getButtonTintList(android.widget.CompoundButton);
+ method public static android.graphics.PorterDuff.Mode? getButtonTintMode(android.widget.CompoundButton);
+ method public static void setButtonTintList(android.widget.CompoundButton, android.content.res.ColorStateList?);
+ method public static void setButtonTintMode(android.widget.CompoundButton, android.graphics.PorterDuff.Mode?);
+ }
+
+ public class ContentLoadingProgressBar extends android.widget.ProgressBar {
+ ctor public ContentLoadingProgressBar(android.content.Context);
+ ctor public ContentLoadingProgressBar(android.content.Context, android.util.AttributeSet?);
+ method public void hide();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void show();
+ }
+
+ public final class EdgeEffectCompat {
+ ctor @Deprecated public EdgeEffectCompat(android.content.Context!);
+ method public static android.widget.EdgeEffect create(android.content.Context, android.util.AttributeSet?);
+ method @Deprecated public boolean draw(android.graphics.Canvas!);
+ method @Deprecated public void finish();
+ method public static float getDistance(android.widget.EdgeEffect);
+ method @Deprecated public boolean isFinished();
+ method @Deprecated public boolean onAbsorb(int);
+ method public static void onPull(android.widget.EdgeEffect, float, float);
+ method @Deprecated public boolean onPull(float);
+ method @Deprecated public boolean onPull(float, float);
+ method public static float onPullDistance(android.widget.EdgeEffect, float, float);
+ method @Deprecated public boolean onRelease();
+ method @Deprecated public void setSize(int, int);
+ }
+
+ public class ImageViewCompat {
+ method public static android.content.res.ColorStateList? getImageTintList(android.widget.ImageView);
+ method public static android.graphics.PorterDuff.Mode? getImageTintMode(android.widget.ImageView);
+ method public static void setImageTintList(android.widget.ImageView, android.content.res.ColorStateList?);
+ method public static void setImageTintMode(android.widget.ImageView, android.graphics.PorterDuff.Mode?);
+ }
+
+ public final class ListPopupWindowCompat {
+ method public static android.view.View.OnTouchListener? createDragToOpenListener(android.widget.ListPopupWindow, android.view.View);
+ method @Deprecated public static android.view.View.OnTouchListener! createDragToOpenListener(Object!, android.view.View!);
+ }
+
+ public class ListViewAutoScrollHelper extends androidx.core.widget.AutoScrollHelper {
+ ctor public ListViewAutoScrollHelper(android.widget.ListView);
+ method public boolean canTargetScrollHorizontally(int);
+ method public boolean canTargetScrollVertically(int);
+ method public void scrollTargetBy(int, int);
+ }
+
+ @Deprecated public final class ListViewCompat {
+ method @Deprecated public static boolean canScrollList(android.widget.ListView, int);
+ method @Deprecated public static void scrollListBy(android.widget.ListView, int);
+ }
+
+ public class NestedScrollView extends android.widget.FrameLayout implements androidx.core.view.NestedScrollingChild3 androidx.core.view.NestedScrollingParent3 androidx.core.view.ScrollingView {
+ ctor public NestedScrollView(android.content.Context);
+ ctor public NestedScrollView(android.content.Context, android.util.AttributeSet?);
+ ctor public NestedScrollView(android.content.Context, android.util.AttributeSet?, int);
+ method public boolean arrowScroll(int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollExtent();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollOffset();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollRange();
+ method protected int computeScrollDeltaToGetChildRectOnScreen(android.graphics.Rect!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollExtent();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollOffset();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollRange();
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?, int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?, int);
+ method public void dispatchNestedScroll(int, int, int, int, int[]?, int, int[]);
+ method public boolean executeKeyEvent(android.view.KeyEvent);
+ method public void fling(int);
+ method public boolean fullScroll(int);
+ method public int getMaxScrollAmount();
+ method public boolean hasNestedScrollingParent(int);
+ method public boolean isFillViewport();
+ method public boolean isSmoothScrollingEnabled();
+ method public void onAttachedToWindow();
+ method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int, int[]);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View, int);
+ method public boolean pageScroll(int);
+ method public void setFillViewport(boolean);
+ method public void setOnScrollChangeListener(androidx.core.widget.NestedScrollView.OnScrollChangeListener?);
+ method public void setSmoothScrollingEnabled(boolean);
+ method public final void smoothScrollBy(int, int);
+ method public final void smoothScrollBy(int, int, int);
+ method public final void smoothScrollTo(int, int);
+ method public final void smoothScrollTo(int, int, int);
+ method public boolean startNestedScroll(int, int);
+ method public void stopNestedScroll(int);
+ }
+
+ public static interface NestedScrollView.OnScrollChangeListener {
+ method public void onScrollChange(androidx.core.widget.NestedScrollView, int, int, int, int);
+ }
+
+ public final class PopupMenuCompat {
+ method public static android.view.View.OnTouchListener? getDragToOpenListener(Object);
+ }
+
+ public final class PopupWindowCompat {
+ method public static boolean getOverlapAnchor(android.widget.PopupWindow);
+ method public static int getWindowLayoutType(android.widget.PopupWindow);
+ method public static void setOverlapAnchor(android.widget.PopupWindow, boolean);
+ method public static void setWindowLayoutType(android.widget.PopupWindow, int);
+ method public static void showAsDropDown(android.widget.PopupWindow, android.view.View, int, int, int);
+ }
+
+ @Deprecated public final class ScrollerCompat {
+ method @Deprecated public void abortAnimation();
+ method @Deprecated public boolean computeScrollOffset();
+ method @Deprecated public static androidx.core.widget.ScrollerCompat! create(android.content.Context!);
+ method @Deprecated public static androidx.core.widget.ScrollerCompat! create(android.content.Context!, android.view.animation.Interpolator!);
+ method @Deprecated public void fling(int, int, int, int, int, int, int, int);
+ method @Deprecated public void fling(int, int, int, int, int, int, int, int, int, int);
+ method @Deprecated public float getCurrVelocity();
+ method @Deprecated public int getCurrX();
+ method @Deprecated public int getCurrY();
+ method @Deprecated public int getFinalX();
+ method @Deprecated public int getFinalY();
+ method @Deprecated public boolean isFinished();
+ method @Deprecated public boolean isOverScrolled();
+ method @Deprecated public void notifyHorizontalEdgeReached(int, int, int);
+ method @Deprecated public void notifyVerticalEdgeReached(int, int, int);
+ method @Deprecated public boolean springBack(int, int, int, int, int, int);
+ method @Deprecated public void startScroll(int, int, int, int);
+ method @Deprecated public void startScroll(int, int, int, int, int);
+ }
+
+ public final class TextViewCompat {
+ method public static int getAutoSizeMaxTextSize(android.widget.TextView);
+ method public static int getAutoSizeMinTextSize(android.widget.TextView);
+ method public static int getAutoSizeStepGranularity(android.widget.TextView);
+ method public static int[] getAutoSizeTextAvailableSizes(android.widget.TextView);
+ method public static int getAutoSizeTextType(android.widget.TextView);
+ method public static android.content.res.ColorStateList? getCompoundDrawableTintList(android.widget.TextView);
+ method public static android.graphics.PorterDuff.Mode? getCompoundDrawableTintMode(android.widget.TextView);
+ method public static android.graphics.drawable.Drawable![] getCompoundDrawablesRelative(android.widget.TextView);
+ method public static int getFirstBaselineToTopHeight(android.widget.TextView);
+ method public static int getLastBaselineToBottomHeight(android.widget.TextView);
+ method public static int getMaxLines(android.widget.TextView);
+ method public static int getMinLines(android.widget.TextView);
+ method public static androidx.core.text.PrecomputedTextCompat.Params getTextMetricsParams(android.widget.TextView);
+ method public static void setAutoSizeTextTypeUniformWithConfiguration(android.widget.TextView, int, int, int, int) throws java.lang.IllegalArgumentException;
+ method public static void setAutoSizeTextTypeUniformWithPresetSizes(android.widget.TextView, int[], int) throws java.lang.IllegalArgumentException;
+ method public static void setAutoSizeTextTypeWithDefaults(android.widget.TextView, int);
+ method public static void setCompoundDrawableTintList(android.widget.TextView, android.content.res.ColorStateList?);
+ method public static void setCompoundDrawableTintMode(android.widget.TextView, android.graphics.PorterDuff.Mode?);
+ method public static void setCompoundDrawablesRelative(android.widget.TextView, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?);
+ method public static void setCompoundDrawablesRelativeWithIntrinsicBounds(android.widget.TextView, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?);
+ method public static void setCompoundDrawablesRelativeWithIntrinsicBounds(android.widget.TextView, @DrawableRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int);
+ method public static void setCustomSelectionActionModeCallback(android.widget.TextView, android.view.ActionMode.Callback);
+ method public static void setFirstBaselineToTopHeight(android.widget.TextView, @IntRange(from=0) @Px int);
+ method public static void setLastBaselineToBottomHeight(android.widget.TextView, @IntRange(from=0) @Px int);
+ method public static void setLineHeight(android.widget.TextView, @IntRange(from=0) @Px int);
+ method public static void setLineHeight(android.widget.TextView, int, @FloatRange(from=0) float);
+ method public static void setPrecomputedText(android.widget.TextView, androidx.core.text.PrecomputedTextCompat);
+ method public static void setTextAppearance(android.widget.TextView, @StyleRes int);
+ method public static void setTextMetricsParams(android.widget.TextView, androidx.core.text.PrecomputedTextCompat.Params);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.view.ActionMode.Callback? unwrapCustomSelectionActionModeCallback(android.view.ActionMode.Callback?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.view.ActionMode.Callback? wrapCustomSelectionActionModeCallback(android.widget.TextView, android.view.ActionMode.Callback?);
+ field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
+ field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
+ }
+
+ @IntDef({androidx.core.widget.TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE, androidx.core.widget.TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface TextViewCompat.AutoSizeTextType {
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TextViewOnReceiveContentListener implements androidx.core.view.OnReceiveContentListener {
+ ctor public TextViewOnReceiveContentListener();
+ method public androidx.core.view.ContentInfoCompat? onReceiveContent(android.view.View, androidx.core.view.ContentInfoCompat);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface TintableCheckedTextView {
+ method public android.content.res.ColorStateList? getSupportCheckMarkTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportCheckMarkTintMode();
+ method public void setSupportCheckMarkTintList(android.content.res.ColorStateList?);
+ method public void setSupportCheckMarkTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public interface TintableCompoundButton {
+ method public android.content.res.ColorStateList? getSupportButtonTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportButtonTintMode();
+ method public void setSupportButtonTintList(android.content.res.ColorStateList?);
+ method public void setSupportButtonTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public interface TintableCompoundDrawablesView {
+ method public android.content.res.ColorStateList? getSupportCompoundDrawablesTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportCompoundDrawablesTintMode();
+ method public void setSupportCompoundDrawablesTintList(android.content.res.ColorStateList?);
+ method public void setSupportCompoundDrawablesTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface TintableImageSourceView {
+ method public android.content.res.ColorStateList? getSupportImageTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportImageTintMode();
+ method public void setSupportImageTintList(android.content.res.ColorStateList?);
+ method public void setSupportImageTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+}
+
diff --git a/core/core/src/androidTest/java/androidx/core/view/inputmethod/ImeViewCompatMultiWindowTest.java b/core/core/src/androidTest/java/androidx/core/view/inputmethod/ImeViewCompatMultiWindowTest.java
index c5fa2ef..971b69c 100644
--- a/core/core/src/androidTest/java/androidx/core/view/inputmethod/ImeViewCompatMultiWindowTest.java
+++ b/core/core/src/androidTest/java/androidx/core/view/inputmethod/ImeViewCompatMultiWindowTest.java
@@ -21,15 +21,15 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.RemoteException;
-import android.support.v4.BaseInstrumentationTestCase;
import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.core.view.WindowInsetsCompat;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
@@ -41,13 +41,20 @@
import androidx.testutils.PollingCheck;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.atomic.AtomicReference;
+
@RunWith(AndroidJUnit4.class)
@LargeTest
@SdkSuppress(minSdkVersion = 30)
-public class ImeViewCompatMultiWindowTest extends BaseInstrumentationTestCase<ImeBaseSplitTestActivity> {
+public class ImeViewCompatMultiWindowTest {
+
+ @Rule
+ public final ActivityScenarioRule<ImeBaseSplitTestActivity> mActivityScenarioRule =
+ new ActivityScenarioRule<>(ImeBaseSplitTestActivity.class);
private static final long ACTIVITY_LAUNCH_TIMEOUT_MS = 10000;
private static final long VISIBILITY_TIMEOUT_MS = 2000;
@@ -56,19 +63,12 @@
private static final String TEST_APP = "androidx.core.test";
- private Activity mActivity;
-
private UiDevice mDevice;
- public ImeViewCompatMultiWindowTest() {
- super(ImeBaseSplitTestActivity.class);
- }
-
@Before
public void setup() throws RemoteException {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mDevice.wakeUp();
- mActivity = mActivityTestRule.getActivity();
}
/**
@@ -88,11 +88,14 @@
.performGlobalAction(GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN);
}
- // Launch ime test activity in secondary split.
- Intent intent = new Intent(mActivity, ImeSecondarySplitViewCompatTestActivity.class)
- .addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- mActivity.startActivity(intent);
+ ActivityScenario<ImeBaseSplitTestActivity> scenario = mActivityScenarioRule.getScenario();
+ scenario.onActivity(activity -> {
+ // Launch ime test activity in secondary split.
+ Intent intent = new Intent(activity, ImeSecondarySplitViewCompatTestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ activity.startActivity(intent);
+ });
assertTrue("Test app is not visible after launching activity",
mDevice.wait(Until.hasObject(By.pkg(TEST_APP)), ACTIVITY_LAUNCH_TIMEOUT_MS));
@@ -100,7 +103,12 @@
UiObject2 editText = waitForFindObject("edit_text_id");
editText.click(CLICK_DURATION_MS);
- WindowManager wm = mActivity.getSystemService(WindowManager.class);
+ AtomicReference<WindowManager> wmRef = new AtomicReference<>();
+ scenario.onActivity(activity -> {
+ wmRef.set(activity.getSystemService(WindowManager.class));
+ });
+
+ WindowManager wm = wmRef.get();
PollingCheck.waitFor(VISIBILITY_TIMEOUT_MS, () -> {
WindowInsets insets = wm.getCurrentWindowMetrics().getWindowInsets();
return insets.isVisible(WindowInsetsCompat.Type.ime());
diff --git a/core/core/src/androidTest/java/androidx/core/view/inputmethod/InputConnectionCompatTest.java b/core/core/src/androidTest/java/androidx/core/view/inputmethod/InputConnectionCompatTest.java
index 30a5f4c..dc65d1f3 100644
--- a/core/core/src/androidTest/java/androidx/core/view/inputmethod/InputConnectionCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/view/inputmethod/InputConnectionCompatTest.java
@@ -29,16 +29,17 @@
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.BaseInstrumentationTestCase;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import androidx.core.app.TestActivity;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
@@ -48,10 +49,11 @@
@RunWith(AndroidJUnit4.class)
@MediumTest
-public class InputConnectionCompatTest extends BaseInstrumentationTestCase<TestActivity> {
- public InputConnectionCompatTest() {
- super(TestActivity.class);
- }
+public class InputConnectionCompatTest {
+
+ @Rule
+ public final ActivityScenarioRule<TestActivity> mActivityScenarioRule =
+ new ActivityScenarioRule<>(TestActivity.class);
private static final String COMMIT_CONTENT_ACTION =
"androidx.core.view.inputmethod.InputConnectionCompat.COMMIT_CONTENT";
@@ -204,7 +206,6 @@
}
return new ArgumentMatcher<Bundle>() {
@Override
- @SuppressWarnings("deprecation")
public boolean matches(Bundle data) {
final Uri contentUri = data.getParcelable(contentUriKey);
final ClipDescription description = data.getParcelable(descriptionKey);
@@ -215,6 +216,7 @@
&& TEST_CLIP_DESCRIPTION.equals(description)
&& TEST_LINK_URI.equals(linkUri)
&& flags == TEST_FLAGS
+ && opts != null
&& opts.equals(TEST_BUNDLE);
}
};
diff --git a/credentials/credentials-e2ee/OWNERS b/credentials/credentials-e2ee/OWNERS
new file mode 100644
index 0000000..c1799c1
--- /dev/null
+++ b/credentials/credentials-e2ee/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1487500
+anhph@google.com
+ludovicb@google.com
diff --git a/credentials/credentials-e2ee/api/current.txt b/credentials/credentials-e2ee/api/current.txt
new file mode 100644
index 0000000..70f4bab
--- /dev/null
+++ b/credentials/credentials-e2ee/api/current.txt
@@ -0,0 +1,22 @@
+// Signature format: 4.0
+package androidx.credentials.e2ee {
+
+ public final class IdentityKey {
+ method @WorkerThread public static androidx.credentials.e2ee.IdentityKey createFromPrf(byte[] prf, byte[]? salt, int keyType);
+ method public byte[] getPrivate();
+ method public byte[] getPublic();
+ method public int getType();
+ property public final byte[] private;
+ property public final byte[] public;
+ property public final int type;
+ field public static final androidx.credentials.e2ee.IdentityKey.Companion Companion;
+ field public static final int IDENTITYKEY_TYPE_ED25519 = 6; // 0x6
+ field public static final int IDENTITYKEY_TYPE_RESERVED = 0; // 0x0
+ }
+
+ public static final class IdentityKey.Companion {
+ method @WorkerThread public androidx.credentials.e2ee.IdentityKey createFromPrf(byte[] prf, byte[]? salt, int keyType);
+ }
+
+}
+
diff --git a/credentials/credentials-e2ee/api/res-current.txt b/credentials/credentials-e2ee/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/credentials/credentials-e2ee/api/res-current.txt
diff --git a/credentials/credentials-e2ee/api/restricted_current.txt b/credentials/credentials-e2ee/api/restricted_current.txt
new file mode 100644
index 0000000..70f4bab
--- /dev/null
+++ b/credentials/credentials-e2ee/api/restricted_current.txt
@@ -0,0 +1,22 @@
+// Signature format: 4.0
+package androidx.credentials.e2ee {
+
+ public final class IdentityKey {
+ method @WorkerThread public static androidx.credentials.e2ee.IdentityKey createFromPrf(byte[] prf, byte[]? salt, int keyType);
+ method public byte[] getPrivate();
+ method public byte[] getPublic();
+ method public int getType();
+ property public final byte[] private;
+ property public final byte[] public;
+ property public final int type;
+ field public static final androidx.credentials.e2ee.IdentityKey.Companion Companion;
+ field public static final int IDENTITYKEY_TYPE_ED25519 = 6; // 0x6
+ field public static final int IDENTITYKEY_TYPE_RESERVED = 0; // 0x0
+ }
+
+ public static final class IdentityKey.Companion {
+ method @WorkerThread public androidx.credentials.e2ee.IdentityKey createFromPrf(byte[] prf, byte[]? salt, int keyType);
+ }
+
+}
+
diff --git a/credentials/credentials-e2ee/build.gradle b/credentials/credentials-e2ee/build.gradle
new file mode 100644
index 0000000..92e6911
--- /dev/null
+++ b/credentials/credentials-e2ee/build.gradle
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 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.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ api(libs.kotlinStdlib)
+ api("androidx.annotation:annotation:1.5.0")
+ implementation("com.google.crypto.tink:tink-android:1.8.0")
+ androidTestImplementation(libs.junit)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.truth)
+}
+
+android {
+ namespace "androidx.credentials.e2ee"
+}
+
+androidx {
+ name = "androidx.credentials:credentials-e2ee"
+ type = LibraryType.PUBLISHED_LIBRARY
+ inceptionYear = "2023"
+ description = "Create Identity Keys, signing keys for E2EE in AOSP."
+}
diff --git a/credentials/credentials-e2ee/src/androidTest/java/androidx/credentials/e2ee/IdentityKeyJavaTest.java b/credentials/credentials-e2ee/src/androidTest/java/androidx/credentials/e2ee/IdentityKeyJavaTest.java
new file mode 100644
index 0000000..899afd8
--- /dev/null
+++ b/credentials/credentials-e2ee/src/androidTest/java/androidx/credentials/e2ee/IdentityKeyJavaTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.credentials.e2ee;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Base64;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.io.BaseEncoding;
+
+import org.junit.Test;
+
+import java.util.Random;
+
+/* Same tests as in IdentityKeyTest.kt. Used to assert Java interop. */
+public class IdentityKeyJavaTest {
+ Random mRandom = new Random();
+
+ @NonNull
+ private byte[] randBytes(int numBytes) {
+ byte[] bytes = new byte[numBytes];
+ mRandom.nextBytes(bytes);
+ return bytes;
+ }
+
+ private String hexEncode(byte[] bytes) {
+ return BaseEncoding.base16().lowerCase().encode(bytes);
+ }
+
+ @Test
+ public void identityKeyWithFixedInputs_mustProduceExpectedOutput() {
+ byte[] prf = new byte[32];
+ byte[] salt = new byte[32];
+ // with an all-zero PRF and salt, this is the expected key
+ String expectedPrivKeyHex =
+ "df7204546f1bee78b85324a7898ca119b387e01386d1aef037781d4a8a036aee";
+ String expectedPubKeyHex =
+ "ba33d523fd7bf0d06ce9298c3440be1bea3748c6270ae3e07ae8ea19abb8ed23";
+
+ IdentityKey identityKey = IdentityKey.createFromPrf(prf, salt,
+ IdentityKey.IDENTITYKEY_TYPE_ED25519);
+
+ assertThat(identityKey.getPrivate()).isNotNull();
+ assertThat(identityKey.getPublic()).isNotNull();
+ assertThat(hexEncode(identityKey.getPrivate())).isEqualTo(expectedPrivKeyHex);
+ assertThat(hexEncode(identityKey.getPublic())).isEqualTo(expectedPubKeyHex);
+ }
+
+
+ @Test
+ public void identityKeyWithoutSalt_mustBeIdenticalToEmptySalt() {
+ for (int i = 0; i < 10; i++) {
+ byte[] prf = randBytes(32);
+ IdentityKey identityKey = IdentityKey.createFromPrf(prf, /* salt= */null,
+ IdentityKey.IDENTITYKEY_TYPE_ED25519);
+ IdentityKey identityKey2 = IdentityKey.createFromPrf(prf, new byte[32],
+ IdentityKey.IDENTITYKEY_TYPE_ED25519);
+
+ assertThat(identityKey).isEqualTo(identityKey2);
+ }
+ }
+
+ @Test
+ public void identityKey_canBeGeneratedUsingWebAuthnPrfOutput() {
+ /*
+ Ideally, we would test the full webauthn interaction (set the PRF extension to true, call
+ navigator.credentials.create, read the PRF output). The problem is that this would tie
+ androidX to the implementation of a password manager.
+ Instead, we manually copy the prfOutput value from
+ //com/google/android/gms/fido/authenticator/embedded/AuthenticationRequestHandlerTest.java,
+ like a test vector. Even if the two values get out of sync, what we care about is the Base64
+ format, as the PRF output is fully random-looking by definition.
+ */
+ byte[] prfOutput = Base64.decode("f2HM0TolWHyYJ/+LQDW8N2vRdE0+risMV/tIKXQdj7tVKdGChdJuMyz1"
+ + "/iX7x4y3GvHLlmja1A8qCsKsekW22Q==", Base64.DEFAULT);
+ byte[] salt = new byte[32];
+ String expectedPrivKeyHex =
+ "bccdec572ae1be6b3c3f3473781965a1935d2614c928f5430b79188950658ad6";
+ String expectedPubKeyHex =
+ "23fa91da0af9edefae9c53c584f933f3d02f934aebddb70511adac91f255afda";
+
+ IdentityKey identityKey = IdentityKey.createFromPrf(prfOutput, salt,
+ IdentityKey.IDENTITYKEY_TYPE_ED25519);
+
+ assertThat(prfOutput).isNotNull();
+ assertThat(identityKey.getPrivate()).isNotNull();
+ assertThat(identityKey.getPublic()).isNotNull();
+ assertThat(hexEncode(identityKey.getPrivate())).isEqualTo(expectedPrivKeyHex);
+ assertThat(hexEncode(identityKey.getPublic())).isEqualTo(expectedPubKeyHex);
+ }
+}
diff --git a/credentials/credentials-e2ee/src/androidTest/java/androidx/credentials/e2ee/IdentityKeyTest.kt b/credentials/credentials-e2ee/src/androidTest/java/androidx/credentials/e2ee/IdentityKeyTest.kt
new file mode 100644
index 0000000..6506872
--- /dev/null
+++ b/credentials/credentials-e2ee/src/androidTest/java/androidx/credentials/e2ee/IdentityKeyTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.credentials.e2ee
+
+import android.util.Base64
+import com.google.common.io.BaseEncoding
+import com.google.common.truth.Truth.assertThat
+import java.util.Random
+import org.junit.Test
+
+class IdentityKeyTest {
+
+ var sRandom: Random = Random()
+ private fun randBytes(numBytes: Int): ByteArray {
+ val bytes = ByteArray(numBytes)
+ sRandom.nextBytes(bytes)
+ return bytes
+ }
+
+ private fun hexEncode(bytes: ByteArray): String {
+ return BaseEncoding.base16().lowerCase().encode(bytes)
+ }
+
+ @Test
+ fun identityKeyWithFixedInputs_mustProduceExpectedOutput() {
+ val prf = ByteArray(32)
+ val salt = ByteArray(32)
+ // with an all-zero PRF and salt, this is the expected key
+ val expectedPrivKeyHex = "df7204546f1bee78b85324a7898ca119b387e01386d1aef037781d4a8a036aee"
+ val expectedPubKeyHex = "ba33d523fd7bf0d06ce9298c3440be1bea3748c6270ae3e07ae8ea19abb8ed23"
+
+ val identityKey = IdentityKey.createFromPrf(prf, salt, IdentityKey.IDENTITYKEY_TYPE_ED25519)
+
+ assertThat(identityKey.private).isNotNull()
+ assertThat(identityKey.public).isNotNull()
+ assertThat(hexEncode(identityKey.private)).isEqualTo(expectedPrivKeyHex)
+ assertThat(hexEncode(identityKey.public)).isEqualTo(expectedPubKeyHex)
+ }
+
+ @Test
+ fun identityKeyWithoutSalt_mustBeIdenticalToEmptySalt() {
+ for (i in 1..10) {
+ val prf = randBytes(32)
+ val identityKey =
+ IdentityKey.createFromPrf(prf, /* salt= */null,
+ IdentityKey.IDENTITYKEY_TYPE_ED25519)
+ val identityKey2 =
+ IdentityKey.createFromPrf(prf, ByteArray(32),
+ IdentityKey.IDENTITYKEY_TYPE_ED25519)
+
+ assertThat(identityKey).isEqualTo(identityKey2)
+ }
+ }
+
+ @Test
+ fun identityKey_canBeGeneratedUsingWebAuthnPrfOutput() {
+ /*
+ Ideally, we would test the full webauthn interaction (set the PRF extension to true, call
+ navigator.credentials.create, read the PRF output). The problem is that this would tie
+ androidX to the implementation of a password manager.
+ Instead, we manually copy the prfOutput value from //com/google/android/gms/fido/authenticator/embedded/AuthenticationRequestHandlerTest.java,
+ like a test vector. Even if the two values get out of sync, what we care about is the Base64
+ format, as the PRF output is fully random-looking by definition.
+ */
+ val prfOutput = Base64.decode("f2HM0TolWHyYJ/+LQDW8N2vRdE0+risMV/tIKXQdj7tVKdGChdJuMyz1" +
+ "/iX7x4y3GvHLlmja1A8qCsKsekW22Q==",
+ Base64.DEFAULT
+ )
+ val salt = ByteArray(32)
+ val expectedPrivKeyHex = "bccdec572ae1be6b3c3f3473781965a1935d2614c928f5430b79188950658ad6"
+ val expectedPubKeyHex = "23fa91da0af9edefae9c53c584f933f3d02f934aebddb70511adac91f255afda"
+
+ val identityKey =
+ IdentityKey.createFromPrf(prfOutput, salt, IdentityKey.IDENTITYKEY_TYPE_ED25519)
+
+ assertThat(prfOutput).isNotNull()
+ assertThat(identityKey.private).isNotNull()
+ assertThat(identityKey.public).isNotNull()
+ assertThat(hexEncode(identityKey.private)).isEqualTo(expectedPrivKeyHex)
+ assertThat(hexEncode(identityKey.public)).isEqualTo(expectedPubKeyHex)
+ }
+}
diff --git a/credentials/credentials-e2ee/src/main/java/androidx/credentials/e2ee/IdentityKey.kt b/credentials/credentials-e2ee/src/main/java/androidx/credentials/e2ee/IdentityKey.kt
new file mode 100644
index 0000000..863f5a3
--- /dev/null
+++ b/credentials/credentials-e2ee/src/main/java/androidx/credentials/e2ee/IdentityKey.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.credentials.e2ee
+
+import androidx.annotation.IntDef
+import androidx.annotation.RestrictTo
+import androidx.annotation.WorkerThread
+import com.google.crypto.tink.subtle.Ed25519Sign
+import com.google.crypto.tink.subtle.Hkdf
+
+/**
+ * A public-private key pair usable for signing, representing an end user
+ * identity in an end-to-end encrypted messaging system.
+ *
+ * @property public The public key, stored as a byte array.
+ * @property private The private key, stored as a byte array.
+ * @property type The type of signing key, e.g. Ed25519.
+ */
+class IdentityKey private constructor(
+ val public: ByteArray,
+ val private: ByteArray,
+ @IdentityKeyType val type: Int
+) {
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(IDENTITYKEY_TYPE_RESERVED, IDENTITYKEY_TYPE_ED25519)
+ annotation class IdentityKeyType
+
+ companion object {
+ /**
+ * The default signing key type, which should not be used.
+ * This is required to match https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ */
+ const val IDENTITYKEY_TYPE_RESERVED = 0
+
+ /**
+ * A signing key on Ed25519.
+ * The value matches https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ */
+ const val IDENTITYKEY_TYPE_ED25519 = 6
+
+ /**
+ * Creates a [IdentityKey], a public/private key pair usable for signing. It is intended for
+ * use with the WebAuthn PRF extension (https://w3c.github.io/webauthn/#prf-extension). The
+ * generated IdentityKey is deterministic given prf and salt, thus the prf value must be kept
+ * secret.
+ * Currently, only Ed25519 is supported as a key type.
+ *
+ * @param prf The PRF output of WebAuthn used in the key derivation.
+ * @param salt An optional salt used in the key derivation.
+ * @param keyType The type of IdentityKey to generate, e.g. Ed25519.
+ * @return a [IdentityKey], a public/private key pair usable for signing.
+ * @throws IllegalArgumentException if the key type is not supported.
+ */
+ @JvmStatic
+ @WorkerThread
+ fun createFromPrf(
+ prf: ByteArray,
+ salt: ByteArray?,
+ @IdentityKeyType keyType: Int
+ ): IdentityKey {
+ if (keyType != IDENTITYKEY_TYPE_ED25519) {
+ throw IllegalArgumentException("Only Ed25519 is supported at this stage.")
+ }
+
+ val hkdf: ByteArray = Hkdf.computeHkdf(
+ "HmacSHA256", prf,
+ // According to RFC 5869, Section 2.2 the salt is optional. If no salt is
+ // provided, the HKDF uses a salt that is an array of zeros of the same length
+ // as the hash digest.
+ /* salt= */ salt ?: ByteArray(32),
+ /* info= */ ByteArray(0),
+ /* size= */ 32
+ )
+ val keyPair: Ed25519Sign.KeyPair = Ed25519Sign.KeyPair.newKeyPairFromSeed(hkdf)
+ return IdentityKey(keyPair.publicKey, keyPair.privateKey, IDENTITYKEY_TYPE_ED25519)
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null) return false
+ if (this === other) return true
+ if (other !is IdentityKey) return false
+ if (type != other.type || !private.contentEquals(other.private) || !public.contentEquals(
+ other.public
+ )
+ ) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = public.contentHashCode()
+ result = 31 * result + private.contentHashCode()
+ result = 31 * result + type
+ return result
+ }
+}
diff --git a/credentials/credentials-e2ee/src/main/java/androidx/credentials/e2ee/androidx-credentials-credentials-e2ee-documentation.md b/credentials/credentials-e2ee/src/main/java/androidx/credentials/e2ee/androidx-credentials-credentials-e2ee-documentation.md
new file mode 100644
index 0000000..deb5dca
--- /dev/null
+++ b/credentials/credentials-e2ee/src/main/java/androidx/credentials/e2ee/androidx-credentials-credentials-e2ee-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+CREDENTIALS CREDENTIALS E2EE
+
+# Package androidx.credentials.e2ee
+
+This package allows developers to generate credentials usable for end-to-end-encrypted messaging.
diff --git a/credentials/credentials/api/current.txt b/credentials/credentials/api/current.txt
index c4331d6..c6570db 100644
--- a/credentials/credentials/api/current.txt
+++ b/credentials/credentials/api/current.txt
@@ -6,6 +6,8 @@
}
public abstract class CreateCredentialRequest {
+ method @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+ method @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
method public final android.os.Bundle getCandidateQueryData();
method public final android.os.Bundle getCredentialData();
method public final androidx.credentials.CreateCredentialRequest.DisplayInfo getDisplayInfo();
@@ -22,6 +24,12 @@
property public final String? origin;
property public final boolean preferImmediatelyAvailableCredentials;
property public final String type;
+ field public static final androidx.credentials.CreateCredentialRequest.Companion Companion;
+ }
+
+ public static final class CreateCredentialRequest.Companion {
+ method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+ method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
}
public static final class CreateCredentialRequest.DisplayInfo {
@@ -30,8 +38,14 @@
ctor public CreateCredentialRequest.DisplayInfo(CharSequence userId, CharSequence? userDisplayName, String? preferDefaultProvider);
method public CharSequence? getUserDisplayName();
method public CharSequence getUserId();
+ method @RequiresApi(23) public static androidx.credentials.CreateCredentialRequest.DisplayInfo parseFromCredentialDataBundle(android.os.Bundle from);
property public final CharSequence? userDisplayName;
property public final CharSequence userId;
+ field public static final androidx.credentials.CreateCredentialRequest.DisplayInfo.Companion Companion;
+ }
+
+ public static final class CreateCredentialRequest.DisplayInfo.Companion {
+ method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest.DisplayInfo parseFromCredentialDataBundle(android.os.Bundle from);
}
public abstract class CreateCredentialResponse {
@@ -124,6 +138,7 @@
method public final android.os.Bundle getCandidateQueryData();
method public final android.os.Bundle getRequestData();
method public final String getType();
+ method public final int getTypePriorityHint();
method public final boolean isAutoSelectAllowed();
method public final boolean isSystemProviderRequired();
property public final java.util.Set<android.content.ComponentName> allowedProviders;
@@ -132,6 +147,7 @@
property public final boolean isSystemProviderRequired;
property public final android.os.Bundle requestData;
property public final String type;
+ property public final int typePriorityHint;
}
public interface CredentialProvider {
@@ -186,6 +202,7 @@
ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired);
ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, optional boolean isAutoSelectAllowed);
ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, optional boolean isAutoSelectAllowed, optional java.util.Set<android.content.ComponentName> allowedProviders);
+ ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, optional boolean isAutoSelectAllowed, optional java.util.Set<android.content.ComponentName> allowedProviders, optional int typePriorityHint);
}
public final class GetPasswordOption extends androidx.credentials.CredentialOption {
@@ -248,6 +265,19 @@
method @VisibleForTesting public androidx.credentials.PrepareGetCredentialResponse.TestBuilder setHasRemoteResultsDelegate(kotlin.jvm.functions.Function0<java.lang.Boolean> handler);
}
+ @IntDef({androidx.credentials.PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR, androidx.credentials.PriorityHints.PRIORITY_OIDC_OR_SIMILAR, androidx.credentials.PriorityHints.PRIORITY_PASSWORD_OR_SIMILAR, androidx.credentials.PriorityHints.PRIORITY_DEFAULT}) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.TYPE}) public @interface PriorityHints {
+ field public static final androidx.credentials.PriorityHints.Companion Companion;
+ field public static final int PRIORITY_DEFAULT = 2000; // 0x7d0
+ field public static final int PRIORITY_OIDC_OR_SIMILAR = 500; // 0x1f4
+ field public static final int PRIORITY_PASSWORD_OR_SIMILAR = 1000; // 0x3e8
+ }
+
+ public static final class PriorityHints.Companion {
+ field public static final int PRIORITY_DEFAULT = 2000; // 0x7d0
+ field public static final int PRIORITY_OIDC_OR_SIMILAR = 500; // 0x1f4
+ field public static final int PRIORITY_PASSWORD_OR_SIMILAR = 1000; // 0x3e8
+ }
+
public final class PublicKeyCredential extends androidx.credentials.Credential {
ctor public PublicKeyCredential(String authenticationResponseJson);
method public String getAuthenticationResponseJson();
@@ -790,9 +820,13 @@
method public CharSequence getTitle();
method public String getType();
method public CharSequence? getTypeDisplayName();
+ method public boolean hasDefaultIcon();
method public boolean isAutoSelectAllowed();
+ method public boolean isAutoSelectAllowedFromOption();
+ property public final boolean hasDefaultIcon;
property public final android.graphics.drawable.Icon icon;
property public final boolean isAutoSelectAllowed;
+ property public final boolean isAutoSelectAllowedFromOption;
property public final java.time.Instant? lastUsedTime;
property public final android.app.PendingIntent pendingIntent;
property public final CharSequence? subtitle;
@@ -836,10 +870,14 @@
method public android.app.PendingIntent getPendingIntent();
method public CharSequence getTypeDisplayName();
method public CharSequence getUsername();
+ method public boolean hasDefaultIcon();
method public boolean isAutoSelectAllowed();
+ method public boolean isAutoSelectAllowedFromOption();
property public final CharSequence? displayName;
+ property public final boolean hasDefaultIcon;
property public final android.graphics.drawable.Icon icon;
property public final boolean isAutoSelectAllowed;
+ property public final boolean isAutoSelectAllowedFromOption;
property public final java.time.Instant? lastUsedTime;
property public final android.app.PendingIntent pendingIntent;
property public final CharSequence typeDisplayName;
@@ -918,10 +956,14 @@
method public android.app.PendingIntent getPendingIntent();
method public CharSequence getTypeDisplayName();
method public CharSequence getUsername();
+ method public boolean hasDefaultIcon();
method public boolean isAutoSelectAllowed();
+ method public boolean isAutoSelectAllowedFromOption();
property public final CharSequence? displayName;
+ property public final boolean hasDefaultIcon;
property public final android.graphics.drawable.Icon icon;
property public final boolean isAutoSelectAllowed;
+ property public final boolean isAutoSelectAllowedFromOption;
property public final java.time.Instant? lastUsedTime;
property public final android.app.PendingIntent pendingIntent;
property public final CharSequence typeDisplayName;
diff --git a/credentials/credentials/api/restricted_current.txt b/credentials/credentials/api/restricted_current.txt
index c4331d6..c6570db 100644
--- a/credentials/credentials/api/restricted_current.txt
+++ b/credentials/credentials/api/restricted_current.txt
@@ -6,6 +6,8 @@
}
public abstract class CreateCredentialRequest {
+ method @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+ method @RequiresApi(23) public static final androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
method public final android.os.Bundle getCandidateQueryData();
method public final android.os.Bundle getCredentialData();
method public final androidx.credentials.CreateCredentialRequest.DisplayInfo getDisplayInfo();
@@ -22,6 +24,12 @@
property public final String? origin;
property public final boolean preferImmediatelyAvailableCredentials;
property public final String type;
+ field public static final androidx.credentials.CreateCredentialRequest.Companion Companion;
+ }
+
+ public static final class CreateCredentialRequest.Companion {
+ method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+ method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest createFrom(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider, optional String? origin);
}
public static final class CreateCredentialRequest.DisplayInfo {
@@ -30,8 +38,14 @@
ctor public CreateCredentialRequest.DisplayInfo(CharSequence userId, CharSequence? userDisplayName, String? preferDefaultProvider);
method public CharSequence? getUserDisplayName();
method public CharSequence getUserId();
+ method @RequiresApi(23) public static androidx.credentials.CreateCredentialRequest.DisplayInfo parseFromCredentialDataBundle(android.os.Bundle from);
property public final CharSequence? userDisplayName;
property public final CharSequence userId;
+ field public static final androidx.credentials.CreateCredentialRequest.DisplayInfo.Companion Companion;
+ }
+
+ public static final class CreateCredentialRequest.DisplayInfo.Companion {
+ method @RequiresApi(23) public androidx.credentials.CreateCredentialRequest.DisplayInfo parseFromCredentialDataBundle(android.os.Bundle from);
}
public abstract class CreateCredentialResponse {
@@ -124,6 +138,7 @@
method public final android.os.Bundle getCandidateQueryData();
method public final android.os.Bundle getRequestData();
method public final String getType();
+ method public final int getTypePriorityHint();
method public final boolean isAutoSelectAllowed();
method public final boolean isSystemProviderRequired();
property public final java.util.Set<android.content.ComponentName> allowedProviders;
@@ -132,6 +147,7 @@
property public final boolean isSystemProviderRequired;
property public final android.os.Bundle requestData;
property public final String type;
+ property public final int typePriorityHint;
}
public interface CredentialProvider {
@@ -186,6 +202,7 @@
ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired);
ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, optional boolean isAutoSelectAllowed);
ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, optional boolean isAutoSelectAllowed, optional java.util.Set<android.content.ComponentName> allowedProviders);
+ ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, optional boolean isAutoSelectAllowed, optional java.util.Set<android.content.ComponentName> allowedProviders, optional int typePriorityHint);
}
public final class GetPasswordOption extends androidx.credentials.CredentialOption {
@@ -248,6 +265,19 @@
method @VisibleForTesting public androidx.credentials.PrepareGetCredentialResponse.TestBuilder setHasRemoteResultsDelegate(kotlin.jvm.functions.Function0<java.lang.Boolean> handler);
}
+ @IntDef({androidx.credentials.PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR, androidx.credentials.PriorityHints.PRIORITY_OIDC_OR_SIMILAR, androidx.credentials.PriorityHints.PRIORITY_PASSWORD_OR_SIMILAR, androidx.credentials.PriorityHints.PRIORITY_DEFAULT}) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.TYPE}) public @interface PriorityHints {
+ field public static final androidx.credentials.PriorityHints.Companion Companion;
+ field public static final int PRIORITY_DEFAULT = 2000; // 0x7d0
+ field public static final int PRIORITY_OIDC_OR_SIMILAR = 500; // 0x1f4
+ field public static final int PRIORITY_PASSWORD_OR_SIMILAR = 1000; // 0x3e8
+ }
+
+ public static final class PriorityHints.Companion {
+ field public static final int PRIORITY_DEFAULT = 2000; // 0x7d0
+ field public static final int PRIORITY_OIDC_OR_SIMILAR = 500; // 0x1f4
+ field public static final int PRIORITY_PASSWORD_OR_SIMILAR = 1000; // 0x3e8
+ }
+
public final class PublicKeyCredential extends androidx.credentials.Credential {
ctor public PublicKeyCredential(String authenticationResponseJson);
method public String getAuthenticationResponseJson();
@@ -790,9 +820,13 @@
method public CharSequence getTitle();
method public String getType();
method public CharSequence? getTypeDisplayName();
+ method public boolean hasDefaultIcon();
method public boolean isAutoSelectAllowed();
+ method public boolean isAutoSelectAllowedFromOption();
+ property public final boolean hasDefaultIcon;
property public final android.graphics.drawable.Icon icon;
property public final boolean isAutoSelectAllowed;
+ property public final boolean isAutoSelectAllowedFromOption;
property public final java.time.Instant? lastUsedTime;
property public final android.app.PendingIntent pendingIntent;
property public final CharSequence? subtitle;
@@ -836,10 +870,14 @@
method public android.app.PendingIntent getPendingIntent();
method public CharSequence getTypeDisplayName();
method public CharSequence getUsername();
+ method public boolean hasDefaultIcon();
method public boolean isAutoSelectAllowed();
+ method public boolean isAutoSelectAllowedFromOption();
property public final CharSequence? displayName;
+ property public final boolean hasDefaultIcon;
property public final android.graphics.drawable.Icon icon;
property public final boolean isAutoSelectAllowed;
+ property public final boolean isAutoSelectAllowedFromOption;
property public final java.time.Instant? lastUsedTime;
property public final android.app.PendingIntent pendingIntent;
property public final CharSequence typeDisplayName;
@@ -918,10 +956,14 @@
method public android.app.PendingIntent getPendingIntent();
method public CharSequence getTypeDisplayName();
method public CharSequence getUsername();
+ method public boolean hasDefaultIcon();
method public boolean isAutoSelectAllowed();
+ method public boolean isAutoSelectAllowedFromOption();
property public final CharSequence? displayName;
+ property public final boolean hasDefaultIcon;
property public final android.graphics.drawable.Icon icon;
property public final boolean isAutoSelectAllowed;
+ property public final boolean isAutoSelectAllowedFromOption;
property public final java.time.Instant? lastUsedTime;
property public final android.app.PendingIntent pendingIntent;
property public final CharSequence typeDisplayName;
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCredentialRequestDisplayInfoTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCredentialRequestDisplayInfoTest.kt
index 252bea5..05e7455 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCredentialRequestDisplayInfoTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCredentialRequestDisplayInfoTest.kt
@@ -119,7 +119,7 @@
)
)
- assertThat(displayInfo!!.userId).isEqualTo(expectedUserId)
+ assertThat(displayInfo.userId).isEqualTo(expectedUserId)
assertThat(displayInfo.userDisplayName).isNull()
assertThat(displayInfo.credentialTypeIcon?.resId).isEqualTo(
R.drawable.ic_password
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt
index 413adf6..c380bda1 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt
@@ -127,7 +127,7 @@
val convertedRequest = createFrom(
request.type, request.credentialData, request.candidateQueryData,
request.isSystemProviderRequired, request.origin
- )!!
+ )
assertThat(convertedRequest).isInstanceOf(CreateCustomCredentialRequest::class.java)
val actualRequest = convertedRequest as CreateCustomCredentialRequest
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
index d54d4f6..ef61508 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
@@ -20,7 +20,6 @@
import static com.google.common.truth.Truth.assertThat;
-import android.app.Activity;
import android.content.Context;
import android.os.Looper;
@@ -105,34 +104,35 @@
}
@Test
- public void testGetCredentialAsyc_requestBasedApi_successCallbackThrows()
- throws InterruptedException {
+ public void testGetCredentialAsyc_successCallbackThrows() throws InterruptedException {
if (Looper.myLooper() == null) {
Looper.prepare();
}
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<GetCredentialException> loadedResult = new AtomicReference<>();
+ ActivityScenario<TestActivity> activityScenario =
+ ActivityScenario.launch(TestActivity.class);
+ activityScenario.onActivity(activity -> {
+ mCredentialManager.getCredentialAsync(
+ activity,
+ new GetCredentialRequest.Builder()
+ .addCredentialOption(new GetPasswordOption())
+ .build(),
+ null,
+ Runnable::run,
+ new CredentialManagerCallback<GetCredentialResponse,
+ GetCredentialException>() {
+ @Override
+ public void onError(@NonNull GetCredentialException e) {
+ loadedResult.set(e);
+ latch.countDown();
+ }
- mCredentialManager.getCredentialAsync(
- new Activity(),
- new GetCredentialRequest.Builder()
- .addCredentialOption(new GetPasswordOption())
- .build(),
- null,
- Runnable::run,
- new CredentialManagerCallback<GetCredentialResponse,
- GetCredentialException>() {
- @Override
- public void onError(@NonNull GetCredentialException e) {
- loadedResult.set(e);
- latch.countDown();
- }
-
- @Override
- public void onResult(@NonNull GetCredentialResponse result) {
- }
- });
-
+ @Override
+ public void onResult(@NonNull GetCredentialResponse result) {
+ }
+ });
+ });
latch.await(100L, TimeUnit.MILLISECONDS);
if (loadedResult.get() == null) {
return; // A strange flow occurred where an exception wasn't propagated up
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialProviderFactoryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialProviderFactoryTest.kt
new file mode 100644
index 0000000..67375f7
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialProviderFactoryTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.credentials
+
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CredentialProviderFactoryTest {
+ private val context = InstrumentationRegistry.getInstrumentation().context
+
+ private lateinit var credentialProviderFactory: CredentialProviderFactory
+
+ @Before
+ fun setup() {
+ credentialProviderFactory = CredentialProviderFactory(context)
+ }
+
+ private fun clearState() {
+ credentialProviderFactory.testMode = false
+ credentialProviderFactory.testPreUProvider = null
+ credentialProviderFactory.testPostUProvider = null
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 34)
+ fun getBestAvailableProvider_postU_success() {
+ if (Build.VERSION.SDK_INT <= 33) {
+ return
+ }
+ clearState()
+ val expectedProvider = FakeProvider(success = true)
+ credentialProviderFactory.testMode = true
+ credentialProviderFactory.testPostUProvider = expectedProvider
+
+ val actualProvider = credentialProviderFactory.getBestAvailableProvider()
+
+ assertThat(actualProvider).isNotNull()
+ assertThat(actualProvider).isEqualTo(expectedProvider)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 34)
+ fun getBestAvailableProvider_postU_notAvailable_preUSuccess() {
+ if (Build.VERSION.SDK_INT <= 33) {
+ return
+ }
+ clearState()
+ val expectedPreUProvider = FakeProvider(success = true)
+ credentialProviderFactory.testMode = true
+ credentialProviderFactory.testPreUProvider = expectedPreUProvider
+ credentialProviderFactory.testPostUProvider = null
+
+ val actualProvider = credentialProviderFactory.getBestAvailableProvider()
+
+ assertThat(actualProvider).isNotNull()
+ assertThat(actualProvider).isEqualTo(expectedPreUProvider)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 34)
+ fun getBestAvailableProvider_postU_notAvailableNoFallbackAllowed_returnsNull() {
+ if (Build.VERSION.SDK_INT <= 33) {
+ return
+ }
+ clearState()
+ val expectedPreUProvider = FakeProvider(success = true)
+ credentialProviderFactory.testMode = true
+ credentialProviderFactory.testPreUProvider = expectedPreUProvider
+ credentialProviderFactory.testPostUProvider = null
+
+ val actualProvider = credentialProviderFactory.getBestAvailableProvider(
+ shouldFallbackToPreU = false
+ )
+
+ assertNull(actualProvider)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 34)
+ fun getBestAvailableProvider_postU_preAndPostUProvNull_returnsNull() {
+ if (Build.VERSION.SDK_INT <= 33) {
+ return
+ }
+ clearState()
+ credentialProviderFactory.testMode = true
+ credentialProviderFactory.testPostUProvider = null
+ credentialProviderFactory.testPreUProvider = null
+
+ assertNull(credentialProviderFactory.getBestAvailableProvider())
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 34)
+ fun getBestAvailableProvider_postU_preAndPostUProvNotAvailable_returnsNull() {
+ if (Build.VERSION.SDK_INT <= 33) {
+ return
+ }
+ clearState()
+ credentialProviderFactory.testMode = true
+ credentialProviderFactory.testPostUProvider = FakeProvider(success = false)
+ credentialProviderFactory.testPreUProvider = FakeProvider(success = false)
+
+ assertNull(credentialProviderFactory.getBestAvailableProvider())
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = 33)
+ fun getBestAvailableProvider_preU_success() {
+ if (Build.VERSION.SDK_INT >= 34) {
+ return
+ }
+ clearState()
+ val expectedProvider = FakeProvider(success = true)
+ credentialProviderFactory.testMode = true
+ credentialProviderFactory.testPreUProvider = expectedProvider
+
+ val actualProvider = credentialProviderFactory.getBestAvailableProvider()
+
+ assertThat(actualProvider).isNotNull()
+ assertThat(actualProvider).isEqualTo(expectedProvider)
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = 33)
+ fun getBestAvailableProvider_preU_providerNotAvailable_returnsNull() {
+ if (Build.VERSION.SDK_INT >= 34) {
+ return
+ }
+ clearState()
+ credentialProviderFactory.testMode = true
+ credentialProviderFactory.testPreUProvider = FakeProvider(success = false)
+
+ val provider = credentialProviderFactory.getBestAvailableProvider()
+
+ assertNull(provider)
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = 33)
+ fun getBestAvailableProvider_preUNull_returnsNull() {
+ if (Build.VERSION.SDK_INT >= 34) {
+ return
+ }
+ clearState()
+ credentialProviderFactory.testMode = true
+ credentialProviderFactory.testPreUProvider = null
+
+ assertNull(credentialProviderFactory.getBestAvailableProvider())
+ }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/FakeProvider.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/FakeProvider.kt
new file mode 100644
index 0000000..0c31631
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/FakeProvider.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials
+
+import android.content.Context
+import android.os.CancellationSignal
+import androidx.credentials.exceptions.ClearCredentialException
+import androidx.credentials.exceptions.CreateCredentialException
+import androidx.credentials.exceptions.GetCredentialException
+import java.util.concurrent.Executor
+
+class FakeProvider(
+ private val success: Boolean
+) : CredentialProvider {
+ override fun onGetCredential(
+ context: Context,
+ request: GetCredentialRequest,
+ cancellationSignal: CancellationSignal?,
+ executor: Executor,
+ callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onCreateCredential(
+ context: Context,
+ request: CreateCredentialRequest,
+ cancellationSignal: CancellationSignal?,
+ executor: Executor,
+ callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun isAvailableOnDevice(): Boolean {
+ return success
+ }
+
+ override fun onClearCredential(
+ request: ClearCredentialStateRequest,
+ cancellationSignal: CancellationSignal?,
+ executor: Executor,
+ callback: CredentialManagerCallback<Void?, ClearCredentialException>
+ ) {
+ TODO("Not yet implemented")
+ }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionJavaTest.java
index b2c0494..6a9a6fc 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionJavaTest.java
@@ -20,6 +20,8 @@
import static org.junit.Assert.assertThrows;
+import static java.util.Collections.emptySet;
+
import android.content.ComponentName;
import android.os.Bundle;
@@ -37,6 +39,9 @@
@SmallTest
public class GetCustomCredentialOptionJavaTest {
+ private static final @PriorityHints int EXPECTED_CUSTOM_DEFAULT_PRIORITY =
+ PriorityHints.PRIORITY_DEFAULT;
+
@Test
public void constructor_nullType_throws() {
assertThrows("Expected null type to throw NPE",
@@ -71,6 +76,27 @@
}
@Test
+ public void constructor_priorityNotPassedIn_defaultPriorityRetrievedSuccess() {
+ GetCustomCredentialOption customCredentialOption = new GetCustomCredentialOption("T",
+ new Bundle(), new Bundle(), true, false);
+
+ assertThat(customCredentialOption.getTypePriorityHint()).isEqualTo(
+ EXPECTED_CUSTOM_DEFAULT_PRIORITY);
+ }
+
+ @Test
+ public void constructor_priorityPassedIn_setPriorityRetrievedSuccess() {
+ @PriorityHints int expectedOverwrittenPriorityHint = PriorityHints.PRIORITY_OIDC_OR_SIMILAR;
+
+ GetCustomCredentialOption customCredentialOption = new GetCustomCredentialOption("T",
+ new Bundle(), new Bundle(), true, false,
+ emptySet(), expectedOverwrittenPriorityHint);
+
+ assertThat(customCredentialOption.getTypePriorityHint()).isEqualTo(
+ expectedOverwrittenPriorityHint);
+ }
+
+ @Test
public void getter_frameworkProperties() {
String expectedType = "TYPE";
Bundle expectedBundle = new Bundle();
@@ -83,6 +109,9 @@
new ComponentName("pkg", "cls"),
new ComponentName("pkg2", "cls2")
);
+ int expectedPriorityCategoryValue = EXPECTED_CUSTOM_DEFAULT_PRIORITY;
+ expectedBundle.putInt(
+ CredentialOption.BUNDLE_KEY_TYPE_PRIORITY_VALUE, expectedPriorityCategoryValue);
GetCustomCredentialOption option = new GetCustomCredentialOption(expectedType,
expectedBundle,
@@ -99,6 +128,7 @@
assertThat(option.isSystemProviderRequired()).isEqualTo(expectedSystemProvider);
assertThat(option.getAllowedProviders())
.containsAtLeastElementsIn(expectedAllowedProviders);
+ assertThat(option.getTypePriorityHint()).isEqualTo(EXPECTED_CUSTOM_DEFAULT_PRIORITY);
}
@Test
@@ -114,12 +144,14 @@
new ComponentName("pkg", "cls"),
new ComponentName("pkg2", "cls2")
);
+ @PriorityHints int expectedPriorityHint = PriorityHints.PRIORITY_OIDC_OR_SIMILAR;
GetCustomCredentialOption option = new GetCustomCredentialOption(expectedType,
expectedBundle,
expectedCandidateQueryDataBundle,
expectedSystemProvider,
expectedAutoSelectAllowed,
- expectedAllowedProviders);
+ expectedAllowedProviders,
+ expectedPriorityHint);
CredentialOption convertedOption = CredentialOption.createFrom(
option.getType(), option.getRequestData(), option.getCandidateQueryData(),
@@ -135,5 +167,6 @@
assertThat(actualOption.isSystemProviderRequired()).isEqualTo(expectedSystemProvider);
assertThat(actualOption.getAllowedProviders())
.containsAtLeastElementsIn(expectedAllowedProviders);
+ assertThat(actualOption.getTypePriorityHint()).isEqualTo(expectedPriorityHint);
}
}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt
index 706b9c0..58ac8a0 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt
@@ -48,7 +48,36 @@
@Test
fun constructor_nonEmptyTypeNonNullBundle_success() {
- GetCustomCredentialOption("T", Bundle(), Bundle(), true, true)
+ GetCustomCredentialOption("T", Bundle(), Bundle(), true,
+ true)
+ }
+
+ @Test
+ fun constructor_priorityNotPassedIn_defaultPriorityRetrievedSuccess() {
+ val customCredentialOption = GetCustomCredentialOption(
+ "T",
+ Bundle(), Bundle(), true, false
+ )
+
+ assertThat(customCredentialOption.typePriorityHint).isEqualTo(
+ EXPECTED_CUSTOM_DEFAULT_PRIORITY
+ )
+ }
+
+ @Test
+ fun constructor_priorityPassedIn_setPriorityRetrievedSuccess() {
+ val expectedOverwrittenPriorityHint: @PriorityHints Int =
+ PriorityHints.PRIORITY_OIDC_OR_SIMILAR
+
+ val customCredentialOption = GetCustomCredentialOption(
+ "T",
+ Bundle(), Bundle(), true, false,
+ emptySet(), expectedOverwrittenPriorityHint
+ )
+
+ assertThat(customCredentialOption.typePriorityHint).isEqualTo(
+ expectedOverwrittenPriorityHint
+ )
}
@Test
@@ -64,6 +93,10 @@
ComponentName("pkg", "cls"),
ComponentName("pkg2", "cls2")
)
+ val expectedPriorityCategoryValue = EXPECTED_CUSTOM_DEFAULT_PRIORITY
+ expectedBundle.putInt(
+ CredentialOption.BUNDLE_KEY_TYPE_PRIORITY_VALUE, expectedPriorityCategoryValue
+ )
val option = GetCustomCredentialOption(
expectedType,
@@ -86,6 +119,7 @@
assertThat(option.isSystemProviderRequired).isEqualTo(expectedSystemProvider)
assertThat(option.allowedProviders)
.containsAtLeastElementsIn(expectedAllowedProviders)
+ assertThat(option.typePriorityHint).isEqualTo(EXPECTED_CUSTOM_DEFAULT_PRIORITY)
}
@Test
@@ -101,13 +135,15 @@
ComponentName("pkg", "cls"),
ComponentName("pkg2", "cls2")
)
+ val expectedPriorityHint: @PriorityHints Int = PriorityHints.PRIORITY_OIDC_OR_SIMILAR
val option = GetCustomCredentialOption(
expectedType,
expectedBundle,
expectedCandidateQueryDataBundle,
expectedSystemProvider,
expectedAutoSelectAllowed,
- expectedAllowedProviders
+ expectedAllowedProviders,
+ expectedPriorityHint
)
val convertedOption = createFrom(
@@ -129,5 +165,11 @@
assertThat(actualOption.isSystemProviderRequired).isEqualTo(expectedSystemProvider)
assertThat(actualOption.allowedProviders)
.containsAtLeastElementsIn(expectedAllowedProviders)
+ assertThat(actualOption.typePriorityHint).isEqualTo(expectedPriorityHint)
+ }
+
+ private companion object {
+ private const val EXPECTED_CUSTOM_DEFAULT_PRIORITY: @PriorityHints Int =
+ PriorityHints.PRIORITY_DEFAULT
}
}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionJavaTest.java
index 3e54f1b..e8790b3 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionJavaTest.java
@@ -34,6 +34,10 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class GetPasswordOptionJavaTest {
+
+ private static final @PriorityHints int EXPECTED_PASSWORD_PRIORITY =
+ PriorityHints.PRIORITY_PASSWORD_OR_SIMILAR;
+
@Test
public void emptyConstructor_success() {
GetPasswordOption option = new GetPasswordOption();
@@ -41,6 +45,7 @@
assertThat(option.isAutoSelectAllowed()).isFalse();
assertThat(option.getAllowedUserIds()).isEmpty();
assertThat(option.getAllowedProviders()).isEmpty();
+ assertThat(option.getTypePriorityHint()).isEqualTo(EXPECTED_PASSWORD_PRIORITY);
}
@Test
@@ -63,6 +68,13 @@
}
@Test
+ public void getter_defaultPriorityHint_success() {
+ GetPasswordOption option = new GetPasswordOption();
+
+ assertThat(option.getTypePriorityHint()).isEqualTo(EXPECTED_PASSWORD_PRIORITY);
+ }
+
+ @Test
public void getter_frameworkProperties() {
Set<String> expectedAllowedUserIds = ImmutableSet.of("id1", "id2", "id3");
Set<ComponentName> expectedAllowedProviders = ImmutableSet.of(
@@ -70,6 +82,7 @@
new ComponentName("pkg2", "cls2")
);
boolean expectedIsAutoSelectAllowed = true;
+ int expectedPriorityCategoryValue = EXPECTED_PASSWORD_PRIORITY;
GetPasswordOption option = new GetPasswordOption(expectedAllowedUserIds,
expectedIsAutoSelectAllowed, expectedAllowedProviders);
@@ -85,9 +98,16 @@
assertThat(option.getCandidateQueryData().getStringArrayList(
GetPasswordOption.BUNDLE_KEY_ALLOWED_USER_IDS))
.containsExactlyElementsIn(expectedAllowedUserIds);
+ assertThat(option.getRequestData().getInt(CredentialOption.BUNDLE_KEY_TYPE_PRIORITY_VALUE))
+ .isEqualTo(expectedPriorityCategoryValue);
+ assertThat(option.getCandidateQueryData().getInt(CredentialOption
+ .BUNDLE_KEY_TYPE_PRIORITY_VALUE))
+ .isEqualTo(expectedPriorityCategoryValue);
assertThat(option.isSystemProviderRequired()).isFalse();
assertThat(option.getAllowedProviders())
.containsExactlyElementsIn(expectedAllowedProviders);
+ assertThat(option.getTypePriorityHint()).isEqualTo(
+ EXPECTED_PASSWORD_PRIORITY);
}
@Test
@@ -111,7 +131,6 @@
Boolean customCandidateQueryDataValue = true;
candidateQueryData.putBoolean(customCandidateQueryDataKey, customCandidateQueryDataValue);
-
CredentialOption convertedOption = CredentialOption.createFrom(
option.getType(), requestData, candidateQueryData,
option.isSystemProviderRequired(), option.getAllowedProviders());
@@ -127,5 +146,7 @@
.isEqualTo(customRequestDataValue);
assertThat(convertedOption.getCandidateQueryData().getBoolean(customCandidateQueryDataKey))
.isEqualTo(customCandidateQueryDataValue);
+ assertThat(convertedOption.getTypePriorityHint()).isEqualTo(
+ EXPECTED_PASSWORD_PRIORITY);
}
}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt
index 105406d..93e2e84 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt
@@ -36,6 +36,7 @@
assertThat(option.isAutoSelectAllowed).isFalse()
assertThat(option.allowedProviders).isEmpty()
assertThat(option.allowedUserIds).isEmpty()
+ assertThat(option.typePriorityHint).isEqualTo(EXPECTED_PASSWORD_PRIORITY)
}
@Test
@@ -61,6 +62,13 @@
}
@Test
+ fun getter_defaultPriorityHint_success() {
+ val option = GetPasswordOption()
+
+ assertThat(option.typePriorityHint).isEqualTo(EXPECTED_PASSWORD_PRIORITY)
+ }
+
+ @Test
fun getter_frameworkProperties() {
val expectedAllowedUserIds: Set<String> = setOf("id1", "id2", "id3")
val expectedAllowedProviders: Set<ComponentName> = setOf(
@@ -68,6 +76,7 @@
ComponentName("pkg2", "cls2")
)
val expectedIsAutoSelectAllowed = true
+ val expectedCategoryValue = EXPECTED_PASSWORD_PRIORITY
val option = GetPasswordOption(
allowedUserIds = expectedAllowedUserIds,
@@ -84,9 +93,17 @@
CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED)).isTrue()
assertThat(option.candidateQueryData.getStringArrayList(
BUNDLE_KEY_ALLOWED_USER_IDS)).containsExactlyElementsIn(expectedAllowedUserIds)
+ assertThat(option.requestData.getInt(CredentialOption.BUNDLE_KEY_TYPE_PRIORITY_VALUE))
+ .isEqualTo(
+ expectedCategoryValue)
+ assertThat(option.candidateQueryData.getInt(CredentialOption
+ .BUNDLE_KEY_TYPE_PRIORITY_VALUE))
+ .isEqualTo(
+ expectedCategoryValue)
assertThat(option.isSystemProviderRequired).isFalse()
assertThat(option.allowedProviders)
.containsExactlyElementsIn(expectedAllowedProviders)
+ assertThat(option.typePriorityHint).isEqualTo(EXPECTED_PASSWORD_PRIORITY)
}
@Test
@@ -130,5 +147,11 @@
.isEqualTo(customRequestDataValue)
assertThat(convertedOption.candidateQueryData.getBoolean(customCandidateQueryDataKey))
.isEqualTo(customCandidateQueryDataValue)
+ assertThat(option.typePriorityHint).isEqualTo(EXPECTED_PASSWORD_PRIORITY)
+ }
+
+ private companion object {
+ const val EXPECTED_PASSWORD_PRIORITY: @PriorityHints Int = PriorityHints
+ .PRIORITY_PASSWORD_OR_SIMILAR
}
}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java
index 18290f8..2d24a36 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java
@@ -17,6 +17,7 @@
package androidx.credentials;
import static androidx.credentials.CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED;
+import static androidx.credentials.CredentialOption.BUNDLE_KEY_TYPE_PRIORITY_VALUE;
import static androidx.credentials.GetPublicKeyCredentialOption.BUNDLE_KEY_REQUEST_JSON;
import static com.google.common.truth.Truth.assertThat;
@@ -41,6 +42,8 @@
@SmallTest
public class GetPublicKeyCredentialOptionJavaTest {
private static final String TEST_REQUEST_JSON = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
+ private static final @PriorityHints int EXPECTED_PASSKEY_PRIORITY =
+ PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR;
@Test
@@ -74,6 +77,15 @@
}
@Test
+ public void getter_defaultPriorityHint_success() {
+ GetPublicKeyCredentialOption getPublicKeyCredentialOption = new
+ GetPublicKeyCredentialOption(TEST_REQUEST_JSON);
+
+ assertThat(getPublicKeyCredentialOption.getTypePriorityHint())
+ .isEqualTo(EXPECTED_PASSKEY_PRIORITY);
+ }
+
+ @Test
public void getter_frameworkProperties_success() {
Set<ComponentName> expectedAllowedProviders = ImmutableSet.of(
new ComponentName("pkg", "cls"),
@@ -90,6 +102,7 @@
expectedData.putByteArray(GetPublicKeyCredentialOption.BUNDLE_KEY_CLIENT_DATA_HASH,
clientDataHash);
expectedData.putBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, expectedIsAutoSelect);
+ expectedData.putInt(BUNDLE_KEY_TYPE_PRIORITY_VALUE, EXPECTED_PASSKEY_PRIORITY);
GetPublicKeyCredentialOption option = new GetPublicKeyCredentialOption(
requestJsonExpected, clientDataHash, expectedAllowedProviders);
@@ -100,6 +113,7 @@
assertThat(option.isSystemProviderRequired()).isFalse();
assertThat(option.getAllowedProviders())
.containsAtLeastElementsIn(expectedAllowedProviders);
+ assertThat(option.getTypePriorityHint()).isEqualTo(EXPECTED_PASSKEY_PRIORITY);
}
@Test
@@ -136,5 +150,6 @@
.isEqualTo(customRequestDataValue);
assertThat(convertedOption.getCandidateQueryData().getBoolean(customCandidateQueryDataKey))
.isEqualTo(customCandidateQueryDataValue);
+ assertThat(option.getTypePriorityHint()).isEqualTo(EXPECTED_PASSKEY_PRIORITY);
}
}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
index 5c5e4e7..1c05489 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
@@ -18,6 +18,7 @@
import android.content.ComponentName
import android.os.Bundle
+import androidx.credentials.CredentialOption.Companion.BUNDLE_KEY_TYPE_PRIORITY_VALUE
import androidx.credentials.CredentialOption.Companion.createFrom
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -30,9 +31,6 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
class GetPublicKeyCredentialOptionTest {
- companion object Constant {
- private const val TEST_REQUEST_JSON = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
- }
@Test
fun constructor_emptyJson_throwsIllegalArgumentException() {
@@ -50,12 +48,20 @@
@Test
fun getter_requestJson_success() {
val testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
- val createPublicKeyCredentialReq = GetPublicKeyCredentialOption(testJsonExpected)
- val testJsonActual = createPublicKeyCredentialReq.requestJson
+ val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(testJsonExpected)
+ val testJsonActual = getPublicKeyCredentialOption.requestJson
assertThat(testJsonActual).isEqualTo(testJsonExpected)
}
@Test
+ fun getter_defaultPriorityHint_success() {
+ val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(TEST_REQUEST_JSON)
+
+ assertThat(getPublicKeyCredentialOption.typePriorityHint)
+ .isEqualTo(EXPECTED_PASSKEY_PRIORITY);
+ }
+
+ @Test
fun getter_frameworkProperties_success() {
val requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
val expectedAutoSelectAllowed = true
@@ -65,6 +71,7 @@
)
val clientDataHash = "hash".toByteArray()
val expectedData = Bundle()
+ val expectedPriorityInt = EXPECTED_PASSKEY_PRIORITY
expectedData.putString(
PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
GetPublicKeyCredentialOption.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION
@@ -73,6 +80,7 @@
GetPublicKeyCredentialOption.BUNDLE_KEY_REQUEST_JSON,
requestJsonExpected
)
+ expectedData.putInt(BUNDLE_KEY_TYPE_PRIORITY_VALUE, expectedPriorityInt)
expectedData.putByteArray(GetPublicKeyCredentialOption.BUNDLE_KEY_CLIENT_DATA_HASH,
clientDataHash)
expectedData.putBoolean(
@@ -90,6 +98,7 @@
assertThat(option.isSystemProviderRequired).isFalse()
assertThat(option.isAutoSelectAllowed).isTrue()
assertThat(option.allowedProviders).containsAtLeastElementsIn(expectedAllowedProviders)
+ assertThat(option.typePriorityHint).isEqualTo(EXPECTED_PASSKEY_PRIORITY)
}
@Test
@@ -132,4 +141,10 @@
assertThat(convertedOption.candidateQueryData.getBoolean(customCandidateQueryDataKey))
.isEqualTo(customCandidateQueryDataValue)
}
+
+ companion object Constant {
+ private const val TEST_REQUEST_JSON = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
+ const val EXPECTED_PASSKEY_PRIORITY: @PriorityHints Int = PriorityHints
+ .PRIORITY_PASSKEY_OR_SIMILAR
+ }
}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
index 3654984..cd6ab67 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
@@ -15,10 +15,14 @@
*/
package androidx.credentials.provider.ui;
+import static androidx.credentials.CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import android.app.PendingIntent;
import android.app.slice.Slice;
@@ -204,6 +208,79 @@
assertNotNull(entry);
assertEntryWithAllParamsFromSlice(entry);
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void isDefaultIcon_noIconSet_returnsTrue() {
+ CustomCredentialEntry entry = new CustomCredentialEntry
+ .Builder(mContext, TYPE, TITLE, mPendingIntent, mBeginCredentialOption).build();
+
+ assertTrue(entry.hasDefaultIcon());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void isDefaultIcon_noIconSetFromSlice_returnsTrue() {
+ CustomCredentialEntry entry = new CustomCredentialEntry
+ .Builder(mContext, TYPE, TITLE, mPendingIntent, mBeginCredentialOption).build();
+
+ Slice slice = CustomCredentialEntry.toSlice(entry);
+
+ assertNotNull(slice);
+
+ CustomCredentialEntry entryFromSlice = CustomCredentialEntry.fromSlice(slice);
+
+ assertNotNull(entryFromSlice);
+ assertTrue(entryFromSlice.hasDefaultIcon());
+ assertTrue(entry.hasDefaultIcon());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void isDefaultIcon_customIconSetFromSlice_returnsTrue() {
+ CustomCredentialEntry entry = new CustomCredentialEntry
+ .Builder(mContext, TYPE, TITLE, mPendingIntent, mBeginCredentialOption)
+ .setIcon(ICON).build();
+
+ Slice slice = CustomCredentialEntry.toSlice(entry);
+
+ assertNotNull(slice);
+
+ CustomCredentialEntry entryFromSlice = CustomCredentialEntry.fromSlice(slice);
+
+ assertNotNull(entryFromSlice);
+ assertFalse(entryFromSlice.hasDefaultIcon());
+ assertFalse(entry.hasDefaultIcon());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void isDefaultIcon_customIcon_returnsFalse() {
+ CustomCredentialEntry entry = new CustomCredentialEntry
+ .Builder(mContext, TYPE, TITLE, mPendingIntent, mBeginCredentialOption)
+ .setIcon(ICON).build();
+
+ assertFalse(entry.hasDefaultIcon());
+ }
+
+ @Test
+ public void isAutoSelectAllowedFromOption_optionAllows_returnsTrue() {
+ mBeginCredentialOption.getCandidateQueryData().putBoolean(
+ BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, true);
+ CustomCredentialEntry entry = new CustomCredentialEntry
+ .Builder(mContext, TYPE, TITLE, mPendingIntent, mBeginCredentialOption).build();
+
+ assertTrue(entry.isAutoSelectAllowedFromOption());
+ }
+
+ @Test
+ public void isAutoSelectAllowedFromOption_optionDisallows_returnsFalse() {
+ CustomCredentialEntry entry = new CustomCredentialEntry
+ .Builder(mContext, TYPE, TITLE, mPendingIntent, mBeginCredentialOption).build();
+
+ assertFalse(entry.isAutoSelectAllowedFromOption());
+ }
+
private CustomCredentialEntry constructEntryWithRequiredParams() {
return new CustomCredentialEntry.Builder(
mContext,
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
index c9f7331..69680b7 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
@@ -21,6 +21,7 @@
import android.graphics.drawable.Icon
import android.os.Bundle
import android.service.credentials.CredentialEntry
+import androidx.credentials.CredentialOption
import androidx.credentials.R
import androidx.credentials.equals
import androidx.credentials.provider.BeginGetCredentialOption
@@ -35,6 +36,7 @@
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import java.time.Instant
+import org.junit.Assert
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertThrows
import org.junit.Test
@@ -225,7 +227,7 @@
@SdkSuppress(minSdkVersion = 28)
fun fromSlice_requiredParams_success() {
val originalEntry = constructEntryWithRequiredParams()
- val slice = CustomCredentialEntry.toSlice(
+ val slice = toSlice(
originalEntry)
assertNotNull(slice)
val entry = fromSlice(slice!!)
@@ -261,6 +263,61 @@
assertNotNull(entry)
assertEntryWithAllParamsFromSlice(entry!!)
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ fun isDefaultIcon_noIconSet_returnsTrue() {
+ val entry = CustomCredentialEntry.Builder(
+ mContext,
+ TYPE,
+ TITLE,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+ Assert.assertTrue(entry.hasDefaultIcon)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ fun isDefaultIcon_customIcon_returnsFalse() {
+ val entry = CustomCredentialEntry.Builder(
+ mContext,
+ TYPE,
+ TITLE,
+ mPendingIntent,
+ BEGIN_OPTION
+ )
+ .setIcon(ICON).build()
+ Assert.assertFalse(entry.hasDefaultIcon)
+ }
+
+ @Test
+ fun isAutoSelectAllowedFromOption_optionAllows_returnsTrue() {
+ BEGIN_OPTION.candidateQueryData.putBoolean(
+ CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, true
+ )
+ val entry = CustomCredentialEntry.Builder(
+ mContext,
+ TYPE,
+ TITLE,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+ Assert.assertTrue(entry.isAutoSelectAllowedFromOption)
+ }
+
+ @Test
+ fun isAutoSelectAllowedFromOption_optionDisallows_returnsFalse() {
+ val entry = CustomCredentialEntry.Builder(
+ mContext,
+ TYPE,
+ TITLE,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+ Assert.assertFalse(entry.isAutoSelectAllowedFromOption)
+ }
+
private fun constructEntryWithRequiredParams(): CustomCredentialEntry {
return CustomCredentialEntry(
mContext,
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
index 9b24056..639f2ad 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
@@ -15,13 +15,17 @@
*/
package androidx.credentials.provider.ui;
+import static androidx.credentials.CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import android.app.PendingIntent;
+import android.app.slice.Slice;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -60,7 +64,7 @@
100, 100, Bitmap.Config.ARGB_8888));
private final BeginGetPasswordOption mBeginGetPasswordOption = new BeginGetPasswordOption(
new HashSet<>(),
- Bundle.EMPTY, "id");
+ new Bundle(), "id");
private final Context mContext = ApplicationProvider.getApplicationContext();
private final Intent mIntent = new Intent();
private final PendingIntent mPendingIntent =
@@ -88,6 +92,49 @@
null, USERNAME, mPendingIntent, mBeginGetPasswordOption
).build());
}
+
+ @SdkSuppress(minSdkVersion = 28)
+ @Test
+ public void isDefaultIcon_customIconSetFromSlice_returnsFalse() {
+ PasswordCredentialEntry entry = new PasswordCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ mBeginGetPasswordOption
+ ).setIcon(ICON).build();
+
+ Slice slice = PasswordCredentialEntry.toSlice(entry);
+
+ assertNotNull(slice);
+
+ PasswordCredentialEntry entryFromSlice = PasswordCredentialEntry
+ .fromSlice(slice);
+
+ assertNotNull(entryFromSlice);
+ assertFalse(entryFromSlice.hasDefaultIcon());
+ assertFalse(entry.hasDefaultIcon());
+ }
+
+ @SdkSuppress(minSdkVersion = 28)
+ @Test
+ public void isDefaultIcon_noIconSetFromSlice_returnsTrue() {
+ PasswordCredentialEntry entry = new PasswordCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ mBeginGetPasswordOption
+ ).build();
+
+ Slice slice = PasswordCredentialEntry.toSlice(entry);
+ assertNotNull(slice);
+ PasswordCredentialEntry entryFromSlice = PasswordCredentialEntry
+ .fromSlice(slice);
+
+ assertNotNull(entryFromSlice);
+ assertTrue(entryFromSlice.hasDefaultIcon());
+ assertTrue(entry.hasDefaultIcon());
+ }
+
@Test
public void build_nullUsername_throwsNPE() {
assertThrows("Expected null username to throw NPE",
@@ -126,6 +173,44 @@
assertThat(TestUtilsKt.equals(entry.getIcon(),
Icon.createWithResource(mContext, R.drawable.ic_password))).isTrue();
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void isDefaultIcon_noIconSet_returnsTrue() {
+ PasswordCredentialEntry entry = new PasswordCredentialEntry
+ .Builder(mContext, USERNAME, mPendingIntent, mBeginGetPasswordOption).build();
+
+ assertTrue(entry.hasDefaultIcon());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void isDefaultIcon_customIcon_returnsFalse() {
+ PasswordCredentialEntry entry = new PasswordCredentialEntry
+ .Builder(mContext, USERNAME, mPendingIntent, mBeginGetPasswordOption)
+ .setIcon(ICON).build();
+
+ assertFalse(entry.hasDefaultIcon());
+ }
+
+ @Test
+ public void isAutoSelectAllowedFromOption_optionAllows_returnsTrue() {
+ mBeginGetPasswordOption.getCandidateQueryData().putBoolean(
+ BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, true);
+ PasswordCredentialEntry entry = new PasswordCredentialEntry
+ .Builder(mContext, USERNAME, mPendingIntent, mBeginGetPasswordOption).build();
+
+ assertTrue(entry.isAutoSelectAllowedFromOption());
+ }
+
+ @Test
+ public void isAutoSelectAllowedFromOption_optionDisallows_returnsFalse() {
+ PasswordCredentialEntry entry = new PasswordCredentialEntry
+ .Builder(mContext, USERNAME, mPendingIntent, mBeginGetPasswordOption).build();
+
+ assertFalse(entry.isAutoSelectAllowedFromOption());
+ }
+
@Test
public void build_nullTypeDisplayName_defaultDisplayNameSet() {
PasswordCredentialEntry entry = new PasswordCredentialEntry.Builder(
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
index 96fd725..53c9275 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
@@ -21,6 +21,7 @@
import android.graphics.drawable.Icon
import android.os.Bundle
import android.service.credentials.CredentialEntry
+import androidx.credentials.CredentialOption
import androidx.credentials.PasswordCredential
import androidx.credentials.R
import androidx.credentials.equals
@@ -35,6 +36,7 @@
import java.time.Instant
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNotNull
+import org.junit.Assert
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
@@ -74,22 +76,117 @@
)
}
}
+
+ @SdkSuppress(minSdkVersion = 28)
@Test
fun constructor_nullIcon_defaultIconSet() {
val entry = PasswordCredentialEntry.Builder(
- mContext, USERNAME, mPendingIntent, BEGIN_OPTION).build()
+ mContext, USERNAME, mPendingIntent, BEGIN_OPTION
+ ).build()
assertThat(
equals(
entry.icon,
Icon.createWithResource(mContext, R.drawable.ic_password)
)
).isTrue()
+ Assert.assertTrue(entry.hasDefaultIcon)
+ }
+
+ @SdkSuppress(minSdkVersion = 28)
+ @Test
+ fun isDefaultIcon_noIconSet_returnsTrue() {
+ val entry = PasswordCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+
+ Assert.assertTrue(entry.hasDefaultIcon)
+ }
+
+ @SdkSuppress(minSdkVersion = 28)
+ @Test
+ fun isDefaultIcon_customIconSetFromSlice_returnsFalse() {
+ val entry = PasswordCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).setIcon(ICON).build()
+
+ val slice = PasswordCredentialEntry.toSlice(entry)
+ assertNotNull(slice)
+
+ val entryFromSlice = fromSlice(slice!!)
+
+ Assert.assertNotNull(entryFromSlice)
+ Assert.assertFalse(entryFromSlice!!.hasDefaultIcon)
+ Assert.assertFalse(entry.hasDefaultIcon)
+ }
+
+ @SdkSuppress(minSdkVersion = 28)
+ @Test
+ fun isDefaultIcon_noIconSetFromSlice_returnsTrue() {
+ val entry = PasswordCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+
+ val slice = PasswordCredentialEntry.toSlice(entry)
+ assertNotNull(slice)
+ val entryFromSlice = fromSlice(slice!!)
+
+ Assert.assertNotNull(entryFromSlice)
+ Assert.assertTrue(entryFromSlice!!.hasDefaultIcon)
+ Assert.assertTrue(entry.hasDefaultIcon)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ fun isDefaultIcon_customIcon_returnsFalse() {
+ val entry = PasswordCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ )
+ .setIcon(ICON).build()
+ Assert.assertFalse(entry.hasDefaultIcon)
+ }
+
+ @Test
+ fun isAutoSelectAllowedFromOption_optionAllows_returnsTrue() {
+ BEGIN_OPTION.candidateQueryData.putBoolean(
+ CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, true
+ )
+ val entry = PasswordCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+ Assert.assertTrue(entry.isAutoSelectAllowedFromOption)
+ }
+
+ @Test
+ fun isAutoSelectAllowedFromOption_optionDisallows_returnsFalse() {
+ val entry = PasswordCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+ Assert.assertFalse(entry.isAutoSelectAllowedFromOption)
}
@Test
@Suppress("DEPRECATION")
fun constructor_nullTypeDisplayName_defaultDisplayNameSet() {
val entry = PasswordCredentialEntry(
- mContext, USERNAME, mPendingIntent, BEGIN_OPTION)
+ mContext, USERNAME, mPendingIntent, BEGIN_OPTION
+ )
assertThat(entry.typeDisplayName).isEqualTo(
mContext.getString(
R.string.android_credentials_TYPE_PASSWORD_CREDENTIAL
@@ -268,7 +365,8 @@
assertNotNull(entry.lastUsedTime)
entry.lastUsedTime?.let {
assertThat(LAST_USED_TIME.toEpochMilli()).isEqualTo(
- it.toEpochMilli())
+ it.toEpochMilli()
+ )
}
assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
assertThat(entry.isAutoSelectAllowed).isEqualTo(IS_AUTO_SELECT_ALLOWED)
@@ -283,7 +381,8 @@
private val LAST_USED_TIME = Instant.now()
private val BEGIN_OPTION = BeginGetPasswordOption(
emptySet<String>(),
- Bundle.EMPTY, "id")
+ Bundle(), "id"
+ )
private val ICON = Icon.createWithBitmap(
Bitmap.createBitmap(
100, 100, Bitmap.Config.ARGB_8888
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
index cc50aad..43cbd13 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
@@ -15,10 +15,14 @@
*/
package androidx.credentials.provider.ui;
+import static androidx.credentials.CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import android.app.PendingIntent;
import android.app.slice.Slice;
@@ -144,6 +148,75 @@
assertNotNull(entry);
assertEntryWithAllParams(entry);
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void isDefaultIcon_noIconSet_returnsTrue() {
+ PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry
+ .Builder(mContext, USERNAME, mPendingIntent, mBeginOption).build();
+
+ assertTrue(entry.hasDefaultIcon());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void isDefaultIcon_noIconSetFromSlice_returnsTrue() {
+ PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry
+ .Builder(mContext, USERNAME, mPendingIntent, mBeginOption).build();
+ Slice slice = PublicKeyCredentialEntry.toSlice(entry);
+
+ assertNotNull(slice);
+
+ PublicKeyCredentialEntry entryFromSlice = PublicKeyCredentialEntry.fromSlice(slice);
+
+ assertTrue(entryFromSlice.hasDefaultIcon());
+ assertTrue(entry.hasDefaultIcon());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void isDefaultIcon_customIconAfterSlice_returnsFalse() {
+ PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry
+ .Builder(mContext, USERNAME, mPendingIntent, mBeginOption)
+ .setIcon(ICON).build();
+ Slice slice = PublicKeyCredentialEntry.toSlice(entry);
+
+ assertNotNull(slice);
+
+ PublicKeyCredentialEntry entryFromSlice = PublicKeyCredentialEntry.fromSlice(slice);
+
+ assertFalse(entryFromSlice.hasDefaultIcon());
+ assertFalse(entry.hasDefaultIcon());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ public void isDefaultIcon_customIcon_returnsFalse() {
+ PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry
+ .Builder(mContext, USERNAME, mPendingIntent, mBeginOption)
+ .setIcon(ICON).build();
+
+ assertFalse(entry.hasDefaultIcon());
+ }
+
+ @Test
+ public void isAutoSelectAllowedFromOption_optionAllows_returnsTrue() {
+ mBeginOption.getCandidateQueryData().putBoolean(
+ BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, true);
+ PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry
+ .Builder(mContext, USERNAME, mPendingIntent, mBeginOption).build();
+
+ assertTrue(entry.isAutoSelectAllowedFromOption());
+ }
+
+ @Test
+ public void isAutoSelectAllowedFromOption_optionDisallows_returnsFalse() {
+ PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry
+ .Builder(mContext, USERNAME, mPendingIntent, mBeginOption).build();
+
+ assertFalse(entry.isAutoSelectAllowedFromOption());
+ }
+
private PublicKeyCredentialEntry constructWithRequiredParamsOnly() {
return new PublicKeyCredentialEntry.Builder(mContext, USERNAME, mPendingIntent,
mBeginOption).build();
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
index 07df803..27b6c71 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
@@ -21,6 +21,7 @@
import android.graphics.drawable.Icon
import android.os.Bundle
import android.service.credentials.CredentialEntry
+import androidx.credentials.CredentialOption
import androidx.credentials.PublicKeyCredential
import androidx.credentials.R
import androidx.credentials.equals
@@ -170,6 +171,96 @@
}
}
@Test
+ @SdkSuppress(minSdkVersion = 28)
+ fun isDefaultIcon_noIconSet_returnsTrue() {
+ val entry = PublicKeyCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+ Assert.assertTrue(entry.hasDefaultIcon)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ fun isDefaultIcon_customIcon_returnsFalse() {
+ val entry = PublicKeyCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ )
+ .setIcon(ICON).build()
+ Assert.assertFalse(entry.hasDefaultIcon)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ fun isDefaultIcon_noIconSetFromSlice_returnsTrue() {
+ val entry = PublicKeyCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+ val slice = toSlice(entry)
+
+ Assert.assertNotNull(slice)
+
+ val entryFromSlice = fromSlice(slice!!)
+
+ Assert.assertNotNull(entryFromSlice)
+ Assert.assertTrue(entryFromSlice!!.hasDefaultIcon)
+ Assert.assertTrue(entry.hasDefaultIcon)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 28)
+ fun isDefaultIcon_customIconAfterSlice_returnsFalse() {
+ val entry = PublicKeyCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).setIcon(ICON).build()
+ val slice = toSlice(entry)
+
+ Assert.assertNotNull(slice)
+
+ val entryFromSlice = fromSlice(slice!!)
+
+ Assert.assertFalse(entryFromSlice!!.hasDefaultIcon)
+ Assert.assertFalse(entry.hasDefaultIcon)
+ }
+
+ @Test
+ fun isAutoSelectAllowedFromOption_optionAllows_returnsTrue() {
+ BEGIN_OPTION.candidateQueryData.putBoolean(
+ CredentialOption.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, true
+ )
+ val entry = PublicKeyCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+
+ Assert.assertTrue(entry.isAutoSelectAllowedFromOption)
+ }
+
+ @Test
+ fun isAutoSelectAllowedFromOption_optionDisallows_returnsFalse() {
+ val entry = PublicKeyCredentialEntry.Builder(
+ mContext,
+ USERNAME,
+ mPendingIntent,
+ BEGIN_OPTION
+ ).build()
+ Assert.assertFalse(entry.isAutoSelectAllowedFromOption)
+ }
+
+ @Test
@SdkSuppress(minSdkVersion = 34)
fun fromCredentialEntry_success() {
val originalEntry = constructWithAllParams()
@@ -222,14 +313,19 @@
companion object {
private val BEGIN_OPTION: BeginGetPublicKeyCredentialOption =
- BeginGetPublicKeyCredentialOption(Bundle(), "id",
- "{\"key1\":{\"key2\":{\"key3\":\"value3\"}}}")
+ BeginGetPublicKeyCredentialOption(
+ Bundle(), "id",
+ "{\"key1\":{\"key2\":{\"key3\":\"value3\"}}}"
+ )
private val USERNAME: CharSequence = "title"
private val DISPLAYNAME: CharSequence = "subtitle"
private val TYPE_DISPLAY_NAME: CharSequence = "Password"
private const val LAST_USED_TIME: Long = 10L
- private val ICON = Icon.createWithBitmap(Bitmap.createBitmap(
- 100, 100, Bitmap.Config.ARGB_8888))
+ private val ICON = Icon.createWithBitmap(
+ Bitmap.createBitmap(
+ 100, 100, Bitmap.Config.ARGB_8888
+ )
+ )
private const val IS_AUTO_SELECT_ALLOWED = true
private const val DEFAULT_SINGLE_PROVIDER_ICON_BIT = false
private const val SINGLE_PROVIDER_ICON_BIT = true
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
index 17d2ac5..e1d0a09 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
@@ -155,7 +155,7 @@
return bundle
}
- internal companion object {
+ companion object {
@RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests
const val BUNDLE_KEY_REQUEST_DISPLAY_INFO =
"androidx.credentials.BUNDLE_KEY_REQUEST_DISPLAY_INFO"
@@ -173,32 +173,33 @@
"androidx.credentials.BUNDLE_KEY_DEFAULT_PROVIDER"
/**
- * Returns a RequestDisplayInfo from a `credentialData` Bundle, or otherwise `null` if
- * parsing fails.
+ * Returns a RequestDisplayInfo from a [CreateCredentialRequest.credentialData] Bundle.
+ *
+ * @param from the raw display data in the Bundle format, retrieved from
+ * [CreateCredentialRequest.credentialData]
*/
@JvmStatic
@RequiresApi(23)
- @RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests
- @Suppress("DEPRECATION") // bundle.getParcelable(key)
- fun parseFromCredentialDataBundle(from: Bundle): DisplayInfo? {
+ fun parseFromCredentialDataBundle(from: Bundle): DisplayInfo {
return try {
val displayInfoBundle = from.getBundle(BUNDLE_KEY_REQUEST_DISPLAY_INFO)!!
val userId = displayInfoBundle.getCharSequence(BUNDLE_KEY_USER_ID)
val displayName =
displayInfoBundle.getCharSequence(BUNDLE_KEY_USER_DISPLAY_NAME)
+ @Suppress("DEPRECATION") // bundle.getParcelable(key)
val icon: Icon? =
displayInfoBundle.getParcelable(BUNDLE_KEY_CREDENTIAL_TYPE_ICON)
val defaultProvider: String? =
displayInfoBundle.getString(BUNDLE_KEY_DEFAULT_PROVIDER)
DisplayInfo(userId!!, displayName, icon, defaultProvider)
} catch (e: Exception) {
- null
+ throw IllegalArgumentException(e)
}
}
}
}
- internal companion object {
+ companion object {
@RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests
const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
"androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
@@ -209,10 +210,18 @@
/**
* Attempts to parse the raw data into one of [CreatePasswordRequest],
* [CreatePublicKeyCredentialRequest], and
- * [CreateCustomCredentialRequest]. Otherwise returns null.
+ * [CreateCustomCredentialRequest].
+ *
+ * @param type matches [CreateCredentialRequest.type]
+ * @param credentialData matches [CreateCredentialRequest.credentialData]
+ * @param candidateQueryData matches [CreateCredentialRequest.candidateQueryData]
+ * @param requireSystemProvider whether the request must only be fulfilled by a system
+ * provider
+ * @param origin the origin of a different application if the request is being made on
+ * behalf of that application
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests
@JvmStatic
+ @JvmOverloads
@RequiresApi(23)
fun createFrom(
type: String,
@@ -220,7 +229,7 @@
candidateQueryData: Bundle,
requireSystemProvider: Boolean,
origin: String? = null,
- ): CreateCredentialRequest? {
+ ): CreateCredentialRequest {
return try {
when (type) {
PasswordCredential.TYPE_PASSWORD_CREDENTIAL ->
@@ -248,11 +257,12 @@
requireSystemProvider,
DisplayInfo.parseFromCredentialDataBundle(
credentialData
- ) ?: return null,
+ ),
credentialData.getBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, false),
origin,
credentialData.getBoolean(
- BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, false),
+ BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, false
+ ),
)
}
}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreatePasswordRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreatePasswordRequest.kt
index 0c41df0..c75b561 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreatePasswordRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreatePasswordRequest.kt
@@ -174,8 +174,11 @@
try {
val id = data.getString(BUNDLE_KEY_ID)!!
val password = data.getString(BUNDLE_KEY_PASSWORD)!!
- val displayInfo = DisplayInfo.parseFromCredentialDataBundle(data)
- ?: DisplayInfo(id, null)
+ val displayInfo = try {
+ DisplayInfo.parseFromCredentialDataBundle(data)
+ } catch (e: IllegalArgumentException) {
+ DisplayInfo(id, null)
+ }
val preferImmediatelyAvailableCredentials =
data.getBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, false)
val isAutoSelectAllowed =
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt
index 85b21c5..60f6be3 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt
@@ -211,8 +211,11 @@
val clientDataHash = data.getByteArray(BUNDLE_KEY_CLIENT_DATA_HASH)
val preferImmediatelyAvailableCredentials =
data.getBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, false)
- val displayInfo = DisplayInfo.parseFromCredentialDataBundle(data)
- ?: getRequestDisplayInfo(requestJson)
+ val displayInfo = try {
+ DisplayInfo.parseFromCredentialDataBundle(data)
+ } catch (e: IllegalArgumentException) {
+ getRequestDisplayInfo(requestJson)
+ }
val isAutoSelectAllowed =
data.getBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, false)
return CreatePublicKeyCredentialRequest(
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialManagerImpl.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialManagerImpl.kt
index ad3b56e..caf4349 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialManagerImpl.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialManagerImpl.kt
@@ -120,8 +120,8 @@
executor: Executor,
callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
) {
- val provider: CredentialProvider? = CredentialProviderFactory
- .getBestAvailableProvider(this.context)
+ val provider: CredentialProvider? = CredentialProviderFactory(context)
+ .getBestAvailableProvider()
if (provider == null) {
callback.onError(
GetCredentialProviderConfigurationException(
@@ -162,7 +162,14 @@
executor: Executor,
callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
) {
- val provider = CredentialProviderFactory.getUAndAboveProvider(context)
+ val provider: CredentialProvider? = CredentialProviderFactory(context)
+ .getBestAvailableProvider(shouldFallbackToPreU = false)
+ if (provider == null) {
+ callback.onError(
+ GetCredentialProviderConfigurationException("No Credential Manager provider found")
+ )
+ return
+ }
provider.onGetCredential(
context, pendingGetCredentialHandle, cancellationSignal, executor, callback)
}
@@ -191,7 +198,13 @@
executor: Executor,
callback: CredentialManagerCallback<PrepareGetCredentialResponse, GetCredentialException>,
) {
- val provider = CredentialProviderFactory.getUAndAboveProvider(context)
+ val provider: CredentialProvider? = CredentialProviderFactory(context)
+ .getBestAvailableProvider(shouldFallbackToPreU = false)
+ if (provider == null) {
+ callback.onError(
+ GetCredentialProviderConfigurationException("No Credential Manager provider found"))
+ return
+ }
provider.onPrepareCredential(request, cancellationSignal, executor, callback)
}
@@ -218,8 +231,8 @@
executor: Executor,
callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>,
) {
- val provider: CredentialProvider? = CredentialProviderFactory
- .getBestAvailableProvider(this.context)
+ val provider: CredentialProvider? = CredentialProviderFactory(this.context)
+ .getBestAvailableProvider()
if (provider == null) {
callback.onError(CreateCredentialProviderConfigurationException(
"createCredentialAsync no provider dependencies found - please ensure the " +
@@ -254,8 +267,8 @@
executor: Executor,
callback: CredentialManagerCallback<Void?, ClearCredentialException>,
) {
- val provider: CredentialProvider? = CredentialProviderFactory
- .getBestAvailableProvider(context)
+ val provider: CredentialProvider? = CredentialProviderFactory(context)
+ .getBestAvailableProvider()
if (provider == null) {
callback.onError(ClearCredentialProviderConfigurationException(
"clearCredentialStateAsync no provider dependencies found - please ensure the " +
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
index 94f12dc..4db4053 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
@@ -27,6 +27,19 @@
* [GetCredentialRequest] will be composed of a list of [CredentialOption] subclasses to indicate
* the specific credential types and configurations that your app accepts.
*
+ * The [typePriorityHint] bit helps decide where the credential will be displayed on the
+ * selector. It is used with more importance than signals like 'last recently used' but with less
+ * importance than other signals, such as the ordering of displayed accounts.
+ * It is expected to be one of the defined [PriorityHints] constants. By default,
+ * [GetCustomCredentialOption] will have [PriorityHints.PRIORITY_DEFAULT], [GetPasswordOption] will
+ * have [PriorityHints.PRIORITY_PASSWORD_OR_SIMILAR] and [GetPublicKeyCredentialOption] will have
+ * [PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR]. It is expected that [GetCustomCredentialOption]
+ * types will remain unchanged unless strong reasons arise and cannot ever have
+ * [PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR]. Given passkeys prevent many security threats that
+ * other credentials do not, we enforce that nothing is shown higher than
+ * passkey types in order to provide end users with the safest credentials first. See the spec
+ * [here](https://w3c.github.io/webauthn/) for more information on passkeys.
+ *
* @property type the credential type determined by the credential-type-specific subclass (e.g.
* the type for [GetPasswordOption] is [PasswordCredential.TYPE_PASSWORD_CREDENTIAL] and for
* [GetPublicKeyCredentialOption] is [PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL])
@@ -43,6 +56,9 @@
* not have android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS; for API level < 34,
* this property will not take effect and you should control the allowed provider via
* [library dependencies](https://developer.android.com/training/sign-in/passkeys#add-dependencies))
+ * @property typePriorityHint sets the priority of this entry, which defines how it appears in
+ * the credential selector, with less precedence than account ordering but more precedence than last
+ * used time; see [PriorityHints] for more information
*/
abstract class CredentialOption internal constructor(
val type: String,
@@ -51,17 +67,24 @@
val isSystemProviderRequired: Boolean,
val isAutoSelectAllowed: Boolean,
val allowedProviders: Set<ComponentName>,
+ val typePriorityHint: @PriorityHints Int,
) {
init {
requestData.putBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, isAutoSelectAllowed)
candidateQueryData.putBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, isAutoSelectAllowed)
+ requestData.putInt(BUNDLE_KEY_TYPE_PRIORITY_VALUE,
+ typePriorityHint);
+ candidateQueryData.putInt(BUNDLE_KEY_TYPE_PRIORITY_VALUE, typePriorityHint)
}
internal companion object {
internal const val BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED =
"androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED"
+ internal const val BUNDLE_KEY_TYPE_PRIORITY_VALUE =
+ "androidx.credentials.BUNDLE_KEY_TYPE_PRIORITY_VALUE"
+
internal fun extractAutoSelectValue(data: Bundle): Boolean {
return data.getBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED)
}
@@ -94,13 +117,16 @@
// Parsing failed but don't crash the process. Instead just output a request with
// the raw framework values.
GetCustomCredentialOption(
- type = type,
- requestData = requestData,
+ requestData,
+ type,
candidateQueryData = candidateQueryData,
isSystemProviderRequired = requireSystemProvider,
isAutoSelectAllowed = requestData.getBoolean(
BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, false),
allowedProviders = allowedProviders,
+ typePriorityHint = requestData.getInt(BUNDLE_KEY_TYPE_PRIORITY_VALUE,
+ GetCustomCredentialOption.CUSTOM_OPTION_PRIORITY_CATEGORY
+ ),
)
}
}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt
index 30ec600..a9a4c45d 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt
@@ -21,11 +21,32 @@
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+import androidx.annotation.VisibleForTesting
/**
* Factory that returns the credential provider to be used by Credential Manager.
*/
-internal class CredentialProviderFactory {
+internal class CredentialProviderFactory(val context: Context) {
+
+ @set:VisibleForTesting
+ @get:VisibleForTesting
+ @set:RestrictTo(RestrictTo.Scope.LIBRARY)
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+ var testMode = false
+
+ @set:VisibleForTesting
+ @get:VisibleForTesting
+ @set:RestrictTo(RestrictTo.Scope.LIBRARY)
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+ var testPostUProvider: CredentialProvider? = null
+
+ @set:VisibleForTesting
+ @get:VisibleForTesting
+ @set:RestrictTo(RestrictTo.Scope.LIBRARY)
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+ var testPreUProvider: CredentialProvider? = null
+
companion object {
private const val TAG = "CredProviderFactory"
private const val MAX_CRED_MAN_PRE_FRAMEWORK_API_LEVEL = Build.VERSION_CODES.TIRAMISU
@@ -33,78 +54,108 @@
/** The metadata key to be used when specifying the provider class name in the
* android manifest file. */
private const val CREDENTIAL_PROVIDER_KEY = "androidx.credentials.CREDENTIAL_PROVIDER_KEY"
+ }
- /**
- * Returns the best available provider.
- * Pre-U, the provider is determined by the provider library that the developer includes in
- * the app. Developer must not add more than one provider library.
- * Post-U, providers will be registered with the framework, and enabled by the user.
- */
- fun getBestAvailableProvider(context: Context): CredentialProvider? {
- return if (Build.VERSION.SDK_INT >= 34) { // Android U
- CredentialProviderFrameworkImpl(context)
- } else if (Build.VERSION.SDK_INT <= MAX_CRED_MAN_PRE_FRAMEWORK_API_LEVEL) {
- tryCreatePreUOemProvider(context)
- } else {
- null
+ /**
+ * Returns the best available provider.
+ * Pre-U, the provider is determined by the provider library that the developer includes in
+ * the app. Developer must not add more than one provider library.
+ * Post-U, providers will be registered with the framework, and enabled by the user.
+ */
+ fun getBestAvailableProvider(shouldFallbackToPreU: Boolean = true): CredentialProvider? {
+ if (Build.VERSION.SDK_INT >= 34) { // Android U
+ val postUProvider = tryCreatePostUProvider()
+ if (postUProvider == null && shouldFallbackToPreU) {
+ return tryCreatePreUOemProvider()
}
+ return postUProvider
+ } else if (Build.VERSION.SDK_INT <= MAX_CRED_MAN_PRE_FRAMEWORK_API_LEVEL) {
+ return tryCreatePreUOemProvider()
+ } else {
+ return null
}
+ }
- @RequiresApi(34)
- fun getUAndAboveProvider(context: Context): CredentialProvider {
- return CredentialProviderFrameworkImpl(context)
- }
-
- private fun tryCreatePreUOemProvider(context: Context): CredentialProvider? {
- val classNames = getAllowedProvidersFromManifest(context)
- if (classNames.isEmpty()) {
+ private fun tryCreatePreUOemProvider(): CredentialProvider? {
+ if (testMode) {
+ if (testPreUProvider == null) {
return null
- } else {
- return instantiatePreUProvider(classNames, context)
}
+ val isAvailable = testPreUProvider!!.isAvailableOnDevice()
+ if (isAvailable) {
+ return testPreUProvider
+ }
+ return null
}
- private fun instantiatePreUProvider(classNames: List<String>, context: Context):
- CredentialProvider? {
- var provider: CredentialProvider? = null
- for (className in classNames) {
- try {
- val klass = Class.forName(className)
- val p = klass.getConstructor(Context::class.java).newInstance(context) as
- CredentialProvider
- if (p.isAvailableOnDevice()) {
- if (provider != null) {
- Log.i(TAG, "Only one active OEM CredentialProvider allowed")
- return null
- }
- provider = p
- }
- } catch (_: Throwable) {
- }
+ val classNames = getAllowedProvidersFromManifest(context)
+ if (classNames.isEmpty()) {
+ return null
+ } else {
+ return instantiatePreUProvider(classNames, context)
+ }
+ }
+
+ @RequiresApi(34)
+ private fun tryCreatePostUProvider(): CredentialProvider? {
+ if (testMode) {
+ if (testPostUProvider == null) {
+ return null
}
+ val isAvailable = testPostUProvider!!.isAvailableOnDevice()
+ if (isAvailable) {
+ return testPostUProvider
+ }
+ return null
+ }
+
+ val provider = CredentialProviderFrameworkImpl(context)
+ if (provider.isAvailableOnDevice()) {
return provider
}
+ return null
+ }
- @Suppress("deprecation")
- private fun getAllowedProvidersFromManifest(context: Context): List<String> {
- val packageInfo = context.packageManager
- .getPackageInfo(
- context.packageName, PackageManager.GET_META_DATA or
- PackageManager.GET_SERVICES
- )
+ private fun instantiatePreUProvider(classNames: List<String>, context: Context):
+ CredentialProvider? {
+ var provider: CredentialProvider? = null
+ for (className in classNames) {
+ try {
+ val klass = Class.forName(className)
+ val p = klass.getConstructor(Context::class.java).newInstance(context) as
+ CredentialProvider
+ if (p.isAvailableOnDevice()) {
+ if (provider != null) {
+ Log.i(TAG, "Only one active OEM CredentialProvider allowed")
+ return null
+ }
+ provider = p
+ }
+ } catch (_: Throwable) {
+ }
+ }
+ return provider
+ }
- val classNames = mutableListOf<String>()
- if (packageInfo.services != null) {
- for (serviceInfo in packageInfo.services) {
- if (serviceInfo.metaData != null) {
- val className = serviceInfo.metaData.getString(CREDENTIAL_PROVIDER_KEY)
- if (className != null) {
- classNames.add(className)
- }
+ @Suppress("deprecation")
+ private fun getAllowedProvidersFromManifest(context: Context): List<String> {
+ val packageInfo = context.packageManager
+ .getPackageInfo(
+ context.packageName, PackageManager.GET_META_DATA or
+ PackageManager.GET_SERVICES
+ )
+
+ val classNames = mutableListOf<String>()
+ if (packageInfo.services != null) {
+ for (serviceInfo in packageInfo.services) {
+ if (serviceInfo.metaData != null) {
+ val className = serviceInfo.metaData.getString(CREDENTIAL_PROVIDER_KEY)
+ if (className != null) {
+ classNames.add(className)
}
}
}
- return classNames.toList()
}
+ return classNames.toList()
}
}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
index 2843f46..8a6bf9a 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
@@ -345,7 +345,7 @@
}
override fun isAvailableOnDevice(): Boolean {
- return Build.VERSION.SDK_INT >= 34
+ return Build.VERSION.SDK_INT >= 34 && credentialManager != null
}
override fun onClearCredential(
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetCustomCredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/GetCustomCredentialOption.kt
index 898ae67e..39396e6b 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetCustomCredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetCustomCredentialOption.kt
@@ -30,34 +30,49 @@
* Note: The Bundle keys for [requestData] and [candidateQueryData] should not be in the form of
* `androidx.credentials.*` as they are reserved for internal use by this androidx library.
*
- * @param type the credential type determined by the credential-type-specific subclass
- * generated for custom use cases
- * @param requestData the request data in the [Bundle] format, generated for custom use cases
- * (note: bundle keys in the form of `androidx.credentials.*` and `android.credentials.*` are
- * reserved for internal library usage)
- * @param candidateQueryData the partial request data in the [Bundle] format that will be sent to
- * the provider during the initial candidate query stage, which should not contain sensitive user
- * information (note: bundle keys in the form of `androidx.credentials.*` and
- * `android.credentials.*` are reserved for internal library usage)
- * @param isSystemProviderRequired true if must only be fulfilled by a system provider and false
+ * The [typePriorityHint] bit helps decide where the credential will be displayed on the
+ * selector. It is used with more importance than signals like 'last recently used' but with less
+ * importance than other signals, such as the ordering of displayed accounts.
+ * It is expected to be one of the defined [PriorityHints] constants. By default,
+ * [GetCustomCredentialOption] will have [PriorityHints.PRIORITY_DEFAULT], [GetPasswordOption] will
+ * have [PriorityHints.PRIORITY_PASSWORD_OR_SIMILAR] and [GetPublicKeyCredentialOption] will have
+ * [PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR]. It is expected that [GetCustomCredentialOption]
+ * types will remain unchanged unless strong reasons arise and cannot ever have
+ * [PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR]. Given passkeys prevent many security threats that
+ * other credentials do not, we enforce that nothing is shown higher than
+ * passkey types in order to provide end users with the safest credentials first. See the spec
+ * [here](https://w3c.github.io/webauthn/) for more information on passkeys.
+ *
+ * @property type the credential type determined by the credential-type-specific subclass (e.g.
+ * the type for [GetPasswordOption] is [PasswordCredential.TYPE_PASSWORD_CREDENTIAL] and for
+ * [GetPublicKeyCredentialOption] is [PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL])
+ * @property requestData the request data in the [Bundle] format
+ * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to
+ * the provider during the initial candidate query stage, which will not contain sensitive user
+ * information
+ * @property isSystemProviderRequired true if must only be fulfilled by a system provider and false
* otherwise
- * @param isAutoSelectAllowed defines if a credential entry will be automatically chosen if it is
- * the only one available option, false by default
- * @param allowedProviders a set of provider service [ComponentName] allowed to receive this
+ * @property isAutoSelectAllowed whether a credential entry will be automatically chosen if it is
+ * the only one available option
+ * @property allowedProviders a set of provider service [ComponentName] allowed to receive this
* option (Note: a [SecurityException] will be thrown if it is set as non-empty but your app does
* not have android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS; for API level < 34,
* this property will not take effect and you should control the allowed provider via
* [library dependencies](https://developer.android.com/training/sign-in/passkeys#add-dependencies))
- * @throws IllegalArgumentException If [type] is empty
- * @throws NullPointerException If [requestData] or [type] is null
+ * @property typePriorityHint sets the priority of this entry, which defines how it appears in
+ * the credential selector amongst the signals used to order the entries, set to
+ * [PriorityHints.PRIORITY_DEFAULT] by default; see [PriorityHints] and
+ * [CredentialOption] for more information
*/
-open class GetCustomCredentialOption @JvmOverloads constructor(
- type: String,
+open class GetCustomCredentialOption internal constructor(
requestData: Bundle,
+ type: String,
candidateQueryData: Bundle,
isSystemProviderRequired: Boolean,
isAutoSelectAllowed: Boolean = false,
allowedProviders: Set<ComponentName> = emptySet(),
+ typePriorityHint: @PriorityHints Int =
+ CUSTOM_OPTION_PRIORITY_CATEGORY
) : CredentialOption(
type = type,
requestData = requestData,
@@ -65,9 +80,114 @@
isSystemProviderRequired = isSystemProviderRequired,
isAutoSelectAllowed = isAutoSelectAllowed,
allowedProviders = allowedProviders,
+ typePriorityHint = typePriorityHint,
+
) {
init {
require(type.isNotEmpty()) { "type should not be empty" }
+ require(typePriorityHint != PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR) {
+ "Custom types should not have passkey level priority." }
+ }
+
+ /**
+ * Allows extending custom versions of GetCredentialOptions for unique use cases.
+ *
+ * If you get a [GetCustomCredentialOption] instead of a type-safe option class such as
+ * [GetPasswordOption], [GetPublicKeyCredentialOption], etc., then you should check if
+ * you have any other library at interest that supports this custom [type] of credential option,
+ * and if so use its parsing utilities to resolve to a type-safe class within that library.
+ *
+ * Note: The Bundle keys for [requestData] and [candidateQueryData] should not be in the form of
+ * `androidx.credentials.*` as they are reserved for internal use by this androidx library.
+ *
+ * @param type the credential type determined by the credential-type-specific subclass
+ * generated for custom use cases
+ * @param requestData the request data in the [Bundle] format, generated for custom use cases
+ * (note: bundle keys in the form of `androidx.credentials.*` and `android.credentials.*` are
+ * reserved for internal library usage)
+ * @param candidateQueryData the partial request data in the [Bundle] format that will be sent to
+ * the provider during the initial candidate query stage, which should not contain sensitive user
+ * information (note: bundle keys in the form of `androidx.credentials.*` and
+ * `android.credentials.*` are reserved for internal library usage)
+ * @param isSystemProviderRequired true if must only be fulfilled by a system provider and false
+ * otherwise
+ * @param isAutoSelectAllowed defines if a credential entry will be automatically chosen if it is
+ * the only one available option, false by default
+ * @param allowedProviders a set of provider service [ComponentName] allowed to receive this
+ * option (Note: a [SecurityException] will be thrown if it is set as non-empty but your app does
+ * not have android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS; for API level < 34,
+ * this property will not take effect and you should control the allowed provider via
+ * [library dependencies](https://developer.android.com/training/sign-in/passkeys#add-dependencies))
+ * @throws IllegalArgumentException If [type] is empty
+ * @throws NullPointerException If [requestData] or [type] is null
+ */
+ @JvmOverloads constructor(
+ type: String,
+ requestData: Bundle,
+ candidateQueryData: Bundle,
+ isSystemProviderRequired: Boolean,
+ isAutoSelectAllowed: Boolean = false,
+ allowedProviders: Set<ComponentName> = emptySet(),
+ ) : this(requestData, type, candidateQueryData, isSystemProviderRequired, isAutoSelectAllowed,
+ allowedProviders)
+
+ /**
+ * Allows extending custom versions of GetCredentialOptions for unique use cases.
+ *
+ * If you get a [GetCustomCredentialOption] instead of a type-safe option class such as
+ * [GetPasswordOption], [GetPublicKeyCredentialOption], etc., then you should check if
+ * you have any other library at interest that supports this custom [type] of credential option,
+ * and if so use its parsing utilities to resolve to a type-safe class within that library.
+ *
+ * Note: The Bundle keys for [requestData] and [candidateQueryData] should not be in the form of
+ * `androidx.credentials.*` as they are reserved for internal use by this androidx library.
+ *
+ * The [typePriorityHint] bit helps decide where the credential will be displayed on the
+ * selector. It is expected that [GetCustomCredentialOption] types will remain unchanged
+ * unless strong reasons arise and cannot ever have [PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR].
+ * Given passkeys prevent many security threats that other credentials do not, we enforce that
+ * nothing is shown higher than passkey types in order to provide end users with the safest
+ * credentials first. See the spec [here](https://w3c.github.io/webauthn/) for more information
+ * on passkeys.
+ *
+ * @param type the credential type determined by the credential-type-specific subclass
+ * generated for custom use cases
+ * @param requestData the request data in the [Bundle] format, generated for custom use cases
+ * (note: bundle keys in the form of `androidx.credentials.*` and `android.credentials.*` are
+ * reserved for internal library usage)
+ * @param candidateQueryData the partial request data in the [Bundle] format that will be sent to
+ * the provider during the initial candidate query stage, which should not contain sensitive user
+ * information (note: bundle keys in the form of `androidx.credentials.*` and
+ * `android.credentials.*` are reserved for internal library usage)
+ * @param isSystemProviderRequired true if must only be fulfilled by a system provider and false
+ * otherwise
+ * @param isAutoSelectAllowed defines if a credential entry will be automatically chosen if it is
+ * the only one available option, false by default
+ * @param allowedProviders a set of provider service [ComponentName] allowed to receive this
+ * option (Note: a [SecurityException] will be thrown if it is set as non-empty but your app does
+ * not have android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS; for API level < 34,
+ * this property will not take effect and you should control the allowed provider via
+ * [library dependencies](https://developer.android.com/training/sign-in/passkeys#add-dependencies))
+ * @param typePriorityHint sets the priority of this entry, which defines how it appears in the
+ * credential selector, with less precedence than account ordering but more precedence than last
+ * used time; see [PriorityHints] and [CredentialOption] for more information
+ * @throws IllegalArgumentException If [type] is empty
+ * @throws NullPointerException If [requestData] or [type] is null
+ */
+ constructor(
+ type: String,
+ requestData: Bundle,
+ candidateQueryData: Bundle,
+ isSystemProviderRequired: Boolean,
+ isAutoSelectAllowed: Boolean = false,
+ allowedProviders: Set<ComponentName> = emptySet(),
+ typePriorityHint: @PriorityHints Int =
+ CUSTOM_OPTION_PRIORITY_CATEGORY,
+ ) : this(requestData, type, candidateQueryData, isSystemProviderRequired, isAutoSelectAllowed,
+ allowedProviders, typePriorityHint)
+
+ internal companion object {
+ internal const val CUSTOM_OPTION_PRIORITY_CATEGORY = PriorityHints.PRIORITY_DEFAULT
}
}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetPasswordOption.kt b/credentials/credentials/src/main/java/androidx/credentials/GetPasswordOption.kt
index c423f5b..4cb6614 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetPasswordOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetPasswordOption.kt
@@ -24,6 +24,10 @@
*
* @property allowedUserIds a optional set of user ids with which the credentials associated are
* requested; leave as empty if you want to request all the available user credentials
+ * @property typePriorityHint always sets the priority of this entry to
+ * [PriorityHints.PRIORITY_PASSWORD_OR_SIMILAR], which defines how it appears in the credential
+ * selector, with less precedence than account ordering but more precedence than last used time;
+ * see [PriorityHints] and [CredentialOption] for more information
*/
class GetPasswordOption private constructor(
val allowedUserIds: Set<String>,
@@ -31,13 +35,16 @@
allowedProviders: Set<ComponentName>,
requestData: Bundle,
candidateQueryData: Bundle,
+ typePriorityHint: @PriorityHints Int =
+ PASSWORD_OPTION_PRIORITY_CATEGORY,
) : CredentialOption(
type = PasswordCredential.TYPE_PASSWORD_CREDENTIAL,
requestData = requestData,
candidateQueryData = candidateQueryData,
isSystemProviderRequired = false,
isAutoSelectAllowed = isAutoSelectAllowed,
- allowedProviders,
+ allowedProviders = allowedProviders,
+ typePriorityHint = typePriorityHint,
) {
/**
@@ -69,6 +76,9 @@
internal const val BUNDLE_KEY_ALLOWED_USER_IDS =
"androidx.credentials.BUNDLE_KEY_ALLOWED_USER_IDS"
+ internal const val PASSWORD_OPTION_PRIORITY_CATEGORY =
+ PriorityHints.PRIORITY_PASSWORD_OR_SIMILAR
+
@JvmStatic
internal fun createFrom(
data: Bundle,
@@ -82,6 +92,8 @@
allowedProviders = allowedProviders,
requestData = data,
candidateQueryData = candidateQueryData,
+ typePriorityHint = data.getInt(BUNDLE_KEY_TYPE_PRIORITY_VALUE,
+ PASSWORD_OPTION_PRIORITY_CATEGORY),
)
}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt
index df95fc9..9e02e91 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt
@@ -29,6 +29,10 @@
* @property clientDataHash a clientDataHash value to sign over in place of assembling and hashing
* clientDataJSON during the signature request; meaningful only if you have set the
* [GetCredentialRequest.origin]
+ * @property typePriorityHint always sets the priority of this entry to
+ * [PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR], which defines how it appears in the credential
+ * selector, with less precedence than account ordering but more precedence than last used time;
+ * see [PriorityHints] and [CredentialOption] for more information
*/
class GetPublicKeyCredentialOption private constructor(
val requestJson: String,
@@ -36,13 +40,16 @@
allowedProviders: Set<ComponentName>,
requestData: Bundle,
candidateQueryData: Bundle,
+ typePriorityCategory: @PriorityHints Int =
+ PUBLIC_KEY_CREDENTIAL_OPTION_PRIORITY_CATEGORY,
) : CredentialOption(
type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
requestData = requestData,
candidateQueryData = candidateQueryData,
isSystemProviderRequired = false,
isAutoSelectAllowed = true,
- allowedProviders,
+ allowedProviders = allowedProviders,
+ typePriorityHint = typePriorityCategory,
) {
/**
@@ -85,6 +92,8 @@
internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION =
"androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION"
+ internal const val PUBLIC_KEY_CREDENTIAL_OPTION_PRIORITY_CATEGORY =
+ PriorityHints.PRIORITY_PASSKEY_OR_SIMILAR
@JvmStatic
internal fun toRequestDataBundle(
@@ -118,6 +127,8 @@
allowedProviders,
requestData = data,
candidateQueryData = candidateQueryData,
+ typePriorityCategory = data.getInt(BUNDLE_KEY_TYPE_PRIORITY_VALUE,
+ PUBLIC_KEY_CREDENTIAL_OPTION_PRIORITY_CATEGORY),
)
} catch (e: Exception) {
throw FrameworkClassParsingException()
diff --git a/credentials/credentials/src/main/java/androidx/credentials/PriorityHints.kt b/credentials/credentials/src/main/java/androidx/credentials/PriorityHints.kt
new file mode 100644
index 0000000..22226e5
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/PriorityHints.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials
+
+import androidx.annotation.IntDef
+import androidx.credentials.PriorityHints.Companion.PRIORITY_DEFAULT
+import androidx.credentials.PriorityHints.Companion.PRIORITY_OIDC_OR_SIMILAR
+import androidx.credentials.PriorityHints.Companion.PRIORITY_PASSKEY_OR_SIMILAR
+import androidx.credentials.PriorityHints.Companion.PRIORITY_PASSWORD_OR_SIMILAR
+
+/**
+ * For our [CredentialOption] types, this allows us to categorize the default priority hint for
+ * those entries. We expect [GetCustomCredentialOption] to utilize, rarely, these differing
+ * priorities. These are subject to change by library owners, but will always remain backwards
+ * compatible, and will always ensure relative ordering of older sets are maintained.
+ */
+@Suppress("PublicTypedef") // Custom types are able to choose amongst 3 of these
+ // values, without having to repeat the constants elsewhere
+@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(value = [PRIORITY_PASSKEY_OR_SIMILAR, PRIORITY_OIDC_OR_SIMILAR,
+ PRIORITY_PASSWORD_OR_SIMILAR, PRIORITY_DEFAULT])
+annotation class PriorityHints {
+ companion object {
+
+ // Only allowed to be set under library owner requirements
+ internal const val PRIORITY_PASSKEY_OR_SIMILAR = 100
+ // Can be set by non library owners
+ const val PRIORITY_OIDC_OR_SIMILAR = 500
+ const val PRIORITY_PASSWORD_OR_SIMILAR = 1000
+ const val PRIORITY_DEFAULT = 2000
+ }
+}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt
index 9eef85b..1f10593 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt
@@ -56,8 +56,8 @@
open val type: String,
val beginGetCredentialOption: BeginGetCredentialOption,
val entryGroupId: CharSequence,
- val affiliatedDomain: CharSequence? = null,
val isDefaultIconPreferredAsSingleProvider: Boolean,
+ val affiliatedDomain: CharSequence? = null,
) {
@RequiresApi(34)
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
index 796f64e..d9f1227 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
@@ -65,6 +65,10 @@
* @param isDefaultIconPreferredAsSingleProvider when set to true, the UI prefers to render the
* default credential type icon (see the default value of [icon]) when you are
* the only available provider; false by default
+ * @property isAutoSelectAllowedFromOption whether the [beginGetCredentialOption] request
+ * for which this entry was created allows this entry to be auto-selected
+ * @property hasDefaultIcon whether this entry was created without a custom icon and hence
+ * contains a default icon set by the library, only to be used in Android API levels >= 28
*
* @throws IllegalArgumentException If [type] or [title] are empty
*
@@ -82,18 +86,31 @@
val icon: Icon,
val lastUsedTime: Instant?,
beginGetCredentialOption: BeginGetCredentialOption,
+ isDefaultIconPreferredAsSingleProvider: Boolean,
entryGroupId: CharSequence? = title,
affiliatedDomain: CharSequence? = null,
- isDefaultIconPreferredAsSingleProvider: Boolean,
- private val autoSelectAllowedFromOption: Boolean = false,
- private val isDefaultIcon: Boolean = false,
+ autoSelectAllowedFromOption: Boolean = CredentialOption.extractAutoSelectValue(
+ beginGetCredentialOption.candidateQueryData),
+ private var isCreatedFromSlice: Boolean = false,
+ private var isDefaultIconFromSlice: Boolean = false,
) : CredentialEntry(
type,
beginGetCredentialOption,
entryGroupId ?: title,
- affiliatedDomain,
- isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider
+ isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+ affiliatedDomain = affiliatedDomain,
) {
+ val isAutoSelectAllowedFromOption = autoSelectAllowedFromOption
+
+ @get:JvmName("hasDefaultIcon")
+ val hasDefaultIcon: Boolean
+ get() {
+ if (Build.VERSION.SDK_INT >= 28) {
+ return Api28Impl.isDefaultIcon(this)
+ }
+ return false
+ }
+
init {
require(type.isNotEmpty()) { "type must not be empty" }
require(title.isNotEmpty()) { "title must not be empty" }
@@ -207,8 +224,8 @@
icon,
lastUsedTime,
beginGetCredentialOption,
- entryGroupId.ifEmpty { title },
isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+ entryGroupId.ifEmpty { title },
)
@RequiresApi(34)
@@ -225,6 +242,15 @@
private object Api28Impl {
@RestrictTo(RestrictTo.Scope.LIBRARY)
@JvmStatic
+ fun isDefaultIcon(entry: CustomCredentialEntry): Boolean {
+ if (entry.isCreatedFromSlice) {
+ return entry.isDefaultIconFromSlice
+ }
+ return entry.icon.type == Icon.TYPE_RESOURCE &&
+ entry.icon.resId == R.drawable.ic_other_sign_in
+ }
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @JvmStatic
fun toSlice(
entry: CustomCredentialEntry
): Slice {
@@ -242,7 +268,7 @@
val isDefaultIconPreferredAsSingleProvider =
entry.isDefaultIconPreferredAsSingleProvider
- val autoSelectAllowed = if (isAutoSelectAllowed == true) {
+ val autoSelectAllowed = if (isAutoSelectAllowed) {
TRUE_STRING
} else {
FALSE_STRING
@@ -297,7 +323,7 @@
)
try {
- if (icon.resId == R.drawable.ic_other_sign_in) {
+ if (entry.hasDefaultIcon) {
sliceBuilder.addInt(
/*true=*/1,
/*subType=*/null,
@@ -307,10 +333,7 @@
} catch (_: IllegalStateException) {
}
- if (CredentialOption.extractAutoSelectValue(
- beginGetCredentialOption.candidateQueryData
- )
- ) {
+ if (entry.isAutoSelectAllowedFromOption) {
sliceBuilder.addInt(
/*true=*/1,
/*subType=*/null,
@@ -397,24 +420,25 @@
return try {
CustomCredentialEntry(
- type,
- title!!,
- pendingIntent!!,
- autoSelectAllowed,
- subtitle,
- typeDisplayName,
- icon!!,
- lastUsedTime,
- BeginGetCustomCredentialOption(
+ type = type,
+ title = title!!,
+ pendingIntent = pendingIntent!!,
+ isAutoSelectAllowed = autoSelectAllowed,
+ subtitle = subtitle,
+ typeDisplayName = typeDisplayName,
+ icon = icon!!,
+ lastUsedTime = lastUsedTime,
+ beginGetCredentialOption = BeginGetCustomCredentialOption(
beginGetCredentialOptionId!!.toString(),
type,
Bundle()
),
- entryGroupId = entryGroupId,
isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+ entryGroupId = entryGroupId,
affiliatedDomain = affiliatedDomain,
autoSelectAllowedFromOption = autoSelectAllowedFromOption,
- isDefaultIcon = isDefaultIcon,
+ isCreatedFromSlice = true,
+ isDefaultIconFromSlice = isDefaultIcon,
)
} catch (e: Exception) {
Log.i(TAG, "fromSlice failed with: " + e.message)
@@ -631,8 +655,8 @@
icon!!,
lastUsedTime,
beginGetCredentialOption,
- entryGroupId = entryGroupId,
isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+ entryGroupId = entryGroupId,
)
}
}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
index 9facc3a..b78ae4e 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
@@ -59,12 +59,12 @@
* with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
* final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
* times
- * @property isAutoSelectAllowed whether this entry is allowed to be auto
- * selected if it is the only one on the UI. Note that setting this value
- * to true does not guarantee this behavior. The developer must also set this to true, and the
- * framework must determine that this is the only entry available for the user.
* @property entryGroupId an ID used for deduplication or grouping entries during display, always
* set to [username]; for more info on this id, see [CredentialEntry]
+ * @property isAutoSelectAllowedFromOption whether the [beginGetCredentialOption] request
+ * for which this entry was created allows this entry to be auto-selected
+ * @property hasDefaultIcon whether this entry was created without a custom icon and hence
+ * contains a default icon set by the library, only to be used in Android API levels >= 28
*
* @throws IllegalArgumentException If [username] is empty
*
@@ -81,18 +81,32 @@
val icon: Icon,
val isAutoSelectAllowed: Boolean,
beginGetPasswordOption: BeginGetPasswordOption,
- entryGroupId: CharSequence? = username,
isDefaultIconPreferredAsSingleProvider: Boolean,
+ entryGroupId: CharSequence? = username,
affiliatedDomain: CharSequence? = null,
- private val autoSelectAllowedFromOption: Boolean = false,
- private val isDefaultIcon: Boolean = false
+ autoSelectAllowedFromOption: Boolean = CredentialOption.extractAutoSelectValue(
+ beginGetPasswordOption.candidateQueryData),
+ private var isCreatedFromSlice: Boolean = false,
+ private var isDefaultIconFromSlice: Boolean = false
) : CredentialEntry(
PasswordCredential.TYPE_PASSWORD_CREDENTIAL,
beginGetPasswordOption,
entryGroupId ?: username,
- affiliatedDomain,
- isDefaultIconPreferredAsSingleProvider
+ isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+ affiliatedDomain = affiliatedDomain,
) {
+
+ val isAutoSelectAllowedFromOption = autoSelectAllowedFromOption
+
+ @get:JvmName("hasDefaultIcon")
+ val hasDefaultIcon: Boolean
+ get() {
+ if (Build.VERSION.SDK_INT >= 28) {
+ return Api28Impl.isDefaultIcon(this)
+ }
+ return false
+ }
+
init {
require(username.isNotEmpty()) { "username must not be empty" }
}
@@ -155,8 +169,8 @@
icon,
isAutoSelectAllowed,
beginGetPasswordOption,
- affiliatedDomain = affiliatedDomain,
isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+ affiliatedDomain = affiliatedDomain,
)
/**
@@ -201,7 +215,7 @@
displayName: CharSequence? = null,
lastUsedTime: Instant? = null,
icon: Icon = Icon.createWithResource(context, R.drawable.ic_password),
- isAutoSelectAllowed: Boolean = false,
+ isAutoSelectAllowed: Boolean = false
) : this(
username,
displayName,
@@ -230,6 +244,15 @@
private object Api28Impl {
@RestrictTo(RestrictTo.Scope.LIBRARY)
@JvmStatic
+ fun isDefaultIcon(entry: PasswordCredentialEntry): Boolean {
+ if (entry.isCreatedFromSlice) {
+ return entry.isDefaultIconFromSlice
+ }
+ return entry.icon.type == Icon.TYPE_RESOURCE &&
+ entry.icon.resId == R.drawable.ic_password
+ }
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @JvmStatic
fun toSlice(
entry: PasswordCredentialEntry
): Slice {
@@ -297,7 +320,7 @@
listOf(SLICE_HINT_IS_DEFAULT_ICON_PREFERRED)
)
try {
- if (icon.resId == R.drawable.ic_password) {
+ if (entry.hasDefaultIcon) {
sliceBuilder.addInt(
/*true=*/1,
/*subType=*/null,
@@ -307,10 +330,7 @@
} catch (_: IllegalStateException) {
}
- if (CredentialOption.extractAutoSelectValue(
- beginGetPasswordCredentialOption.candidateQueryData
- )
- ) {
+ if (entry.isAutoSelectAllowedFromOption) {
sliceBuilder.addInt(
/*true=*/1,
/*subType=*/null,
@@ -396,14 +416,14 @@
return try {
PasswordCredentialEntry(
- title!!,
- subTitle,
- typeDisplayName!!,
- pendingIntent!!,
- lastUsedTime,
- icon!!,
- autoSelectAllowed,
- BeginGetPasswordOption.createFrom(
+ username = title!!,
+ displayName = subTitle,
+ typeDisplayName = typeDisplayName!!,
+ pendingIntent = pendingIntent!!,
+ lastUsedTime = lastUsedTime,
+ icon = icon!!,
+ isAutoSelectAllowed = autoSelectAllowed,
+ beginGetPasswordOption = BeginGetPasswordOption.createFrom(
Bundle(),
beginGetPasswordOptionId!!.toString()
),
@@ -411,7 +431,8 @@
isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = affiliatedDomain,
autoSelectAllowedFromOption = autoSelectAllowedFromOption,
- isDefaultIcon = isDefaultIcon,
+ isCreatedFromSlice = true,
+ isDefaultIconFromSlice = isDefaultIcon,
)
} catch (e: Exception) {
Log.i(TAG, "fromSlice failed with: " + e.message)
@@ -619,8 +640,8 @@
icon!!,
autoSelectAllowed,
beginGetPasswordOption,
- affiliatedDomain = affiliatedDomain,
isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+ affiliatedDomain = affiliatedDomain,
)
}
}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PendingIntentHandler.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PendingIntentHandler.kt
index e6e8f55..0bec31c 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PendingIntentHandler.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PendingIntentHandler.kt
@@ -77,21 +77,25 @@
Log.i(TAG, "Request not found in pendingIntent")
return frameworkReq
}
- return ProviderCreateCredentialRequest(
- androidx.credentials.CreateCredentialRequest
- .createFrom(
- frameworkReq.type,
- frameworkReq.data,
- frameworkReq.data,
- requireSystemProvider = false,
+ return try {
+ ProviderCreateCredentialRequest(
+ androidx.credentials.CreateCredentialRequest
+ .createFrom(
+ frameworkReq.type,
+ frameworkReq.data,
+ frameworkReq.data,
+ requireSystemProvider = false,
+ frameworkReq.callingAppInfo.origin
+ ),
+ CallingAppInfo(
+ frameworkReq.callingAppInfo.packageName,
+ frameworkReq.callingAppInfo.signingInfo,
frameworkReq.callingAppInfo.origin
- ) ?: return null,
- CallingAppInfo(
- frameworkReq.callingAppInfo.packageName,
- frameworkReq.callingAppInfo.signingInfo,
- frameworkReq.callingAppInfo.origin
+ )
)
- )
+ } catch (e: IllegalArgumentException) {
+ return null
+ }
}
/**
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
index d70ca3d..b41375c 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
@@ -59,15 +59,15 @@
* with flag [PendingIntent.FLAG_MUTABLE] to allow the Android system to attach the
* final request, and NOT with flag [PendingIntent.FLAG_ONE_SHOT] as it can be invoked multiple
* times
- * @property isAutoSelectAllowed whether this entry is allowed to be auto
- * selected if it is the only one on the UI. Note that setting this value
- * to true does not guarantee this behavior. The developer must also set this to true, and the
- * framework must determine that this is the only entry available for the user.
* @property affiliatedDomain the user visible affiliated domain, a CharSequence
* representation of a web domain or an app package name that the given credential in this
* entry is associated with when it is different from the requesting entity, default null
* @property entryGroupId an ID used for deduplication or grouping entries during display, always
* set to [username]; for more info on this id, see [CredentialEntry]
+ * @property isAutoSelectAllowedFromOption whether the [beginGetCredentialOption] request
+ * for which this entry was created allows this entry to be auto-selected
+ * @property hasDefaultIcon whether this entry was created without a custom icon and hence
+ * contains a default icon set by the library, only to be used in Android API levels >= 28
*
* @throws IllegalArgumentException If [username] is empty
*
@@ -83,18 +83,31 @@
val lastUsedTime: Instant?,
val isAutoSelectAllowed: Boolean,
beginGetPublicKeyCredentialOption: BeginGetPublicKeyCredentialOption,
+ isDefaultIconPreferredAsSingleProvider: Boolean,
entryGroupId: CharSequence? = username,
affiliatedDomain: CharSequence? = null,
- isDefaultIconPreferredAsSingleProvider: Boolean,
- private val autoSelectAllowedFromOption: Boolean = false,
- private val isDefaultIcon: Boolean = false
+ autoSelectAllowedFromOption: Boolean = CredentialOption.extractAutoSelectValue(
+ beginGetPublicKeyCredentialOption.candidateQueryData
+ ),
+ private val isCreatedFromSlice: Boolean = false,
+ private val isDefaultIconFromSlice: Boolean = false,
) : CredentialEntry(
PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
beginGetPublicKeyCredentialOption,
entryGroupId ?: username,
- affiliatedDomain,
- isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider
+ isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
+ affiliatedDomain = affiliatedDomain,
) {
+ val isAutoSelectAllowedFromOption = autoSelectAllowedFromOption
+
+ @get:JvmName("hasDefaultIcon")
+ val hasDefaultIcon: Boolean
+ get() {
+ if (Build.VERSION.SDK_INT >= 28) {
+ return Api28Impl.isDefaultIcon(this)
+ }
+ return false
+ }
init {
require(username.isNotEmpty()) { "username must not be empty" }
@@ -226,6 +239,16 @@
private object Api28Impl {
@RestrictTo(RestrictTo.Scope.LIBRARY)
@JvmStatic
+ fun isDefaultIcon(entry: PublicKeyCredentialEntry): Boolean {
+ if (entry.isCreatedFromSlice) {
+ return entry.isDefaultIconFromSlice
+ }
+ return entry.icon.type == Icon.TYPE_RESOURCE &&
+ entry.icon.resId == R.drawable.ic_passkey
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @JvmStatic
fun toSlice(
entry: PublicKeyCredentialEntry
): Slice {
@@ -296,7 +319,7 @@
listOf(SLICE_HINT_IS_DEFAULT_ICON_PREFERRED)
)
try {
- if (icon.resId == R.drawable.ic_passkey) {
+ if (entry.hasDefaultIcon) {
sliceBuilder.addInt(
/*true=*/1,
/*subType=*/null,
@@ -306,10 +329,7 @@
} catch (_: IllegalStateException) {
}
- if (CredentialOption.extractAutoSelectValue(
- beginGetPublicKeyCredentialOption.candidateQueryData
- )
- ) {
+ if (entry.isAutoSelectAllowedFromOption) {
sliceBuilder.addInt(
/*true=*/1,
/*subType=*/null,
@@ -394,22 +414,24 @@
return try {
PublicKeyCredentialEntry(
- title!!,
- subtitle,
- typeDisplayName!!,
- pendingIntent!!,
- icon!!,
- lastUsedTime,
- autoSelectAllowed,
- BeginGetPublicKeyCredentialOption.createFromEntrySlice(
- Bundle(),
- beginGetPublicKeyCredentialOptionId!!.toString()
- ),
+ username = title!!,
+ displayName = subtitle,
+ typeDisplayName = typeDisplayName!!,
+ pendingIntent = pendingIntent!!,
+ icon = icon!!,
+ lastUsedTime = lastUsedTime,
+ isAutoSelectAllowed = autoSelectAllowed,
+ beginGetPublicKeyCredentialOption = BeginGetPublicKeyCredentialOption
+ .createFromEntrySlice(
+ Bundle(),
+ beginGetPublicKeyCredentialOptionId!!.toString(),
+ ),
entryGroupId = entryGroupId,
isDefaultIconPreferredAsSingleProvider = isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = affiliatedDomain,
autoSelectAllowedFromOption = autoSelectAllowedFromOption,
- isDefaultIcon = isDefaultIcon,
+ isCreatedFromSlice = true,
+ isDefaultIconFromSlice = isDefaultIcon,
)
} catch (e: Exception) {
Log.i(TAG, "fromSlice failed with: " + e.message)
diff --git a/credentials/credentials/src/main/res/drawable/ic_passkey.xml b/credentials/credentials/src/main/res/drawable/ic_passkey.xml
index 0626a8f..751b0b4 100644
--- a/credentials/credentials/src/main/res/drawable/ic_passkey.xml
+++ b/credentials/credentials/src/main/res/drawable/ic_passkey.xml
@@ -14,16 +14,12 @@
limitations under the License.
-->
-<vector
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- tools:ignore="VectorPath"
- android:alpha="0.8"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path android:fillColor="#4C463C" android:fillType="evenOdd" android:pathData="M22.18,14.09C22.18,15.364 21.408,16.459 20.306,16.931L21.247,17.872L20.219,18.9L21.247,19.928L19.068,22.107L18.099,21.138L18.099,17.017C16.878,16.604 16,15.45 16,14.09C16,12.383 17.383,11 19.09,11C20.796,11 22.18,12.383 22.18,14.09ZM20.692,14.091C20.692,14.976 19.975,15.693 19.09,15.693C18.205,15.693 17.488,14.976 17.488,14.091C17.488,13.206 18.205,12.488 19.09,12.488C19.975,12.488 20.692,13.206 20.692,14.091Z"/>
- <path android:fillColor="#4C463C" android:pathData="M14.978,8.476C14.978,10.865 13.041,12.802 10.652,12.802C8.263,12.802 6.326,10.865 6.326,8.476C6.326,6.087 8.263,4.15 10.652,4.15C13.041,4.15 14.978,6.087 14.978,8.476Z"/>
- <path android:fillColor="#4C463C" android:pathData="M2,19.263C2,16.39 7.762,14.937 10.652,14.937C11.782,14.937 13.353,15.16 14.845,15.602C15.177,16.491 15.804,17.236 16.607,17.717V21.454H2V19.263Z"/>
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M120,800L120,688Q120,654 137.5,625.5Q155,597 184,582Q246,551 310,535.5Q374,520 440,520Q460,520 480,521.5Q500,523 520,526Q516,584 541,635.5Q566,687 614,720L614,800L120,800ZM760,920L700,860L700,674Q656,661 628,624.5Q600,588 600,540Q600,482 641,441Q682,400 740,400Q798,400 839,441Q880,482 880,540Q880,585 854.5,620Q829,655 790,670L840,720L780,780L840,840L760,920ZM440,480Q374,480 327,433Q280,386 280,320Q280,254 327,207Q374,160 440,160Q506,160 553,207Q600,254 600,320Q600,386 553,433Q506,480 440,480ZM740,560Q757,560 768.5,548.5Q780,537 780,520Q780,503 768.5,491.5Q757,480 740,480Q723,480 711.5,491.5Q700,503 700,520Q700,537 711.5,548.5Q723,560 740,560Z"/>
</vector>
\ No newline at end of file
diff --git a/datastore/datastore-benchmark/src/androidTest/java/androidx/datastore/core/SingleProcessDatastoreTest.kt b/datastore/datastore-benchmark/src/androidTest/java/androidx/datastore/core/SingleProcessDatastoreTest.kt
index 7ed76dd..9fd5fa9 100644
--- a/datastore/datastore-benchmark/src/androidTest/java/androidx/datastore/core/SingleProcessDatastoreTest.kt
+++ b/datastore/datastore-benchmark/src/androidTest/java/androidx/datastore/core/SingleProcessDatastoreTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.LargeTest
import androidx.test.filters.MediumTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestScope
@@ -55,12 +56,17 @@
@LargeTest
fun create() = testScope.runTest {
benchmark.measureRepeated {
+ // create a new scope for each instance and cancel it to avoid hoarding memory
+ val newScope = runWithTimingDisabled {
+ TestScope(UnconfinedTestDispatcher())
+ }
val testFile = tmp.newFile()
val store = DataStoreFactory.create(
serializer = TestingSerializer(),
- scope = dataStoreScope
+ scope = newScope
) { testFile }
runWithTimingDisabled {
+ newScope.cancel()
Assert.assertNotNull(store)
}
}
@@ -89,7 +95,7 @@
@Test
@MediumTest
- fun update() = testScope.runTest {
+ fun update_withoutValueChange() = testScope.runTest {
val scope = this
val testFile = tmp.newFile()
val store = DataStoreFactory.create(
@@ -107,4 +113,27 @@
}
}
}
+
+ @Test
+ @MediumTest
+ fun update_withValueChange() = testScope.runTest {
+ val scope = this
+ val testFile = tmp.newFile()
+ val store = DataStoreFactory.create(
+ serializer = TestingSerializer(),
+ scope = dataStoreScope
+ ) { testFile }
+ var counter = 0
+ benchmark.measureRepeated {
+ runBlocking(scope.coroutineContext) {
+ val newValue = (++ counter).toByte()
+ store.updateData { newValue }
+ val data = store.data.first()
+ runWithTimingDisabled {
+ val exp: Byte = newValue
+ Assert.assertEquals(exp, data)
+ }
+ }
+ }
+ }
}
diff --git a/datastore/datastore-core-okio/api/1.1.0-beta02.txt b/datastore/datastore-core-okio/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..53d375d
--- /dev/null
+++ b/datastore/datastore-core-okio/api/1.1.0-beta02.txt
@@ -0,0 +1,21 @@
+// Signature format: 4.0
+package androidx.datastore.core.okio {
+
+ public interface OkioSerializer<T> {
+ method public T getDefaultValue();
+ method public suspend Object? readFrom(okio.BufferedSource source, kotlin.coroutines.Continuation<? super T>);
+ method public suspend Object? writeTo(T t, okio.BufferedSink sink, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ property public abstract T defaultValue;
+ }
+
+ public final class OkioStorage<T> implements androidx.datastore.core.Storage<T> {
+ ctor public OkioStorage(okio.FileSystem fileSystem, androidx.datastore.core.okio.OkioSerializer<T> serializer, optional kotlin.jvm.functions.Function2<? super okio.Path,? super okio.FileSystem,? extends androidx.datastore.core.InterProcessCoordinator> coordinatorProducer, kotlin.jvm.functions.Function0<okio.Path> producePath);
+ method public androidx.datastore.core.StorageConnection<T> createConnection();
+ }
+
+ public final class OkioStorageKt {
+ method public static androidx.datastore.core.InterProcessCoordinator createSingleProcessCoordinator(okio.Path path);
+ }
+
+}
+
diff --git a/datastore/datastore-core-okio/api/restricted_1.1.0-beta02.txt b/datastore/datastore-core-okio/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..53d375d
--- /dev/null
+++ b/datastore/datastore-core-okio/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,21 @@
+// Signature format: 4.0
+package androidx.datastore.core.okio {
+
+ public interface OkioSerializer<T> {
+ method public T getDefaultValue();
+ method public suspend Object? readFrom(okio.BufferedSource source, kotlin.coroutines.Continuation<? super T>);
+ method public suspend Object? writeTo(T t, okio.BufferedSink sink, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ property public abstract T defaultValue;
+ }
+
+ public final class OkioStorage<T> implements androidx.datastore.core.Storage<T> {
+ ctor public OkioStorage(okio.FileSystem fileSystem, androidx.datastore.core.okio.OkioSerializer<T> serializer, optional kotlin.jvm.functions.Function2<? super okio.Path,? super okio.FileSystem,? extends androidx.datastore.core.InterProcessCoordinator> coordinatorProducer, kotlin.jvm.functions.Function0<okio.Path> producePath);
+ method public androidx.datastore.core.StorageConnection<T> createConnection();
+ }
+
+ public final class OkioStorageKt {
+ method public static androidx.datastore.core.InterProcessCoordinator createSingleProcessCoordinator(okio.Path path);
+ }
+
+}
+
diff --git a/datastore/datastore-core-okio/build.gradle b/datastore/datastore-core-okio/build.gradle
index 1c3b505..4e0beb0 100644
--- a/datastore/datastore-core-okio/build.gradle
+++ b/datastore/datastore-core-okio/build.gradle
@@ -21,18 +21,15 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
+
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
id("AndroidXPlugin")
}
-def enableNative = KmpPlatformsKt.enableNative(project)
-
androidXMultiplatform {
jvm()
mac()
@@ -80,11 +77,9 @@
}
}
- if (enableNative) {
- nativeMain {
- dependencies {
- implementation(libs.atomicFu)
- }
+ nativeMain {
+ dependencies {
+ implementation(libs.atomicFu)
}
}
diff --git a/datastore/datastore-core/api/1.1.0-beta02.txt b/datastore/datastore-core/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..2700ff7
--- /dev/null
+++ b/datastore/datastore-core/api/1.1.0-beta02.txt
@@ -0,0 +1,112 @@
+// Signature format: 4.0
+package androidx.datastore.core {
+
+ public interface Closeable {
+ method public void close();
+ }
+
+ public final class CloseableKt {
+ method public static inline <T extends androidx.datastore.core.Closeable, R> R use(T, kotlin.jvm.functions.Function1<? super T,? extends R> block);
+ }
+
+ public final class CorruptionException extends java.io.IOException {
+ ctor public CorruptionException(String message, optional Throwable? cause);
+ }
+
+ public interface DataMigration<T> {
+ method public suspend Object? cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public suspend Object? migrate(T currentData, kotlin.coroutines.Continuation<? super T>);
+ method public suspend Object? shouldMigrate(T currentData, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+ }
+
+ public interface DataStore<T> {
+ method public kotlinx.coroutines.flow.Flow<T> getData();
+ method public suspend Object? updateData(kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> transform, kotlin.coroutines.Continuation<? super T>);
+ property public abstract kotlinx.coroutines.flow.Flow<T> data;
+ }
+
+ public final class DataStoreFactory {
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, optional kotlinx.coroutines.CoroutineScope scope, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Storage<T> storage, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, optional kotlinx.coroutines.CoroutineScope scope);
+ field public static final androidx.datastore.core.DataStoreFactory INSTANCE;
+ }
+
+ public final class FileStorage<T> implements androidx.datastore.core.Storage<T> {
+ ctor public FileStorage(androidx.datastore.core.Serializer<T> serializer, optional kotlin.jvm.functions.Function1<? super java.io.File,? extends androidx.datastore.core.InterProcessCoordinator> coordinatorProducer, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public androidx.datastore.core.StorageConnection<T> createConnection();
+ }
+
+ public interface InterProcessCoordinator {
+ method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getUpdateNotifications();
+ method public suspend Object? getVersion(kotlin.coroutines.Continuation<? super java.lang.Integer>);
+ method public suspend Object? incrementAndGetVersion(kotlin.coroutines.Continuation<? super java.lang.Integer>);
+ method public suspend <T> Object? lock(kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
+ method public suspend <T> Object? tryLock(kotlin.jvm.functions.Function2<? super java.lang.Boolean,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
+ property public abstract kotlinx.coroutines.flow.Flow<kotlin.Unit> updateNotifications;
+ }
+
+ public final class InterProcessCoordinator_jvmKt {
+ method public static androidx.datastore.core.InterProcessCoordinator createSingleProcessCoordinator(java.io.File file);
+ }
+
+ public final class MultiProcessCoordinatorKt {
+ method public static androidx.datastore.core.InterProcessCoordinator createMultiProcessCoordinator(kotlin.coroutines.CoroutineContext context, java.io.File file);
+ }
+
+ public final class MultiProcessDataStoreFactory {
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, optional kotlinx.coroutines.CoroutineScope scope, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Storage<T> storage);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Storage<T> storage, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Storage<T> storage, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Storage<T> storage, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, optional kotlinx.coroutines.CoroutineScope scope);
+ field public static final androidx.datastore.core.MultiProcessDataStoreFactory INSTANCE;
+ }
+
+ public interface ReadScope<T> extends androidx.datastore.core.Closeable {
+ method public suspend Object? readData(kotlin.coroutines.Continuation<? super T>);
+ }
+
+ public interface Serializer<T> {
+ method public T getDefaultValue();
+ method public suspend Object? readFrom(java.io.InputStream input, kotlin.coroutines.Continuation<? super T>);
+ method public suspend Object? writeTo(T t, java.io.OutputStream output, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ property public abstract T defaultValue;
+ }
+
+ public interface Storage<T> {
+ method public androidx.datastore.core.StorageConnection<T> createConnection();
+ }
+
+ public interface StorageConnection<T> extends androidx.datastore.core.Closeable {
+ method public androidx.datastore.core.InterProcessCoordinator getCoordinator();
+ method public suspend <R> Object? readScope(kotlin.jvm.functions.Function3<? super androidx.datastore.core.ReadScope<T>,? super java.lang.Boolean,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+ method public suspend Object? writeScope(kotlin.jvm.functions.Function2<? super androidx.datastore.core.WriteScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ property public abstract androidx.datastore.core.InterProcessCoordinator coordinator;
+ }
+
+ public final class StorageConnectionKt {
+ method public static suspend <T> Object? readData(androidx.datastore.core.StorageConnection<T>, kotlin.coroutines.Continuation<? super T>);
+ method public static suspend <T> Object? writeData(androidx.datastore.core.StorageConnection<T>, T value, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ }
+
+ public interface WriteScope<T> extends androidx.datastore.core.ReadScope<T> {
+ method public suspend Object? writeData(T value, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ }
+
+}
+
+package androidx.datastore.core.handlers {
+
+ public final class ReplaceFileCorruptionHandler<T> {
+ ctor public ReplaceFileCorruptionHandler(kotlin.jvm.functions.Function1<? super androidx.datastore.core.CorruptionException,? extends T> produceNewData);
+ method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public suspend Object? handleCorruption(androidx.datastore.core.CorruptionException ex, kotlin.coroutines.Continuation<? super T>) throws java.io.IOException;
+ }
+
+}
+
diff --git a/datastore/datastore-core/api/current.ignore b/datastore/datastore-core/api/current.ignore
deleted file mode 100644
index 9d477c1..0000000
--- a/datastore/datastore-core/api/current.ignore
+++ /dev/null
@@ -1,23 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.datastore.core.DataMigration#migrate(T, kotlin.coroutines.Continuation<? super T>) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.core.DataMigration.migrate(T currentData, kotlin.coroutines.Continuation<? super T> arg2)
-InvalidNullConversion: androidx.datastore.core.DataMigration#shouldMigrate(T, kotlin.coroutines.Continuation<? super java.lang.Boolean>) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.core.DataMigration.shouldMigrate(T currentData, kotlin.coroutines.Continuation<? super java.lang.Boolean> arg2)
-InvalidNullConversion: androidx.datastore.core.Serializer#writeTo(T, java.io.OutputStream, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter t in androidx.datastore.core.Serializer.writeTo(T t, java.io.OutputStream output, kotlin.coroutines.Continuation<? super kotlin.Unit> arg3)
-
-
-ParameterNameChange: androidx.datastore.core.DataMigration#cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
- Attempted to remove parameter name from parameter arg1 in androidx.datastore.core.DataMigration.cleanUp
-ParameterNameChange: androidx.datastore.core.DataMigration#migrate(T, kotlin.coroutines.Continuation<? super T>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.core.DataMigration.migrate
-ParameterNameChange: androidx.datastore.core.DataMigration#shouldMigrate(T, kotlin.coroutines.Continuation<? super java.lang.Boolean>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.core.DataMigration.shouldMigrate
-ParameterNameChange: androidx.datastore.core.DataStore#updateData(kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.core.DataStore.updateData
-ParameterNameChange: androidx.datastore.core.Serializer#readFrom(java.io.InputStream, kotlin.coroutines.Continuation<? super T>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.core.Serializer.readFrom
-ParameterNameChange: androidx.datastore.core.Serializer#writeTo(T, java.io.OutputStream, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
- Attempted to remove parameter name from parameter arg3 in androidx.datastore.core.Serializer.writeTo
-ParameterNameChange: androidx.datastore.core.handlers.ReplaceFileCorruptionHandler#handleCorruption(androidx.datastore.core.CorruptionException, kotlin.coroutines.Continuation<? super T>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.core.handlers.ReplaceFileCorruptionHandler.handleCorruption
diff --git a/datastore/datastore-core/api/res-1.1.0-beta02.txt b/datastore/datastore-core/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/datastore/datastore-core/api/res-1.1.0-beta02.txt
diff --git a/datastore/datastore-core/api/restricted_1.1.0-beta02.txt b/datastore/datastore-core/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..2700ff7
--- /dev/null
+++ b/datastore/datastore-core/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,112 @@
+// Signature format: 4.0
+package androidx.datastore.core {
+
+ public interface Closeable {
+ method public void close();
+ }
+
+ public final class CloseableKt {
+ method public static inline <T extends androidx.datastore.core.Closeable, R> R use(T, kotlin.jvm.functions.Function1<? super T,? extends R> block);
+ }
+
+ public final class CorruptionException extends java.io.IOException {
+ ctor public CorruptionException(String message, optional Throwable? cause);
+ }
+
+ public interface DataMigration<T> {
+ method public suspend Object? cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public suspend Object? migrate(T currentData, kotlin.coroutines.Continuation<? super T>);
+ method public suspend Object? shouldMigrate(T currentData, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+ }
+
+ public interface DataStore<T> {
+ method public kotlinx.coroutines.flow.Flow<T> getData();
+ method public suspend Object? updateData(kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> transform, kotlin.coroutines.Continuation<? super T>);
+ property public abstract kotlinx.coroutines.flow.Flow<T> data;
+ }
+
+ public final class DataStoreFactory {
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, optional kotlinx.coroutines.CoroutineScope scope, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Storage<T> storage, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, optional kotlinx.coroutines.CoroutineScope scope);
+ field public static final androidx.datastore.core.DataStoreFactory INSTANCE;
+ }
+
+ public final class FileStorage<T> implements androidx.datastore.core.Storage<T> {
+ ctor public FileStorage(androidx.datastore.core.Serializer<T> serializer, optional kotlin.jvm.functions.Function1<? super java.io.File,? extends androidx.datastore.core.InterProcessCoordinator> coordinatorProducer, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public androidx.datastore.core.StorageConnection<T> createConnection();
+ }
+
+ public interface InterProcessCoordinator {
+ method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getUpdateNotifications();
+ method public suspend Object? getVersion(kotlin.coroutines.Continuation<? super java.lang.Integer>);
+ method public suspend Object? incrementAndGetVersion(kotlin.coroutines.Continuation<? super java.lang.Integer>);
+ method public suspend <T> Object? lock(kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
+ method public suspend <T> Object? tryLock(kotlin.jvm.functions.Function2<? super java.lang.Boolean,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
+ property public abstract kotlinx.coroutines.flow.Flow<kotlin.Unit> updateNotifications;
+ }
+
+ public final class InterProcessCoordinator_jvmKt {
+ method public static androidx.datastore.core.InterProcessCoordinator createSingleProcessCoordinator(java.io.File file);
+ }
+
+ public final class MultiProcessCoordinatorKt {
+ method public static androidx.datastore.core.InterProcessCoordinator createMultiProcessCoordinator(kotlin.coroutines.CoroutineContext context, java.io.File file);
+ }
+
+ public final class MultiProcessDataStoreFactory {
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, optional kotlinx.coroutines.CoroutineScope scope, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Serializer<T> serializer, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Storage<T> storage);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Storage<T> storage, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Storage<T> storage, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations);
+ method public <T> androidx.datastore.core.DataStore<T> create(androidx.datastore.core.Storage<T> storage, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<T>> migrations, optional kotlinx.coroutines.CoroutineScope scope);
+ field public static final androidx.datastore.core.MultiProcessDataStoreFactory INSTANCE;
+ }
+
+ public interface ReadScope<T> extends androidx.datastore.core.Closeable {
+ method public suspend Object? readData(kotlin.coroutines.Continuation<? super T>);
+ }
+
+ public interface Serializer<T> {
+ method public T getDefaultValue();
+ method public suspend Object? readFrom(java.io.InputStream input, kotlin.coroutines.Continuation<? super T>);
+ method public suspend Object? writeTo(T t, java.io.OutputStream output, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ property public abstract T defaultValue;
+ }
+
+ public interface Storage<T> {
+ method public androidx.datastore.core.StorageConnection<T> createConnection();
+ }
+
+ public interface StorageConnection<T> extends androidx.datastore.core.Closeable {
+ method public androidx.datastore.core.InterProcessCoordinator getCoordinator();
+ method public suspend <R> Object? readScope(kotlin.jvm.functions.Function3<? super androidx.datastore.core.ReadScope<T>,? super java.lang.Boolean,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+ method public suspend Object? writeScope(kotlin.jvm.functions.Function2<? super androidx.datastore.core.WriteScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ property public abstract androidx.datastore.core.InterProcessCoordinator coordinator;
+ }
+
+ public final class StorageConnectionKt {
+ method public static suspend <T> Object? readData(androidx.datastore.core.StorageConnection<T>, kotlin.coroutines.Continuation<? super T>);
+ method public static suspend <T> Object? writeData(androidx.datastore.core.StorageConnection<T>, T value, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ }
+
+ public interface WriteScope<T> extends androidx.datastore.core.ReadScope<T> {
+ method public suspend Object? writeData(T value, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ }
+
+}
+
+package androidx.datastore.core.handlers {
+
+ public final class ReplaceFileCorruptionHandler<T> {
+ ctor public ReplaceFileCorruptionHandler(kotlin.jvm.functions.Function1<? super androidx.datastore.core.CorruptionException,? extends T> produceNewData);
+ method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public suspend Object? handleCorruption(androidx.datastore.core.CorruptionException ex, kotlin.coroutines.Continuation<? super T>) throws java.io.IOException;
+ }
+
+}
+
diff --git a/datastore/datastore-core/api/restricted_current.ignore b/datastore/datastore-core/api/restricted_current.ignore
deleted file mode 100644
index 9d477c1..0000000
--- a/datastore/datastore-core/api/restricted_current.ignore
+++ /dev/null
@@ -1,23 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.datastore.core.DataMigration#migrate(T, kotlin.coroutines.Continuation<? super T>) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.core.DataMigration.migrate(T currentData, kotlin.coroutines.Continuation<? super T> arg2)
-InvalidNullConversion: androidx.datastore.core.DataMigration#shouldMigrate(T, kotlin.coroutines.Continuation<? super java.lang.Boolean>) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.core.DataMigration.shouldMigrate(T currentData, kotlin.coroutines.Continuation<? super java.lang.Boolean> arg2)
-InvalidNullConversion: androidx.datastore.core.Serializer#writeTo(T, java.io.OutputStream, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter t in androidx.datastore.core.Serializer.writeTo(T t, java.io.OutputStream output, kotlin.coroutines.Continuation<? super kotlin.Unit> arg3)
-
-
-ParameterNameChange: androidx.datastore.core.DataMigration#cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
- Attempted to remove parameter name from parameter arg1 in androidx.datastore.core.DataMigration.cleanUp
-ParameterNameChange: androidx.datastore.core.DataMigration#migrate(T, kotlin.coroutines.Continuation<? super T>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.core.DataMigration.migrate
-ParameterNameChange: androidx.datastore.core.DataMigration#shouldMigrate(T, kotlin.coroutines.Continuation<? super java.lang.Boolean>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.core.DataMigration.shouldMigrate
-ParameterNameChange: androidx.datastore.core.DataStore#updateData(kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.core.DataStore.updateData
-ParameterNameChange: androidx.datastore.core.Serializer#readFrom(java.io.InputStream, kotlin.coroutines.Continuation<? super T>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.core.Serializer.readFrom
-ParameterNameChange: androidx.datastore.core.Serializer#writeTo(T, java.io.OutputStream, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
- Attempted to remove parameter name from parameter arg3 in androidx.datastore.core.Serializer.writeTo
-ParameterNameChange: androidx.datastore.core.handlers.ReplaceFileCorruptionHandler#handleCorruption(androidx.datastore.core.CorruptionException, kotlin.coroutines.Continuation<? super T>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.core.handlers.ReplaceFileCorruptionHandler.handleCorruption
diff --git a/datastore/datastore-core/build.gradle b/datastore/datastore-core/build.gradle
index 16e0319..3891e85 100644
--- a/datastore/datastore-core/build.gradle
+++ b/datastore/datastore-core/build.gradle
@@ -21,12 +21,9 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
-import com.google.protobuf.gradle.ProtobufExtract
plugins {
id("AndroidXPlugin")
@@ -35,8 +32,6 @@
id ("kotlin-parcelize")
}
-def enableNative = KmpPlatformsKt.enableNative(project)
-
android {
externalNativeBuild {
cmake {
@@ -152,16 +147,14 @@
}
}
- if (enableNative) {
- nativeMain {
- dependsOn(commonMain)
- dependencies {
- implementation(libs.atomicFu)
- }
+ nativeMain {
+ dependsOn(commonMain)
+ dependencies {
+ implementation(libs.atomicFu)
}
- nativeTest {
- dependsOn(commonTest)
- }
+ }
+ nativeTest {
+ dependsOn(commonTest)
}
targets.all { target ->
diff --git a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessTest.kt
index 24bd9db..9ecbb53 100644
--- a/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessTest.kt
+++ b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessTest.kt
@@ -54,6 +54,7 @@
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
@@ -602,6 +603,7 @@
store.data.take(8).toList(collectedBytes)
}
+ runCurrent()
repeat(7) {
store.updateData { it.inc() }
}
@@ -626,6 +628,7 @@
flowOf8.toList(bytesFromSecondCollect)
}
+ runCurrent()
repeat(7) {
store.updateData { it.inc() }
}
@@ -659,6 +662,7 @@
flowOf8.take(8).toList(collectedBytes)
}
+ runCurrent()
repeat(7) {
store.updateData { it.inc() }
}
@@ -685,6 +689,7 @@
}
}
+ runCurrent()
repeat(15) {
store.updateData { it.inc() }
}
diff --git a/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt b/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt
index da2b8fd..9bc16b5 100644
--- a/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt
+++ b/datastore/datastore-core/src/commonMain/kotlin/androidx/datastore/core/DataStoreImpl.kt
@@ -22,18 +22,25 @@
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
+import kotlin.time.Duration
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
+import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.completeWith
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.WhileSubscribed
+import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
@@ -57,7 +64,36 @@
private val scope: CoroutineScope = CoroutineScope(ioDispatcher() + SupervisorJob())
) : DataStore<T> {
- override val data: Flow<T> = flow {
+ /**
+ * Shared flow responsible for observing [InterProcessCoordinator] for file changes.
+ * Each downstream [data] flow collects on this [kotlinx.coroutines.flow.SharedFlow] to ensure
+ * we observe the [InterProcessCoordinator] when there is an active collection on the [data].
+ */
+ private val updateCollection = flow<Unit> {
+ // deferring 1 flow so we can create coordinator lazily just to match existing behavior.
+ // also wait for initialization to complete before watching update events.
+ readAndInit.awaitComplete()
+ coordinator.updateNotifications.conflate().collect {
+ val currentState = inMemoryCache.currentState
+ if (currentState !is Final) {
+ // update triggered reads should always wait for lock
+ readDataAndUpdateCache(requireLock = true)
+ }
+ }
+ }.shareIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(
+ stopTimeout = Duration.ZERO,
+ replayExpiration = Duration.ZERO
+ ),
+ replay = 0
+ )
+
+ /**
+ * The actual values of DataStore. This is exposed in the API via [data] to be able to combine
+ * its lifetime with IPC update collection ([updateCollection]).
+ */
+ private val internalDataFlow: Flow<T> = flow {
/**
* If downstream flow is UnInitialized, no data has been read yet, we need to trigger a new
* read then start emitting values once we have seen a new value (or exception).
@@ -106,16 +142,35 @@
)
}
+ override val data: Flow<T> = channelFlow {
+ val updateCollector = launch(start = CoroutineStart.LAZY) {
+ updateCollection.collect {
+ // collect it infinitely so it keeps running as long as the data flow is active.
+ }
+ }
+ internalDataFlow
+ .onStart { updateCollector.start() }
+ .onCompletion { updateCollector.cancel() }
+ .collect {
+ send(it)
+ }
+ }
+
override suspend fun updateData(transform: suspend (t: T) -> T): T {
- val ack = CompletableDeferred<T>()
- val currentDownStreamFlowState = inMemoryCache.currentState
-
- val updateMsg =
- Message.Update(transform, ack, currentDownStreamFlowState, coroutineContext)
-
- writeActor.offer(updateMsg)
-
- return ack.await()
+ val parentContextElement = coroutineContext[UpdatingDataContextElement.Companion.Key]
+ parentContextElement?.checkNotUpdating(this)
+ val childContextElement = UpdatingDataContextElement(
+ parent = parentContextElement,
+ instance = this
+ )
+ return withContext(childContextElement) {
+ val ack = CompletableDeferred<T>()
+ val currentDownStreamFlowState = inMemoryCache.currentState
+ val updateMsg =
+ Message.Update(transform, ack, currentDownStreamFlowState, coroutineContext)
+ writeActor.offer(updateMsg)
+ ack.await()
+ }
}
// cache is only set by the reads who have file lock, so cache always has stable data
@@ -123,30 +178,26 @@
private val readAndInit = InitDataStore(initTasksList)
- private lateinit var updateCollector: Job
-
// TODO(b/269772127): make this private after we allow multiple instances of DataStore on the
// same file
- internal val storageConnection: StorageConnection<T> by lazy {
+ private val storageConnectionDelegate = lazy {
storage.createConnection()
}
+ internal val storageConnection by storageConnectionDelegate
private val coordinator: InterProcessCoordinator by lazy { storageConnection.coordinator }
private val writeActor = SimpleActor<Message.Update<T>>(
scope = scope,
onComplete = {
- // TODO(b/267792241): remove it if updateCollector is better scoped
- // no more reads so stop listening to file changes
- if (::updateCollector.isInitialized) {
- updateCollector.cancel()
- }
+ // We expect it to always be non-null but we will leave the alternative as a no-op
+ // just in case.
it?.let {
inMemoryCache.tryUpdate(Final(it))
}
- // We expect it to always be non-null but we will leave the alternative as a no-op
- // just in case.
-
- storageConnection.close()
+ // don't try to close storage connection if it was not created in the first place.
+ if (storageConnectionDelegate.isInitialized()) {
+ storageConnection.close()
+ }
},
onUndeliveredElement = { msg, ex ->
msg.ack.completeExceptionally(
@@ -379,17 +430,6 @@
}
}
inMemoryCache.tryUpdate(initData)
- if (!::updateCollector.isInitialized) {
- updateCollector = scope.launch {
- coordinator.updateNotifications.conflate().collect {
- val currentState = inMemoryCache.currentState
- if (currentState !is Final) {
- // update triggered reads should always wait for lock
- readDataAndUpdateCache(requireLock = true)
- }
- }
- }
- }
}
@OptIn(ExperimentalContracts::class)
@@ -464,15 +504,53 @@
*/
internal abstract class RunOnce {
private val runMutex = Mutex()
- private var didRun: Boolean = false
+ private val didRun = CompletableDeferred<Unit>()
protected abstract suspend fun doRun()
+ suspend fun awaitComplete() = didRun.await()
+
suspend fun runIfNeeded() {
- if (didRun) return
+ if (didRun.isCompleted) return
runMutex.withLock {
- if (didRun) return
+ if (didRun.isCompleted) return
doRun()
- didRun = true
+ didRun.complete(Unit)
}
}
}
+
+/**
+ * [CoroutineContext.Element] that is added to the coroutineContext when [DataStore.updateData] is
+ * called to detect nested calls. b/241760537 (see: [DataStoreImpl.updateData])
+ *
+ * It is OK for different DataStore instances to nest updateData calls, they just cannot be on the
+ * same DataStore. To track these instances, each [UpdatingDataContextElement] holds a reference
+ * to a parent.
+ */
+internal class UpdatingDataContextElement(
+ private val parent: UpdatingDataContextElement?,
+ private val instance: DataStoreImpl<*>
+) : CoroutineContext.Element {
+
+ companion object {
+ internal val NESTED_UPDATE_ERROR_MESSAGE = """
+ Calling updateData inside updateData on the same DataStore instance is not supported
+ since updates made in the parent updateData call will not be visible to the nested
+ updateData call. See https://issuetracker.google.com/issues/241760537 for details.
+ """.trimIndent()
+ internal object Key : CoroutineContext.Key<UpdatingDataContextElement>
+ }
+ /**
+ * Checks the given [candidate] is not currently in a [DataStore.updateData] block.
+ */
+ fun checkNotUpdating(candidate: DataStore<*>) {
+ if (instance === candidate) {
+ error(NESTED_UPDATE_ERROR_MESSAGE)
+ }
+ // check the parent if it exists to detect nested calls between [DataStore] instances.
+ parent?.checkNotUpdating(candidate)
+ }
+
+ override val key: CoroutineContext.Key<*>
+ get() = Key
+}
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CloseDownstreamOnCloseTest.kt b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CloseDownstreamOnCloseTest.kt
index 5ecb216..087891b 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CloseDownstreamOnCloseTest.kt
+++ b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/CloseDownstreamOnCloseTest.kt
@@ -24,11 +24,13 @@
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
abstract class CloseDownstreamOnCloseTest<F : TestFile<F>>(private val testIO: TestIO<F, *>) {
@@ -47,11 +49,13 @@
) { testFile }
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun closeWhileCollecting() = testScope.runTest {
val collector = async {
store.data.toList().map { it.toInt() }
}
+ runCurrent()
store.updateData { 1 }
datastoreScope.cancel()
dispatcher.scheduler.advanceUntilIdle()
diff --git a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt
index ed9fd38..9db7724 100644
--- a/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt
+++ b/datastore/datastore-core/src/commonTest/kotlin/androidx/datastore/core/SingleProcessDataStoreTest.kt
@@ -19,13 +19,16 @@
import androidx.datastore.TestFile
import androidx.datastore.TestIO
import androidx.datastore.TestingSerializerConfig
+import androidx.datastore.core.UpdatingDataContextElement.Companion.NESTED_UPDATE_ERROR_MESSAGE
import androidx.datastore.core.handlers.NoOpCorruptionHandler
+import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
import androidx.kruth.assertThat
import androidx.kruth.assertThrows
import kotlin.coroutines.AbstractCoroutineContextElement
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.cancellation.CancellationException
import kotlin.test.BeforeTest
+import kotlin.test.Ignore
import kotlin.test.Test
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
@@ -37,12 +40,19 @@
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -513,6 +523,7 @@
val flowCollectionJob = async {
store.data.take(8).toList(collectedBytes)
}
+ runCurrent()
repeat(7) {
store.updateData { it.inc() }
@@ -538,6 +549,7 @@
flowOf8.toList(bytesFromSecondCollect)
}
+ runCurrent()
repeat(7) {
store.updateData { it.inc() }
}
@@ -566,6 +578,7 @@
flowOf8.take(8).toList(collectedBytes)
}
+ runCurrent()
repeat(7) {
store.updateData { it.inc() }
}
@@ -592,7 +605,7 @@
flowCollection2.await()
}
}
-
+ runCurrent()
repeat(15) {
store.updateData { it.inc() }
}
@@ -968,6 +981,167 @@
}
}
+ @Test
+ fun observeFileOnlyWhenDatastoreIsObserved() = runTest {
+ class InterProcessCoordinatorWithInfiniteUpdates(
+ val delegate: InterProcessCoordinator,
+ val observerCount: MutableStateFlow<Int> = MutableStateFlow(0)
+ ) : InterProcessCoordinator by delegate {
+ override val updateNotifications: Flow<Unit>
+ get() {
+ return flow<Unit> {
+ // never emit but never finish either so we know when we are being collected
+ suspendCancellableCoroutine { }
+ }.onStart {
+ observerCount.update { it + 1 }
+ }.onCompletion {
+ observerCount.update { it - 1 }
+ }
+ }
+ }
+ val observerCount = MutableStateFlow(0)
+ store = testIO.getStore(
+ serializerConfig,
+ dataStoreScope,
+ {
+ InterProcessCoordinatorWithInfiniteUpdates(
+ delegate = createSingleProcessCoordinator(testFile.path()),
+ observerCount = observerCount
+ )
+ }
+ ) { testFile }
+ fun hasObservers(): Boolean {
+ runCurrent()
+ return observerCount.value > 0
+ }
+ assertThat(hasObservers()).isFalse()
+ val collector1 = async {
+ store.data.collect {}
+ }
+ runCurrent()
+ assertThat(hasObservers()).isTrue()
+ val collector2 = async {
+ store.data.collect {}
+ }
+ assertThat(hasObservers()).isTrue()
+ collector1.cancelAndJoin()
+ assertThat(hasObservers()).isTrue()
+ collector2.cancelAndJoin()
+ assertThat(hasObservers()).isFalse()
+ val collector3 = async {
+ store.data.collect {}
+ }
+ assertThat(hasObservers()).isTrue()
+ collector3.cancelAndJoin()
+ assertThat(hasObservers()).isFalse()
+ }
+
+ @Test
+ fun nestedUpdateCallsShouldntDeadlock() = runTest {
+ val result = store.updateData {
+ assertThrows<IllegalStateException> {
+ store.updateData {
+ // won't execute
+ 2.toByte()
+ }
+ }.hasMessageThat().isEqualTo(NESTED_UPDATE_ERROR_MESSAGE)
+ 1.toByte()
+ }
+ assertThat(result).isEqualTo(1.toByte())
+ assertThat(store.data.first()).isEqualTo(1.toByte())
+ // write again, make sure we allow new transactions
+ store.updateData {
+ 3.toByte()
+ }
+ assertThat(store.data.first()).isEqualTo(3.toByte())
+ }
+
+ @Test
+ fun nestedUpdatesInDifferentStores() = runTest {
+ val testFile2 = testIO.newTempFile(parentFile = tempFolder)
+ val store2 = testIO.getStore(
+ serializerConfig,
+ dataStoreScope,
+ { createSingleProcessCoordinator(testFile2.path()) }
+ ) { testFile2 }
+ store.updateData {
+ store2.updateData {
+ 2.toByte()
+ }
+ 1.toByte()
+ }
+ assertThat(store.data.first()).isEqualTo(1.toByte())
+ assertThat(store2.data.first()).isEqualTo(2.toByte())
+ }
+
+ @Test
+ fun nestedUpdatesInDifferentStores_fail() = runTest {
+ val testFile2 = testIO.newTempFile(parentFile = tempFolder)
+ val store2 = testIO.getStore(
+ serializerConfig,
+ dataStoreScope,
+ { createSingleProcessCoordinator(testFile2.path()) }
+ ) { testFile2 }
+ store.updateData {
+ store2.updateData {
+ assertThrows<IllegalStateException> {
+ // we are in a nested transaction from store, hence this won't be allowed
+ store.updateData {
+ 3.toByte()
+ }
+ }.hasMessageThat().isEqualTo(NESTED_UPDATE_ERROR_MESSAGE)
+ 2.toByte()
+ }
+ 1.toByte()
+ }
+ assertThat(store.data.first()).isEqualTo(1.toByte())
+ assertThat(store2.data.first()).isEqualTo(2.toByte())
+ }
+
+ @Ignore // b/289582516
+ @Test
+ fun testReadHandlesCorruptionAfterInit() {
+ runTest {
+ val newStore = newDataStore(
+ testFile,
+ corruptionHandler = ReplaceFileCorruptionHandler { _ -> 2 }
+ )
+
+ // create the file to prevent serializer from returning default value
+ newStore.updateData { 1 }
+ assertThat(newStore.data.first()).isEqualTo(1)
+
+ // increment version to force non-cached read which gets [CorruptionException], the
+ // current state is [Data]
+ serializerConfig.failReadWithCorruptionException = true
+ serializerConfig.defaultValue = 2
+ newStore.incrementSharedCounter()
+ assertThat(newStore.data.first()).isEqualTo(2)
+ }
+ }
+
+ @Ignore // b/289582516
+ @Test
+ fun testUpdateHandlesCorruptionAfterInit() {
+ runTest {
+ val newStore = newDataStore(
+ testFile,
+ corruptionHandler = ReplaceFileCorruptionHandler { _ -> 2 }
+ )
+
+ // create the file to prevent serializer from returning default value
+ newStore.updateData { 1 }
+ assertThat(newStore.data.first()).isEqualTo(1)
+
+ // increment version to force non-cached read which gets [CorruptionException], the
+ // current state is [Data]
+ serializerConfig.failReadWithCorruptionException = true
+ serializerConfig.defaultValue = 2
+ newStore.incrementSharedCounter()
+ assertThat(newStore.updateData { 3.toByte() }).isEqualTo(3)
+ }
+ }
+
private class TestingCorruptionHandler(
private val replaceWith: Byte? = null
) : CorruptionHandler<Byte> {
diff --git a/datastore/datastore-preferences-core/api/1.1.0-beta02.txt b/datastore/datastore-preferences-core/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..a2e5dcc
--- /dev/null
+++ b/datastore/datastore-preferences-core/api/1.1.0-beta02.txt
@@ -0,0 +1,74 @@
+// Signature format: 4.0
+package androidx.datastore.preferences.core {
+
+ public final class MutablePreferences extends androidx.datastore.preferences.core.Preferences {
+ method public java.util.Map<androidx.datastore.preferences.core.Preferences.Key<?>,java.lang.Object> asMap();
+ method public void clear();
+ method public operator <T> boolean contains(androidx.datastore.preferences.core.Preferences.Key<T> key);
+ method public operator <T> T? get(androidx.datastore.preferences.core.Preferences.Key<T> key);
+ method public operator void minusAssign(androidx.datastore.preferences.core.Preferences.Key<?> key);
+ method public operator void plusAssign(androidx.datastore.preferences.core.Preferences prefs);
+ method public operator void plusAssign(androidx.datastore.preferences.core.Preferences.Pair<?> pair);
+ method public void putAll(androidx.datastore.preferences.core.Preferences.Pair<?>... pairs);
+ method public <T> T remove(androidx.datastore.preferences.core.Preferences.Key<T> key);
+ method public operator <T> void set(androidx.datastore.preferences.core.Preferences.Key<T> key, T value);
+ }
+
+ public final class PreferenceDataStoreFactory {
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> create(optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>> migrations, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> create(optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>> migrations, optional kotlinx.coroutines.CoroutineScope scope, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> create(optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> create(androidx.datastore.core.Storage<androidx.datastore.preferences.core.Preferences> storage, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>> migrations, optional kotlinx.coroutines.CoroutineScope scope);
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> createWithPath(optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>> migrations, optional kotlinx.coroutines.CoroutineScope scope, kotlin.jvm.functions.Function0<okio.Path> produceFile);
+ field public static final androidx.datastore.preferences.core.PreferenceDataStoreFactory INSTANCE;
+ }
+
+ public abstract class Preferences {
+ method public abstract java.util.Map<androidx.datastore.preferences.core.Preferences.Key<?>,java.lang.Object> asMap();
+ method public abstract operator <T> boolean contains(androidx.datastore.preferences.core.Preferences.Key<T> key);
+ method public abstract operator <T> T? get(androidx.datastore.preferences.core.Preferences.Key<T> key);
+ method public final androidx.datastore.preferences.core.MutablePreferences toMutablePreferences();
+ method public final androidx.datastore.preferences.core.Preferences toPreferences();
+ }
+
+ public static final class Preferences.Key<T> {
+ method public String getName();
+ method public infix androidx.datastore.preferences.core.Preferences.Pair<T> to(T value);
+ property public final String name;
+ }
+
+ public static final class Preferences.Pair<T> {
+ }
+
+ public final class PreferencesFactory {
+ method public static androidx.datastore.preferences.core.Preferences create(androidx.datastore.preferences.core.Preferences.Pair<?>... pairs);
+ method public static androidx.datastore.preferences.core.Preferences createEmpty();
+ method public static androidx.datastore.preferences.core.MutablePreferences createMutable(androidx.datastore.preferences.core.Preferences.Pair<?>... pairs);
+ }
+
+ public final class PreferencesKeys {
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.Boolean> booleanKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<byte[]> byteArrayKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.Double> doubleKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.Float> floatKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.Integer> intKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.Long> longKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.String> stringKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.util.Set<java.lang.String>> stringSetKey(String name);
+ }
+
+ public final class PreferencesKt {
+ method public static suspend Object? edit(androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>, kotlin.jvm.functions.Function2<? super androidx.datastore.preferences.core.MutablePreferences,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> transform, kotlin.coroutines.Continuation<? super androidx.datastore.preferences.core.Preferences>);
+ }
+
+ public final class PreferencesSerializer implements androidx.datastore.core.okio.OkioSerializer<androidx.datastore.preferences.core.Preferences> {
+ method public androidx.datastore.preferences.core.Preferences getDefaultValue();
+ method @kotlin.jvm.Throws(exceptionClasses={IOException::class, CorruptionException::class}) public suspend Object? readFrom(okio.BufferedSource source, kotlin.coroutines.Continuation<? super androidx.datastore.preferences.core.Preferences>) throws androidx.datastore.core.CorruptionException, java.io.IOException;
+ method @kotlin.jvm.Throws(exceptionClasses={IOException::class, CorruptionException::class}) public suspend Object? writeTo(androidx.datastore.preferences.core.Preferences t, okio.BufferedSink sink, kotlin.coroutines.Continuation<? super kotlin.Unit>) throws androidx.datastore.core.CorruptionException, java.io.IOException;
+ property public androidx.datastore.preferences.core.Preferences defaultValue;
+ field public static final androidx.datastore.preferences.core.PreferencesSerializer INSTANCE;
+ }
+
+}
+
diff --git a/datastore/datastore-preferences-core/api/current.ignore b/datastore/datastore-preferences-core/api/current.ignore
deleted file mode 100644
index dcd14f6..0000000
--- a/datastore/datastore-preferences-core/api/current.ignore
+++ /dev/null
@@ -1,9 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.datastore.preferences.core.MutablePreferences#set(androidx.datastore.preferences.core.Preferences.Key<T>, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter value in androidx.datastore.preferences.core.MutablePreferences.set(androidx.datastore.preferences.core.Preferences.Key<T> key, T value)
-InvalidNullConversion: androidx.datastore.preferences.core.Preferences.Key#to(T) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter value in androidx.datastore.preferences.core.Preferences.Key.to(T value)
-
-
-ParameterNameChange: androidx.datastore.preferences.core.PreferencesKt#edit(androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>, kotlin.jvm.functions.Function2<? super androidx.datastore.preferences.core.MutablePreferences,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>, kotlin.coroutines.Continuation<? super androidx.datastore.preferences.core.Preferences>) parameter #2:
- Attempted to remove parameter name from parameter arg3 in androidx.datastore.preferences.core.PreferencesKt.edit
diff --git a/datastore/datastore-preferences-core/api/restricted_1.1.0-beta02.txt b/datastore/datastore-preferences-core/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..a2e5dcc
--- /dev/null
+++ b/datastore/datastore-preferences-core/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,74 @@
+// Signature format: 4.0
+package androidx.datastore.preferences.core {
+
+ public final class MutablePreferences extends androidx.datastore.preferences.core.Preferences {
+ method public java.util.Map<androidx.datastore.preferences.core.Preferences.Key<?>,java.lang.Object> asMap();
+ method public void clear();
+ method public operator <T> boolean contains(androidx.datastore.preferences.core.Preferences.Key<T> key);
+ method public operator <T> T? get(androidx.datastore.preferences.core.Preferences.Key<T> key);
+ method public operator void minusAssign(androidx.datastore.preferences.core.Preferences.Key<?> key);
+ method public operator void plusAssign(androidx.datastore.preferences.core.Preferences prefs);
+ method public operator void plusAssign(androidx.datastore.preferences.core.Preferences.Pair<?> pair);
+ method public void putAll(androidx.datastore.preferences.core.Preferences.Pair<?>... pairs);
+ method public <T> T remove(androidx.datastore.preferences.core.Preferences.Key<T> key);
+ method public operator <T> void set(androidx.datastore.preferences.core.Preferences.Key<T> key, T value);
+ }
+
+ public final class PreferenceDataStoreFactory {
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> create(optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>> migrations, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> create(optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>> migrations, optional kotlinx.coroutines.CoroutineScope scope, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> create(optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> create(androidx.datastore.core.Storage<androidx.datastore.preferences.core.Preferences> storage, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>> migrations, optional kotlinx.coroutines.CoroutineScope scope);
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ method public androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences> createWithPath(optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>> migrations, optional kotlinx.coroutines.CoroutineScope scope, kotlin.jvm.functions.Function0<okio.Path> produceFile);
+ field public static final androidx.datastore.preferences.core.PreferenceDataStoreFactory INSTANCE;
+ }
+
+ public abstract class Preferences {
+ method public abstract java.util.Map<androidx.datastore.preferences.core.Preferences.Key<?>,java.lang.Object> asMap();
+ method public abstract operator <T> boolean contains(androidx.datastore.preferences.core.Preferences.Key<T> key);
+ method public abstract operator <T> T? get(androidx.datastore.preferences.core.Preferences.Key<T> key);
+ method public final androidx.datastore.preferences.core.MutablePreferences toMutablePreferences();
+ method public final androidx.datastore.preferences.core.Preferences toPreferences();
+ }
+
+ public static final class Preferences.Key<T> {
+ method public String getName();
+ method public infix androidx.datastore.preferences.core.Preferences.Pair<T> to(T value);
+ property public final String name;
+ }
+
+ public static final class Preferences.Pair<T> {
+ }
+
+ public final class PreferencesFactory {
+ method public static androidx.datastore.preferences.core.Preferences create(androidx.datastore.preferences.core.Preferences.Pair<?>... pairs);
+ method public static androidx.datastore.preferences.core.Preferences createEmpty();
+ method public static androidx.datastore.preferences.core.MutablePreferences createMutable(androidx.datastore.preferences.core.Preferences.Pair<?>... pairs);
+ }
+
+ public final class PreferencesKeys {
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.Boolean> booleanKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<byte[]> byteArrayKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.Double> doubleKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.Float> floatKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.Integer> intKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.Long> longKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.lang.String> stringKey(String name);
+ method public static androidx.datastore.preferences.core.Preferences.Key<java.util.Set<java.lang.String>> stringSetKey(String name);
+ }
+
+ public final class PreferencesKt {
+ method public static suspend Object? edit(androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>, kotlin.jvm.functions.Function2<? super androidx.datastore.preferences.core.MutablePreferences,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> transform, kotlin.coroutines.Continuation<? super androidx.datastore.preferences.core.Preferences>);
+ }
+
+ public final class PreferencesSerializer implements androidx.datastore.core.okio.OkioSerializer<androidx.datastore.preferences.core.Preferences> {
+ method public androidx.datastore.preferences.core.Preferences getDefaultValue();
+ method @kotlin.jvm.Throws(exceptionClasses={IOException::class, CorruptionException::class}) public suspend Object? readFrom(okio.BufferedSource source, kotlin.coroutines.Continuation<? super androidx.datastore.preferences.core.Preferences>) throws androidx.datastore.core.CorruptionException, java.io.IOException;
+ method @kotlin.jvm.Throws(exceptionClasses={IOException::class, CorruptionException::class}) public suspend Object? writeTo(androidx.datastore.preferences.core.Preferences t, okio.BufferedSink sink, kotlin.coroutines.Continuation<? super kotlin.Unit>) throws androidx.datastore.core.CorruptionException, java.io.IOException;
+ property public androidx.datastore.preferences.core.Preferences defaultValue;
+ field public static final androidx.datastore.preferences.core.PreferencesSerializer INSTANCE;
+ }
+
+}
+
diff --git a/datastore/datastore-preferences-core/api/restricted_current.ignore b/datastore/datastore-preferences-core/api/restricted_current.ignore
deleted file mode 100644
index dcd14f6..0000000
--- a/datastore/datastore-preferences-core/api/restricted_current.ignore
+++ /dev/null
@@ -1,9 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.datastore.preferences.core.MutablePreferences#set(androidx.datastore.preferences.core.Preferences.Key<T>, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter value in androidx.datastore.preferences.core.MutablePreferences.set(androidx.datastore.preferences.core.Preferences.Key<T> key, T value)
-InvalidNullConversion: androidx.datastore.preferences.core.Preferences.Key#to(T) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter value in androidx.datastore.preferences.core.Preferences.Key.to(T value)
-
-
-ParameterNameChange: androidx.datastore.preferences.core.PreferencesKt#edit(androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>, kotlin.jvm.functions.Function2<? super androidx.datastore.preferences.core.MutablePreferences,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>, kotlin.coroutines.Continuation<? super androidx.datastore.preferences.core.Preferences>) parameter #2:
- Attempted to remove parameter name from parameter arg3 in androidx.datastore.preferences.core.PreferencesKt.edit
diff --git a/datastore/datastore-preferences-core/build.gradle b/datastore/datastore-preferences-core/build.gradle
index 712741f..cff152d 100644
--- a/datastore/datastore-preferences-core/build.gradle
+++ b/datastore/datastore-preferences-core/build.gradle
@@ -22,7 +22,6 @@
* modifying its settings.
*/
import androidx.build.BundleInsideHelper
-import androidx.build.KmpPlatformsKt
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -32,8 +31,6 @@
alias(libs.plugins.kotlinSerialization)
}
-def enableNative = KmpPlatformsKt.enableNative(project)
-
androidXMultiplatform {
jvm() {
withJava()
@@ -80,22 +77,20 @@
implementation(project(":kruth:kruth"))
}
}
- if (enableNative) {
- nativeMain {
- dependsOn(commonMain)
- dependencies {
- implementation(libs.kotlinSerializationCore)
- implementation(libs.kotlinSerializationProtobuf)
- }
+ nativeMain {
+ dependsOn(commonMain)
+ dependencies {
+ implementation(libs.kotlinSerializationCore)
+ implementation(libs.kotlinSerializationProtobuf)
}
+ }
- nativeTest {
- dependsOn(commonTest)
- dependencies {
- implementation(libs.kotlinTest)
- implementation(project(":internal-testutils-datastore"))
- implementation(project(":kruth:kruth"))
- }
+ nativeTest {
+ dependsOn(commonTest)
+ dependencies {
+ implementation(libs.kotlinTest)
+ implementation(project(":internal-testutils-datastore"))
+ implementation(project(":kruth:kruth"))
}
}
diff --git a/datastore/datastore-preferences-rxjava2/api/1.1.0-beta02.txt b/datastore/datastore-preferences-rxjava2/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..4a1028c
--- /dev/null
+++ b/datastore/datastore-preferences-rxjava2/api/1.1.0-beta02.txt
@@ -0,0 +1,19 @@
+// Signature format: 4.0
+package androidx.datastore.preferences.rxjava2 {
+
+ public final class RxPreferenceDataStoreBuilder {
+ ctor public RxPreferenceDataStoreBuilder(android.content.Context context, String name);
+ ctor public RxPreferenceDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile);
+ method public androidx.datastore.preferences.rxjava2.RxPreferenceDataStoreBuilder addDataMigration(androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences> dataMigration);
+ method public androidx.datastore.preferences.rxjava2.RxPreferenceDataStoreBuilder addRxDataMigration(androidx.datastore.rxjava2.RxDataMigration<androidx.datastore.preferences.core.Preferences> rxDataMigration);
+ method public androidx.datastore.rxjava2.RxDataStore<androidx.datastore.preferences.core.Preferences> build();
+ method public androidx.datastore.preferences.rxjava2.RxPreferenceDataStoreBuilder setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences> corruptionHandler);
+ method public androidx.datastore.preferences.rxjava2.RxPreferenceDataStoreBuilder setIoScheduler(io.reactivex.Scheduler ioScheduler);
+ }
+
+ public final class RxPreferenceDataStoreDelegateKt {
+ method public static kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.rxjava2.RxDataStore<androidx.datastore.preferences.core.Preferences>> rxPreferencesDataStore(String name, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>>> produceMigrations, optional io.reactivex.Scheduler scheduler);
+ }
+
+}
+
diff --git a/datastore/datastore-preferences-rxjava2/api/res-1.1.0-beta02.txt b/datastore/datastore-preferences-rxjava2/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/datastore/datastore-preferences-rxjava2/api/res-1.1.0-beta02.txt
diff --git a/datastore/datastore-preferences-rxjava2/api/restricted_1.1.0-beta02.txt b/datastore/datastore-preferences-rxjava2/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..4a1028c
--- /dev/null
+++ b/datastore/datastore-preferences-rxjava2/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,19 @@
+// Signature format: 4.0
+package androidx.datastore.preferences.rxjava2 {
+
+ public final class RxPreferenceDataStoreBuilder {
+ ctor public RxPreferenceDataStoreBuilder(android.content.Context context, String name);
+ ctor public RxPreferenceDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile);
+ method public androidx.datastore.preferences.rxjava2.RxPreferenceDataStoreBuilder addDataMigration(androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences> dataMigration);
+ method public androidx.datastore.preferences.rxjava2.RxPreferenceDataStoreBuilder addRxDataMigration(androidx.datastore.rxjava2.RxDataMigration<androidx.datastore.preferences.core.Preferences> rxDataMigration);
+ method public androidx.datastore.rxjava2.RxDataStore<androidx.datastore.preferences.core.Preferences> build();
+ method public androidx.datastore.preferences.rxjava2.RxPreferenceDataStoreBuilder setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences> corruptionHandler);
+ method public androidx.datastore.preferences.rxjava2.RxPreferenceDataStoreBuilder setIoScheduler(io.reactivex.Scheduler ioScheduler);
+ }
+
+ public final class RxPreferenceDataStoreDelegateKt {
+ method public static kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.rxjava2.RxDataStore<androidx.datastore.preferences.core.Preferences>> rxPreferencesDataStore(String name, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>>> produceMigrations, optional io.reactivex.Scheduler scheduler);
+ }
+
+}
+
diff --git a/datastore/datastore-preferences-rxjava3/api/1.1.0-beta02.txt b/datastore/datastore-preferences-rxjava3/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..b11bf51
--- /dev/null
+++ b/datastore/datastore-preferences-rxjava3/api/1.1.0-beta02.txt
@@ -0,0 +1,19 @@
+// Signature format: 4.0
+package androidx.datastore.preferences.rxjava3 {
+
+ public final class RxPreferenceDataStoreBuilder {
+ ctor public RxPreferenceDataStoreBuilder(android.content.Context context, String name);
+ ctor public RxPreferenceDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile);
+ method public androidx.datastore.preferences.rxjava3.RxPreferenceDataStoreBuilder addDataMigration(androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences> dataMigration);
+ method public androidx.datastore.preferences.rxjava3.RxPreferenceDataStoreBuilder addRxDataMigration(androidx.datastore.rxjava3.RxDataMigration<androidx.datastore.preferences.core.Preferences> rxDataMigration);
+ method public androidx.datastore.rxjava3.RxDataStore<androidx.datastore.preferences.core.Preferences> build();
+ method public androidx.datastore.preferences.rxjava3.RxPreferenceDataStoreBuilder setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences> corruptionHandler);
+ method public androidx.datastore.preferences.rxjava3.RxPreferenceDataStoreBuilder setIoScheduler(io.reactivex.rxjava3.core.Scheduler ioScheduler);
+ }
+
+ public final class RxPreferenceDataStoreDelegateKt {
+ method public static kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.rxjava3.RxDataStore<androidx.datastore.preferences.core.Preferences>> rxPreferencesDataStore(String name, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>>> produceMigrations, optional io.reactivex.rxjava3.core.Scheduler scheduler);
+ }
+
+}
+
diff --git a/datastore/datastore-preferences-rxjava3/api/res-1.1.0-beta02.txt b/datastore/datastore-preferences-rxjava3/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/datastore/datastore-preferences-rxjava3/api/res-1.1.0-beta02.txt
diff --git a/datastore/datastore-preferences-rxjava3/api/restricted_1.1.0-beta02.txt b/datastore/datastore-preferences-rxjava3/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..b11bf51
--- /dev/null
+++ b/datastore/datastore-preferences-rxjava3/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,19 @@
+// Signature format: 4.0
+package androidx.datastore.preferences.rxjava3 {
+
+ public final class RxPreferenceDataStoreBuilder {
+ ctor public RxPreferenceDataStoreBuilder(android.content.Context context, String name);
+ ctor public RxPreferenceDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile);
+ method public androidx.datastore.preferences.rxjava3.RxPreferenceDataStoreBuilder addDataMigration(androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences> dataMigration);
+ method public androidx.datastore.preferences.rxjava3.RxPreferenceDataStoreBuilder addRxDataMigration(androidx.datastore.rxjava3.RxDataMigration<androidx.datastore.preferences.core.Preferences> rxDataMigration);
+ method public androidx.datastore.rxjava3.RxDataStore<androidx.datastore.preferences.core.Preferences> build();
+ method public androidx.datastore.preferences.rxjava3.RxPreferenceDataStoreBuilder setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences> corruptionHandler);
+ method public androidx.datastore.preferences.rxjava3.RxPreferenceDataStoreBuilder setIoScheduler(io.reactivex.rxjava3.core.Scheduler ioScheduler);
+ }
+
+ public final class RxPreferenceDataStoreDelegateKt {
+ method public static kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.rxjava3.RxDataStore<androidx.datastore.preferences.core.Preferences>> rxPreferencesDataStore(String name, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>>> produceMigrations, optional io.reactivex.rxjava3.core.Scheduler scheduler);
+ }
+
+}
+
diff --git a/datastore/datastore-preferences/api/1.1.0-beta02.txt b/datastore/datastore-preferences/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..3ee7f63
--- /dev/null
+++ b/datastore/datastore-preferences/api/1.1.0-beta02.txt
@@ -0,0 +1,20 @@
+// Signature format: 4.0
+package androidx.datastore.preferences {
+
+ public final class PreferenceDataStoreDelegateKt {
+ method public static kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>> preferencesDataStore(String name, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>>> produceMigrations, optional kotlinx.coroutines.CoroutineScope scope);
+ }
+
+ public final class PreferenceDataStoreFile {
+ method public static java.io.File preferencesDataStoreFile(android.content.Context, String name);
+ }
+
+ public final class SharedPreferencesMigrationKt {
+ method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName);
+ method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String> keysToMigrate);
+ method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences);
+ method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, optional java.util.Set<java.lang.String> keysToMigrate);
+ }
+
+}
+
diff --git a/datastore/datastore-preferences/api/res-1.1.0-beta02.txt b/datastore/datastore-preferences/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/datastore/datastore-preferences/api/res-1.1.0-beta02.txt
diff --git a/datastore/datastore-preferences/api/restricted_1.1.0-beta02.txt b/datastore/datastore-preferences/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..3ee7f63
--- /dev/null
+++ b/datastore/datastore-preferences/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,20 @@
+// Signature format: 4.0
+package androidx.datastore.preferences {
+
+ public final class PreferenceDataStoreDelegateKt {
+ method public static kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>> preferencesDataStore(String name, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.core.Preferences>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<androidx.datastore.preferences.core.Preferences>>> produceMigrations, optional kotlinx.coroutines.CoroutineScope scope);
+ }
+
+ public final class PreferenceDataStoreFile {
+ method public static java.io.File preferencesDataStoreFile(android.content.Context, String name);
+ }
+
+ public final class SharedPreferencesMigrationKt {
+ method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName);
+ method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String> keysToMigrate);
+ method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences);
+ method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, optional java.util.Set<java.lang.String> keysToMigrate);
+ }
+
+}
+
diff --git a/datastore/datastore-rxjava2/api/1.1.0-beta02.txt b/datastore/datastore-rxjava2/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..a05559f
--- /dev/null
+++ b/datastore/datastore-rxjava2/api/1.1.0-beta02.txt
@@ -0,0 +1,48 @@
+// Signature format: 4.0
+package androidx.datastore.rxjava2 {
+
+ public interface RxDataMigration<T> {
+ method public io.reactivex.Completable cleanUp();
+ method public io.reactivex.Single<T!> migrate(T?);
+ method public io.reactivex.Single<java.lang.Boolean!> shouldMigrate(T?);
+ }
+
+ public final class RxDataStore<T> implements io.reactivex.disposables.Disposable {
+ method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public io.reactivex.Flowable<T> data();
+ method public void dispose();
+ method public boolean isDisposed();
+ method public io.reactivex.Completable shutdownComplete();
+ method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public io.reactivex.Single<T> updateDataAsync(io.reactivex.functions.Function<T,io.reactivex.Single<T>> transform);
+ field public static final androidx.datastore.rxjava2.RxDataStore.Companion Companion;
+ }
+
+ public static final class RxDataStore.Companion {
+ }
+
+ public final class RxDataStoreBuilder<T> {
+ ctor public RxDataStoreBuilder(android.content.Context context, String fileName, androidx.datastore.core.Serializer<T> serializer);
+ ctor public RxDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile, androidx.datastore.core.Serializer<T> serializer);
+ method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> addDataMigration(androidx.datastore.core.DataMigration<T> dataMigration);
+ method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> addRxDataMigration(androidx.datastore.rxjava2.RxDataMigration<T> rxDataMigration);
+ method public androidx.datastore.rxjava2.RxDataStore<T> build();
+ method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T> corruptionHandler);
+ method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setIoScheduler(io.reactivex.Scheduler ioScheduler);
+ }
+
+ public final class RxDataStoreDelegateKt {
+ method public static <T> kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.rxjava2.RxDataStore<T>> rxDataStore(String fileName, androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<T>>> produceMigrations, optional io.reactivex.Scheduler scheduler);
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface RxSharedPreferencesMigration<T> {
+ method public io.reactivex.Single<T> migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T currentData);
+ method public default io.reactivex.Single<java.lang.Boolean> shouldMigrate(T currentData);
+ }
+
+ public final class RxSharedPreferencesMigrationBuilder<T> {
+ ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava2.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
+ method public androidx.datastore.core.DataMigration<T> build();
+ method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
+ }
+
+}
+
diff --git a/datastore/datastore-rxjava2/api/current.ignore b/datastore/datastore-rxjava2/api/current.ignore
deleted file mode 100644
index a36ad8f..0000000
--- a/datastore/datastore-rxjava2/api/current.ignore
+++ /dev/null
@@ -1,13 +0,0 @@
-// Baseline format: 1.0
-BecameUnchecked: androidx.datastore.rxjava2.RxDataStore#data():
- Removed method androidx.datastore.rxjava2.RxDataStore.data() from compatibility checked API surface
-BecameUnchecked: androidx.datastore.rxjava2.RxDataStore#updateDataAsync(io.reactivex.functions.Function<T,io.reactivex.Single<T>>):
- Removed method androidx.datastore.rxjava2.RxDataStore.updateDataAsync(io.reactivex.functions.Function<T,io.reactivex.Single<T>>) from compatibility checked API surface
-BecameUnchecked: androidx.datastore.rxjava2.RxDataStore#updateDataAsync(io.reactivex.functions.Function<T,io.reactivex.Single<T>>) parameter #0:
- Removed parameter transform in androidx.datastore.rxjava2.RxDataStore.updateDataAsync(io.reactivex.functions.Function<T,io.reactivex.Single<T>> transform) from compatibility checked API surface
-
-
-InvalidNullConversion: androidx.datastore.rxjava2.RxSharedPreferencesMigration#migrate(androidx.datastore.migrations.SharedPreferencesView, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.rxjava2.RxSharedPreferencesMigration.migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T currentData)
-InvalidNullConversion: androidx.datastore.rxjava2.RxSharedPreferencesMigration#shouldMigrate(T) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.rxjava2.RxSharedPreferencesMigration.shouldMigrate(T currentData)
diff --git a/datastore/datastore-rxjava2/api/res-1.1.0-beta02.txt b/datastore/datastore-rxjava2/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/datastore/datastore-rxjava2/api/res-1.1.0-beta02.txt
diff --git a/datastore/datastore-rxjava2/api/restricted_1.1.0-beta02.txt b/datastore/datastore-rxjava2/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..a05559f
--- /dev/null
+++ b/datastore/datastore-rxjava2/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,48 @@
+// Signature format: 4.0
+package androidx.datastore.rxjava2 {
+
+ public interface RxDataMigration<T> {
+ method public io.reactivex.Completable cleanUp();
+ method public io.reactivex.Single<T!> migrate(T?);
+ method public io.reactivex.Single<java.lang.Boolean!> shouldMigrate(T?);
+ }
+
+ public final class RxDataStore<T> implements io.reactivex.disposables.Disposable {
+ method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public io.reactivex.Flowable<T> data();
+ method public void dispose();
+ method public boolean isDisposed();
+ method public io.reactivex.Completable shutdownComplete();
+ method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public io.reactivex.Single<T> updateDataAsync(io.reactivex.functions.Function<T,io.reactivex.Single<T>> transform);
+ field public static final androidx.datastore.rxjava2.RxDataStore.Companion Companion;
+ }
+
+ public static final class RxDataStore.Companion {
+ }
+
+ public final class RxDataStoreBuilder<T> {
+ ctor public RxDataStoreBuilder(android.content.Context context, String fileName, androidx.datastore.core.Serializer<T> serializer);
+ ctor public RxDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile, androidx.datastore.core.Serializer<T> serializer);
+ method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> addDataMigration(androidx.datastore.core.DataMigration<T> dataMigration);
+ method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> addRxDataMigration(androidx.datastore.rxjava2.RxDataMigration<T> rxDataMigration);
+ method public androidx.datastore.rxjava2.RxDataStore<T> build();
+ method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T> corruptionHandler);
+ method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setIoScheduler(io.reactivex.Scheduler ioScheduler);
+ }
+
+ public final class RxDataStoreDelegateKt {
+ method public static <T> kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.rxjava2.RxDataStore<T>> rxDataStore(String fileName, androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<T>>> produceMigrations, optional io.reactivex.Scheduler scheduler);
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface RxSharedPreferencesMigration<T> {
+ method public io.reactivex.Single<T> migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T currentData);
+ method public default io.reactivex.Single<java.lang.Boolean> shouldMigrate(T currentData);
+ }
+
+ public final class RxSharedPreferencesMigrationBuilder<T> {
+ ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava2.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
+ method public androidx.datastore.core.DataMigration<T> build();
+ method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
+ }
+
+}
+
diff --git a/datastore/datastore-rxjava2/api/restricted_current.ignore b/datastore/datastore-rxjava2/api/restricted_current.ignore
deleted file mode 100644
index 2b1768d..0000000
--- a/datastore/datastore-rxjava2/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.datastore.rxjava2.RxSharedPreferencesMigration#migrate(androidx.datastore.migrations.SharedPreferencesView, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.rxjava2.RxSharedPreferencesMigration.migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T currentData)
-InvalidNullConversion: androidx.datastore.rxjava2.RxSharedPreferencesMigration#shouldMigrate(T) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.rxjava2.RxSharedPreferencesMigration.shouldMigrate(T currentData)
diff --git a/datastore/datastore-rxjava3/api/1.1.0-beta02.txt b/datastore/datastore-rxjava3/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..14b5e2c
--- /dev/null
+++ b/datastore/datastore-rxjava3/api/1.1.0-beta02.txt
@@ -0,0 +1,48 @@
+// Signature format: 4.0
+package androidx.datastore.rxjava3 {
+
+ public interface RxDataMigration<T> {
+ method public io.reactivex.rxjava3.core.Completable cleanUp();
+ method public io.reactivex.rxjava3.core.Single<T!> migrate(T?);
+ method public io.reactivex.rxjava3.core.Single<java.lang.Boolean!> shouldMigrate(T?);
+ }
+
+ public final class RxDataStore<T> implements io.reactivex.rxjava3.disposables.Disposable {
+ method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public io.reactivex.rxjava3.core.Flowable<T> data();
+ method public void dispose();
+ method public boolean isDisposed();
+ method public io.reactivex.rxjava3.core.Completable shutdownComplete();
+ method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public io.reactivex.rxjava3.core.Single<T> updateDataAsync(io.reactivex.rxjava3.functions.Function<T,io.reactivex.rxjava3.core.Single<T>> transform);
+ field public static final androidx.datastore.rxjava3.RxDataStore.Companion Companion;
+ }
+
+ public static final class RxDataStore.Companion {
+ }
+
+ public final class RxDataStoreBuilder<T> {
+ ctor public RxDataStoreBuilder(android.content.Context context, String fileName, androidx.datastore.core.Serializer<T> serializer);
+ ctor public RxDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile, androidx.datastore.core.Serializer<T> serializer);
+ method public androidx.datastore.rxjava3.RxDataStoreBuilder<T> addDataMigration(androidx.datastore.core.DataMigration<T> dataMigration);
+ method public androidx.datastore.rxjava3.RxDataStoreBuilder<T> addRxDataMigration(androidx.datastore.rxjava3.RxDataMigration<T> rxDataMigration);
+ method public androidx.datastore.rxjava3.RxDataStore<T> build();
+ method public androidx.datastore.rxjava3.RxDataStoreBuilder<T> setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T> corruptionHandler);
+ method public androidx.datastore.rxjava3.RxDataStoreBuilder<T> setIoScheduler(io.reactivex.rxjava3.core.Scheduler ioScheduler);
+ }
+
+ public final class RxDataStoreDelegateKt {
+ method public static <T> kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.rxjava3.RxDataStore<T>> rxDataStore(String fileName, androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<T>>> produceMigrations, optional io.reactivex.rxjava3.core.Scheduler scheduler);
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface RxSharedPreferencesMigration<T> {
+ method public io.reactivex.rxjava3.core.Single<T> migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T currentData);
+ method public default io.reactivex.rxjava3.core.Single<java.lang.Boolean> shouldMigrate(T currentData);
+ }
+
+ public final class RxSharedPreferencesMigrationBuilder<T> {
+ ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava3.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
+ method public androidx.datastore.core.DataMigration<T> build();
+ method public androidx.datastore.rxjava3.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
+ }
+
+}
+
diff --git a/datastore/datastore-rxjava3/api/current.ignore b/datastore/datastore-rxjava3/api/current.ignore
deleted file mode 100644
index e286357..0000000
--- a/datastore/datastore-rxjava3/api/current.ignore
+++ /dev/null
@@ -1,13 +0,0 @@
-// Baseline format: 1.0
-BecameUnchecked: androidx.datastore.rxjava3.RxDataStore#data():
- Removed method androidx.datastore.rxjava3.RxDataStore.data() from compatibility checked API surface
-BecameUnchecked: androidx.datastore.rxjava3.RxDataStore#updateDataAsync(io.reactivex.rxjava3.functions.Function<T,io.reactivex.rxjava3.core.Single<T>>):
- Removed method androidx.datastore.rxjava3.RxDataStore.updateDataAsync(io.reactivex.rxjava3.functions.Function<T,io.reactivex.rxjava3.core.Single<T>>) from compatibility checked API surface
-BecameUnchecked: androidx.datastore.rxjava3.RxDataStore#updateDataAsync(io.reactivex.rxjava3.functions.Function<T,io.reactivex.rxjava3.core.Single<T>>) parameter #0:
- Removed parameter transform in androidx.datastore.rxjava3.RxDataStore.updateDataAsync(io.reactivex.rxjava3.functions.Function<T,io.reactivex.rxjava3.core.Single<T>> transform) from compatibility checked API surface
-
-
-InvalidNullConversion: androidx.datastore.rxjava3.RxSharedPreferencesMigration#migrate(androidx.datastore.migrations.SharedPreferencesView, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.rxjava3.RxSharedPreferencesMigration.migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T currentData)
-InvalidNullConversion: androidx.datastore.rxjava3.RxSharedPreferencesMigration#shouldMigrate(T) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.rxjava3.RxSharedPreferencesMigration.shouldMigrate(T currentData)
diff --git a/datastore/datastore-rxjava3/api/res-1.1.0-beta02.txt b/datastore/datastore-rxjava3/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/datastore/datastore-rxjava3/api/res-1.1.0-beta02.txt
diff --git a/datastore/datastore-rxjava3/api/restricted_1.1.0-beta02.txt b/datastore/datastore-rxjava3/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..14b5e2c
--- /dev/null
+++ b/datastore/datastore-rxjava3/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,48 @@
+// Signature format: 4.0
+package androidx.datastore.rxjava3 {
+
+ public interface RxDataMigration<T> {
+ method public io.reactivex.rxjava3.core.Completable cleanUp();
+ method public io.reactivex.rxjava3.core.Single<T!> migrate(T?);
+ method public io.reactivex.rxjava3.core.Single<java.lang.Boolean!> shouldMigrate(T?);
+ }
+
+ public final class RxDataStore<T> implements io.reactivex.rxjava3.disposables.Disposable {
+ method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public io.reactivex.rxjava3.core.Flowable<T> data();
+ method public void dispose();
+ method public boolean isDisposed();
+ method public io.reactivex.rxjava3.core.Completable shutdownComplete();
+ method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public io.reactivex.rxjava3.core.Single<T> updateDataAsync(io.reactivex.rxjava3.functions.Function<T,io.reactivex.rxjava3.core.Single<T>> transform);
+ field public static final androidx.datastore.rxjava3.RxDataStore.Companion Companion;
+ }
+
+ public static final class RxDataStore.Companion {
+ }
+
+ public final class RxDataStoreBuilder<T> {
+ ctor public RxDataStoreBuilder(android.content.Context context, String fileName, androidx.datastore.core.Serializer<T> serializer);
+ ctor public RxDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile, androidx.datastore.core.Serializer<T> serializer);
+ method public androidx.datastore.rxjava3.RxDataStoreBuilder<T> addDataMigration(androidx.datastore.core.DataMigration<T> dataMigration);
+ method public androidx.datastore.rxjava3.RxDataStoreBuilder<T> addRxDataMigration(androidx.datastore.rxjava3.RxDataMigration<T> rxDataMigration);
+ method public androidx.datastore.rxjava3.RxDataStore<T> build();
+ method public androidx.datastore.rxjava3.RxDataStoreBuilder<T> setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T> corruptionHandler);
+ method public androidx.datastore.rxjava3.RxDataStoreBuilder<T> setIoScheduler(io.reactivex.rxjava3.core.Scheduler ioScheduler);
+ }
+
+ public final class RxDataStoreDelegateKt {
+ method public static <T> kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.rxjava3.RxDataStore<T>> rxDataStore(String fileName, androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<T>>> produceMigrations, optional io.reactivex.rxjava3.core.Scheduler scheduler);
+ }
+
+ @kotlin.jvm.JvmDefaultWithCompatibility public interface RxSharedPreferencesMigration<T> {
+ method public io.reactivex.rxjava3.core.Single<T> migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T currentData);
+ method public default io.reactivex.rxjava3.core.Single<java.lang.Boolean> shouldMigrate(T currentData);
+ }
+
+ public final class RxSharedPreferencesMigrationBuilder<T> {
+ ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava3.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
+ method public androidx.datastore.core.DataMigration<T> build();
+ method public androidx.datastore.rxjava3.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
+ }
+
+}
+
diff --git a/datastore/datastore-rxjava3/api/restricted_current.ignore b/datastore/datastore-rxjava3/api/restricted_current.ignore
deleted file mode 100644
index 624856f..0000000
--- a/datastore/datastore-rxjava3/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.datastore.rxjava3.RxSharedPreferencesMigration#migrate(androidx.datastore.migrations.SharedPreferencesView, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.rxjava3.RxSharedPreferencesMigration.migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T currentData)
-InvalidNullConversion: androidx.datastore.rxjava3.RxSharedPreferencesMigration#shouldMigrate(T) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.rxjava3.RxSharedPreferencesMigration.shouldMigrate(T currentData)
diff --git a/datastore/datastore/api/1.1.0-beta02.txt b/datastore/datastore/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..422513d
--- /dev/null
+++ b/datastore/datastore/api/1.1.0-beta02.txt
@@ -0,0 +1,40 @@
+// Signature format: 4.0
+package androidx.datastore {
+
+ public final class DataStoreDelegateKt {
+ method public static <T> kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.core.DataStore<T>> dataStore(String fileName, androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<T>>> produceMigrations, optional kotlinx.coroutines.CoroutineScope scope);
+ }
+
+ public final class DataStoreFile {
+ method public static java.io.File dataStoreFile(android.content.Context, String fileName);
+ }
+
+}
+
+package androidx.datastore.migrations {
+
+ public final class SharedPreferencesMigration<T> implements androidx.datastore.core.DataMigration<T> {
+ ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String> keysToMigrate, optional kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String> keysToMigrate, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, optional java.util.Set<java.lang.String> keysToMigrate, optional kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, optional java.util.Set<java.lang.String> keysToMigrate, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public suspend Object? cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit>) throws java.io.IOException;
+ method public suspend Object? migrate(T currentData, kotlin.coroutines.Continuation<? super T>);
+ method public suspend Object? shouldMigrate(T currentData, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+ }
+
+ public final class SharedPreferencesView {
+ method public operator boolean contains(String key);
+ method public java.util.Map<java.lang.String,java.lang.Object?> getAll();
+ method public boolean getBoolean(String key, boolean defValue);
+ method public float getFloat(String key, float defValue);
+ method public int getInt(String key, int defValue);
+ method public long getLong(String key, long defValue);
+ method public String? getString(String key, optional String? defValue);
+ method public java.util.Set<java.lang.String>? getStringSet(String key, optional java.util.Set<java.lang.String>? defValues);
+ }
+
+}
+
diff --git a/datastore/datastore/api/current.ignore b/datastore/datastore/api/current.ignore
deleted file mode 100644
index a3186a11..0000000
--- a/datastore/datastore/api/current.ignore
+++ /dev/null
@@ -1,17 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.datastore.migrations.SharedPreferencesMigration#migrate(T, kotlin.coroutines.Continuation<? super T>) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.migrations.SharedPreferencesMigration.migrate(T currentData, kotlin.coroutines.Continuation<? super T> arg2)
-InvalidNullConversion: androidx.datastore.migrations.SharedPreferencesMigration#shouldMigrate(T, kotlin.coroutines.Continuation<? super java.lang.Boolean>) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.migrations.SharedPreferencesMigration.shouldMigrate(T currentData, kotlin.coroutines.Continuation<? super java.lang.Boolean> arg2)
-
-
-ParameterNameChange: androidx.datastore.migrations.SharedPreferencesMigration#cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
- Attempted to remove parameter name from parameter arg1 in androidx.datastore.migrations.SharedPreferencesMigration.cleanUp
-ParameterNameChange: androidx.datastore.migrations.SharedPreferencesMigration#migrate(T, kotlin.coroutines.Continuation<? super T>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.migrations.SharedPreferencesMigration.migrate
-ParameterNameChange: androidx.datastore.migrations.SharedPreferencesMigration#shouldMigrate(T, kotlin.coroutines.Continuation<? super java.lang.Boolean>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.migrations.SharedPreferencesMigration.shouldMigrate
-
-
-RemovedClass: androidx.datastore.migrations.SharedPreferencesMigrationKt:
- Removed class androidx.datastore.migrations.SharedPreferencesMigrationKt
diff --git a/datastore/datastore/api/res-1.1.0-beta02.txt b/datastore/datastore/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/datastore/datastore/api/res-1.1.0-beta02.txt
diff --git a/datastore/datastore/api/restricted_1.1.0-beta02.txt b/datastore/datastore/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..422513d
--- /dev/null
+++ b/datastore/datastore/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1,40 @@
+// Signature format: 4.0
+package androidx.datastore {
+
+ public final class DataStoreDelegateKt {
+ method public static <T> kotlin.properties.ReadOnlyProperty<android.content.Context,androidx.datastore.core.DataStore<T>> dataStore(String fileName, androidx.datastore.core.Serializer<T> serializer, optional androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler, optional kotlin.jvm.functions.Function1<? super android.content.Context,? extends java.util.List<? extends androidx.datastore.core.DataMigration<T>>> produceMigrations, optional kotlinx.coroutines.CoroutineScope scope);
+ }
+
+ public final class DataStoreFile {
+ method public static java.io.File dataStoreFile(android.content.Context, String fileName);
+ }
+
+}
+
+package androidx.datastore.migrations {
+
+ public final class SharedPreferencesMigration<T> implements androidx.datastore.core.DataMigration<T> {
+ ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String> keysToMigrate, optional kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String> keysToMigrate, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, optional java.util.Set<java.lang.String> keysToMigrate, optional kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, optional java.util.Set<java.lang.String> keysToMigrate, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+ method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public suspend Object? cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit>) throws java.io.IOException;
+ method public suspend Object? migrate(T currentData, kotlin.coroutines.Continuation<? super T>);
+ method public suspend Object? shouldMigrate(T currentData, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+ }
+
+ public final class SharedPreferencesView {
+ method public operator boolean contains(String key);
+ method public java.util.Map<java.lang.String,java.lang.Object?> getAll();
+ method public boolean getBoolean(String key, boolean defValue);
+ method public float getFloat(String key, float defValue);
+ method public int getInt(String key, int defValue);
+ method public long getLong(String key, long defValue);
+ method public String? getString(String key, optional String? defValue);
+ method public java.util.Set<java.lang.String>? getStringSet(String key, optional java.util.Set<java.lang.String>? defValues);
+ }
+
+}
+
diff --git a/datastore/datastore/api/restricted_current.ignore b/datastore/datastore/api/restricted_current.ignore
deleted file mode 100644
index a3186a11..0000000
--- a/datastore/datastore/api/restricted_current.ignore
+++ /dev/null
@@ -1,17 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.datastore.migrations.SharedPreferencesMigration#migrate(T, kotlin.coroutines.Continuation<? super T>) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.migrations.SharedPreferencesMigration.migrate(T currentData, kotlin.coroutines.Continuation<? super T> arg2)
-InvalidNullConversion: androidx.datastore.migrations.SharedPreferencesMigration#shouldMigrate(T, kotlin.coroutines.Continuation<? super java.lang.Boolean>) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter currentData in androidx.datastore.migrations.SharedPreferencesMigration.shouldMigrate(T currentData, kotlin.coroutines.Continuation<? super java.lang.Boolean> arg2)
-
-
-ParameterNameChange: androidx.datastore.migrations.SharedPreferencesMigration#cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
- Attempted to remove parameter name from parameter arg1 in androidx.datastore.migrations.SharedPreferencesMigration.cleanUp
-ParameterNameChange: androidx.datastore.migrations.SharedPreferencesMigration#migrate(T, kotlin.coroutines.Continuation<? super T>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.migrations.SharedPreferencesMigration.migrate
-ParameterNameChange: androidx.datastore.migrations.SharedPreferencesMigration#shouldMigrate(T, kotlin.coroutines.Continuation<? super java.lang.Boolean>) parameter #1:
- Attempted to remove parameter name from parameter arg2 in androidx.datastore.migrations.SharedPreferencesMigration.shouldMigrate
-
-
-RemovedClass: androidx.datastore.migrations.SharedPreferencesMigrationKt:
- Removed class androidx.datastore.migrations.SharedPreferencesMigrationKt
diff --git a/datastore/datastore/src/androidMain/kotlin/androidx/datastore/migrations/SharedPreferencesMigration.android.kt b/datastore/datastore/src/androidMain/kotlin/androidx/datastore/migrations/SharedPreferencesMigration.android.kt
index 00da050..436e6ad 100644
--- a/datastore/datastore/src/androidMain/kotlin/androidx/datastore/migrations/SharedPreferencesMigration.android.kt
+++ b/datastore/datastore/src/androidMain/kotlin/androidx/datastore/migrations/SharedPreferencesMigration.android.kt
@@ -193,9 +193,8 @@
private fun deleteSharedPreferences(context: Context, name: String) {
if (Build.VERSION.SDK_INT >= 24) {
- if (!Api24Impl.deleteSharedPreferences(context, name)) {
- throw IOException("Unable to delete SharedPreferences: $name")
- }
+ // Silently continue if we aren't able to delete the Shared Preferences. See b/195553816
+ Api24Impl.deleteSharedPreferences(context, name)
} else {
// Context.deleteSharedPreferences is SDK 24+, so we have to reproduce the definition
val prefsFile = getSharedPrefsFile(context, name)
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 0c69500..39f6b99 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -251,6 +251,8 @@
WARN: .*\/unzippedJvmSources\/androidx\/draganddrop\/DropHelper\.java:[0-9]+ Failed to resolve See <a href="https:\/\/developer\.android\.com\/guide\/topics\/ui\/drag-drop">Drag and drop<\/a> in DClass DropHelper
WARN: .*\/unzippedJvmSources\/androidx\/emoji\/text\/EmojiCompat\.java:[0-9]+ Missing @param tag for parameter `useEmojiAsDefaultStyle` in DFunction setUseEmojiAsDefaultStyle
WARN: .*\/unzippedJvmSources\/androidx\/emoji2\/text\/EmojiCompat\.java:[0-9]+ Missing @param tag for parameter `useEmojiAsDefaultStyle` in DFunction setUseEmojiAsDefaultStyle
+# Due to b/325460563
+ERROR: An attempt to write \$OUT_DIR/androidx/docs\-tip\-of\-tree/build/docs//reference/androidx/room/Room\.html several times!
WARN: .*\/unzippedJvmSources\/androidx\/fragment\/app\/Fragment\.java:[0-9]+ Missing @param tag for parameter `inflater` in DFunction onCreateOptionsMenu
WARN: .*\/unzippedJvmSources\/androidx\/graphics\/opengl\/egl\/EGLSpec\.kt:[0-9]+ Missing @param tag for parameter `context` in DFunction eglMakeCurrent
WARN: .*\/unzippedJvmSources\/androidx\/leanback\/app\/SearchFragment\.java:[0-9]+ Missing @param tag for parameter `query` in DFunction createArgs
@@ -284,6 +286,7 @@
WARN: .*\/unzippedJvmSources\/androidx\/paging\/compose\/LazyPagingItems\.kt:[0-9]+ Failed to resolve See PagingSource\.invalidate in DFunction refresh\. Did you mean PagingSource#invalidate\?
WARN: .*\/unzippedJvmSources\/androidx\/paging\/LoadStateAdapter\.kt:[0-9]+ Missing @param tag for parameter `holder` in DFunction onBindViewHolder
WARN: .*\/unzippedJvmSources\/androidx\/palette\/graphics\/Palette\.java:[0-9]+ Missing @param tag for parameter `target` in DFunction getColorForTarget
+WARN\: \$OUT_DIR\/androidx\/docs\-tip\-of\-tree\/build\/unzippedMultiplatformSources\/nativeMain\/androidx\/room\/RoomDatabase\.native\.kt\:UnknownLine Link does not resolve for \@throws kotlin\.IllegalArgumentException in DFunction valueOf\. Is it from a package that the containing file does not import\? Are docs inherited by an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\, e\.g\. \`\@throws java\.io\.IOException under some conditions\`\.
WARN: .*\/unzippedJvmSources\/androidx\/recyclerview\/widget\/BatchingListUpdateCallback\.java:[0-9]+ Missing @param tag for parameter `payload` in DFunction onChanged
WARN: .*\/unzippedJvmSources\/androidx\/recyclerview\/widget\/DefaultItemAnimator\.java:[0-9]+ Missing @param tag for parameter `fromX` in DFunction animateMove
WARN: .*\/unzippedJvmSources\/androidx\/recyclerview\/widget\/DefaultItemAnimator\.java:[0-9]+ Missing @param tag for parameter `fromY` in DFunction animateMove
@@ -393,6 +396,7 @@
WARN: .*\/unzippedJvmSources\/androidx\/wear\/watchface\/ComplicationSlot\.kt:[0-9]+ Unable to find reference @param supportedTypes in DClass Builder\. Are you trying to refer to something not visible to users\?
WARN: .*\/unzippedJvmSources\/androidx\/wear\/watchface\/Renderer\.kt:[0-9]+ Link does not resolve for @throws Renderer\.GlesException in DClass GlesRenderer2\. Is it from a package that the containing file does not import\? Are docs inherited by an un-documented override function, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name, e\.g\. `@throws java\.io\.IOException under some conditions`\.
WARN: .*\/unzippedJvmSources\/androidx\/wear\/watchface\/style\/CurrentUserStyleRepository\.kt:[0-9]+ Unable to find reference @param copySelectedOptions in DClass UserStyle\. Are you trying to refer to something not visible to users\?
+WARN\: \$OUT_DIR\/androidx\/docs\-tip\-of\-tree\/build\/unzippedMultiplatformSources\/nativeMain\/androidx\/lifecycle\/LifecycleRegistry\.native\.kt\:[0-9]+ Link does not resolve for \@throws IllegalStateException in DFunction addObserver\. Is it from a package that the containing file does not import\? Are docs inherited by an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\, e\.g\. \`\@throws java\.io\.IOException under some conditions\`\.
WARN: .*\/unzippedJvmSources\/androidx\/webkit\/CookieManagerCompat\.java:UnknownLine Missing @param tag for parameter `cookieManager` in DFunction getCookieInfo
WARN: .*\/unzippedJvmSources\/androidx\/webkit\/WebSettingsCompat\.java:[0-9]+ Missing @param tag for parameter `settings` in DFunction setSafeBrowsingEnabled
WARN: .*\/unzippedJvmSources\/androidx\/webkit\/WebSettingsCompat\.java:[0-9]+ Missing @param tag for parameter `settings` in DFunction setDisabledActionModeMenuItems
@@ -405,6 +409,8 @@
WARN: .*\/unzippedJvmSources\/androidx\/webkit\/WebViewCompat\.java:[0-9]+ Missing @param tag for parameter `webview` in DFunction removeWebMessageListener
WARN: .*\/unzippedJvmSources\/androidx\/work\/testing\/TestListenableWorkerBuilder\.kt:UnknownLine Missing @param tag for parameter `tags` in DFunction TestListenableWorkerBuilder
WARN: .*\/unzippedJvmSources\/androidx\/work\/testing\/TestWorkerBuilder\.kt:UnknownLine Missing @param tag for parameter `tags` in DFunction TestWorkerBuilder
+# Due to b/325460563
+ERROR: An attempt to write \$OUT_DIR/androidx/docs\-tip\-of\-tree/build/docs//reference/kotlin/androidx/room/Room\.html several times!
WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `placeholdersEnabled` in DFunction LoadInitialParams
WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedInitialKey` in DFunction LoadInitialParams
WARN: .*\/unzippedMultiplatformSources\/commonJvmAndroidMain\/androidx\/paging\/ItemKeyedDataSource\.jvm\.kt:[0-9]+ Missing @param tag for parameter `requestedLoadSize` in DFunction LoadInitialParams
@@ -601,3 +607,6 @@
docs-public:docs completing
docs-tip-of-tree:docs starting
docs-tip-of-tree:docs completing
+# Investigate more b/325465332
+# > Task :room:integration-tests:room-testapp-multiplatform:ksp.*
+i: \[ksp\] loaded provider\(s\): \[androidx\.room\.RoomKspProcessor\$Provider\]
diff --git a/development/update-verification-metadata.sh b/development/update-verification-metadata.sh
index 70b1472..59390f6 100755
--- a/development/update-verification-metadata.sh
+++ b/development/update-verification-metadata.sh
@@ -72,7 +72,9 @@
sed -i 's/\(trusted-key.*\)version="[^"]*"/\1/' gradle/verification-metadata.xml
# rename keyring
- mv gradle/verification-keyring-dryrun.keys gradle/verification-keyring.keys 2>/dev/null || true
+ if [ "$dryrun" == "true" ]; then
+ mv gradle/verification-keyring.dryrun.keys gradle/verification-keyring.keys
+ fi
}
regenerateVerificationMetadata
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 8859c54..70645c2 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -23,7 +23,7 @@
docsWithoutApiSince("androidx.ads:ads-identifier:1.0.0-alpha05")
docsWithoutApiSince("androidx.ads:ads-identifier-common:1.0.0-alpha05")
docsWithoutApiSince("androidx.ads:ads-identifier-provider:1.0.0-alpha05")
- kmpDocs("androidx.annotation:annotation:1.7.1")
+ kmpDocs("androidx.annotation:annotation:1.8.0-alpha01")
docs("androidx.annotation:annotation-experimental:1.4.0-rc01")
docs("androidx.appcompat:appcompat:1.7.0-alpha03")
docs("androidx.appcompat:appcompat-resources:1.7.0-alpha03")
@@ -39,16 +39,16 @@
docs("androidx.asynclayoutinflater:asynclayoutinflater:1.1.0-alpha01")
docs("androidx.asynclayoutinflater:asynclayoutinflater-appcompat:1.1.0-alpha01")
docs("androidx.autofill:autofill:1.3.0-alpha01")
- docs("androidx.benchmark:benchmark-common:1.2.3")
- docs("androidx.benchmark:benchmark-junit4:1.2.3")
- docs("androidx.benchmark:benchmark-macro:1.2.3")
- docs("androidx.benchmark:benchmark-macro-junit4:1.2.3")
+ docs("androidx.benchmark:benchmark-common:1.3.0-alpha01")
+ docs("androidx.benchmark:benchmark-junit4:1.3.0-alpha01")
+ docs("androidx.benchmark:benchmark-macro:1.3.0-alpha01")
+ docs("androidx.benchmark:benchmark-macro-junit4:1.3.0-alpha01")
docs("androidx.biometric:biometric:1.2.0-alpha05")
docs("androidx.biometric:biometric-ktx:1.2.0-alpha05")
samples("androidx.biometric:biometric-ktx-samples:1.2.0-alpha05")
docs("androidx.bluetooth:bluetooth:1.0.0-alpha02")
docs("androidx.bluetooth:bluetooth-testing:1.0.0-alpha02")
- docs("androidx.browser:browser:1.8.0-beta02")
+ docs("androidx.browser:browser:1.8.0-rc01")
docs("androidx.camera:camera-camera2:1.4.0-alpha04")
docs("androidx.camera:camera-core:1.4.0-alpha04")
docs("androidx.camera:camera-effects:1.4.0-alpha04")
@@ -69,59 +69,59 @@
docs("androidx.cardview:cardview:1.0.0")
kmpDocs("androidx.collection:collection:1.4.0")
docs("androidx.collection:collection-ktx:1.4.0-rc01")
- kmpDocs("androidx.compose.animation:animation:1.7.0-alpha02")
- kmpDocs("androidx.compose.animation:animation-core:1.7.0-alpha02")
- kmpDocs("androidx.compose.animation:animation-graphics:1.7.0-alpha02")
- samples("androidx.compose.animation:animation-samples:1.7.0-alpha02")
- samples("androidx.compose.animation:animation-core-samples:1.7.0-alpha02")
- samples("androidx.compose.animation:animation-graphics-samples:1.7.0-alpha02")
- kmpDocs("androidx.compose.foundation:foundation:1.7.0-alpha02")
- kmpDocs("androidx.compose.foundation:foundation-layout:1.7.0-alpha02")
- samples("androidx.compose.foundation:foundation-layout-samples:1.7.0-alpha02")
- samples("androidx.compose.foundation:foundation-samples:1.7.0-alpha02")
- kmpDocs("androidx.compose.material3:material3:1.2.0")
+ kmpDocs("androidx.compose.animation:animation:1.7.0-alpha03")
+ kmpDocs("androidx.compose.animation:animation-core:1.7.0-alpha03")
+ kmpDocs("androidx.compose.animation:animation-graphics:1.7.0-alpha03")
+ samples("androidx.compose.animation:animation-samples:1.7.0-alpha03")
+ samples("androidx.compose.animation:animation-core-samples:1.7.0-alpha03")
+ samples("androidx.compose.animation:animation-graphics-samples:1.7.0-alpha03")
+ kmpDocs("androidx.compose.foundation:foundation:1.7.0-alpha03")
+ kmpDocs("androidx.compose.foundation:foundation-layout:1.7.0-alpha03")
+ samples("androidx.compose.foundation:foundation-layout-samples:1.7.0-alpha03")
+ samples("androidx.compose.foundation:foundation-samples:1.7.0-alpha03")
+ kmpDocs("androidx.compose.material3:material3:1.3.0-alpha01")
kmpDocs("androidx.compose.material3:material3-adaptive:1.0.0-alpha05")
- kmpDocs("androidx.compose.material3:material3-adaptive-navigation-suite:1.0.0-alpha02")
- samples("androidx.compose.material3:material3-adaptive-navigation-suite-samples:1.2.0")
+ kmpDocs("androidx.compose.material3:material3-adaptive-navigation-suite:1.0.0-alpha04")
+ samples("androidx.compose.material3:material3-adaptive-navigation-suite-samples:1.3.0-alpha01")
samples("androidx.compose.material3:material3-adaptive-samples:1.2.0")
- samples("androidx.compose.material3:material3-samples:1.2.0")
- kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0")
- samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0")
- kmpDocs("androidx.compose.material:material:1.7.0-alpha02")
- kmpDocs("androidx.compose.material:material-icons-core:1.7.0-alpha02")
- samples("androidx.compose.material:material-icons-core-samples:1.7.0-alpha02")
- kmpDocs("androidx.compose.material:material-ripple:1.7.0-alpha02")
- samples("androidx.compose.material:material-samples:1.7.0-alpha02")
- kmpDocs("androidx.compose.runtime:runtime:1.7.0-alpha02")
- docs("androidx.compose.runtime:runtime-livedata:1.7.0-alpha02")
- samples("androidx.compose.runtime:runtime-livedata-samples:1.7.0-alpha02")
- docs("androidx.compose.runtime:runtime-rxjava2:1.7.0-alpha02")
- samples("androidx.compose.runtime:runtime-rxjava2-samples:1.7.0-alpha02")
- docs("androidx.compose.runtime:runtime-rxjava3:1.7.0-alpha02")
- samples("androidx.compose.runtime:runtime-rxjava3-samples:1.7.0-alpha02")
- kmpDocs("androidx.compose.runtime:runtime-saveable:1.7.0-alpha02")
- samples("androidx.compose.runtime:runtime-saveable-samples:1.7.0-alpha02")
- samples("androidx.compose.runtime:runtime-samples:1.7.0-alpha02")
+ samples("androidx.compose.material3:material3-samples:1.3.0-alpha01")
+ kmpDocs("androidx.compose.material3:material3-window-size-class:1.3.0-alpha01")
+ samples("androidx.compose.material3:material3-window-size-class-samples:1.3.0-alpha01")
+ kmpDocs("androidx.compose.material:material:1.7.0-alpha03")
+ kmpDocs("androidx.compose.material:material-icons-core:1.7.0-alpha03")
+ samples("androidx.compose.material:material-icons-core-samples:1.7.0-alpha03")
+ kmpDocs("androidx.compose.material:material-ripple:1.7.0-alpha03")
+ samples("androidx.compose.material:material-samples:1.7.0-alpha03")
+ kmpDocs("androidx.compose.runtime:runtime:1.7.0-alpha03")
+ docs("androidx.compose.runtime:runtime-livedata:1.7.0-alpha03")
+ samples("androidx.compose.runtime:runtime-livedata-samples:1.7.0-alpha03")
+ docs("androidx.compose.runtime:runtime-rxjava2:1.7.0-alpha03")
+ samples("androidx.compose.runtime:runtime-rxjava2-samples:1.7.0-alpha03")
+ docs("androidx.compose.runtime:runtime-rxjava3:1.7.0-alpha03")
+ samples("androidx.compose.runtime:runtime-rxjava3-samples:1.7.0-alpha03")
+ kmpDocs("androidx.compose.runtime:runtime-saveable:1.7.0-alpha03")
+ samples("androidx.compose.runtime:runtime-saveable-samples:1.7.0-alpha03")
+ samples("androidx.compose.runtime:runtime-samples:1.7.0-alpha03")
docs("androidx.compose.runtime:runtime-tracing:1.0.0-beta01")
- kmpDocs("androidx.compose.ui:ui:1.7.0-alpha02")
- kmpDocs("androidx.compose.ui:ui-geometry:1.7.0-alpha02")
- kmpDocs("androidx.compose.ui:ui-graphics:1.7.0-alpha02")
- samples("androidx.compose.ui:ui-graphics-samples:1.7.0-alpha02")
- kmpDocs("androidx.compose.ui:ui-test:1.7.0-alpha02")
- kmpDocs("androidx.compose.ui:ui-test-junit4:1.7.0-alpha02")
- samples("androidx.compose.ui:ui-test-samples:1.7.0-alpha02")
- kmpDocs("androidx.compose.ui:ui-text:1.7.0-alpha02")
- docs("androidx.compose.ui:ui-text-google-fonts:1.7.0-alpha02")
- samples("androidx.compose.ui:ui-text-samples:1.7.0-alpha02")
- kmpDocs("androidx.compose.ui:ui-tooling:1.7.0-alpha02")
- kmpDocs("androidx.compose.ui:ui-tooling-data:1.7.0-alpha02")
- kmpDocs("androidx.compose.ui:ui-tooling-preview:1.7.0-alpha02")
- kmpDocs("androidx.compose.ui:ui-unit:1.7.0-alpha02")
- samples("androidx.compose.ui:ui-unit-samples:1.7.0-alpha02")
- kmpDocs("androidx.compose.ui:ui-util:1.7.0-alpha02")
- docs("androidx.compose.ui:ui-viewbinding:1.7.0-alpha02")
- samples("androidx.compose.ui:ui-viewbinding-samples:1.7.0-alpha02")
- samples("androidx.compose.ui:ui-samples:1.7.0-alpha02")
+ kmpDocs("androidx.compose.ui:ui:1.7.0-alpha03")
+ kmpDocs("androidx.compose.ui:ui-geometry:1.7.0-alpha03")
+ kmpDocs("androidx.compose.ui:ui-graphics:1.7.0-alpha03")
+ samples("androidx.compose.ui:ui-graphics-samples:1.7.0-alpha03")
+ kmpDocs("androidx.compose.ui:ui-test:1.7.0-alpha03")
+ kmpDocs("androidx.compose.ui:ui-test-junit4:1.7.0-alpha03")
+ samples("androidx.compose.ui:ui-test-samples:1.7.0-alpha03")
+ kmpDocs("androidx.compose.ui:ui-text:1.7.0-alpha03")
+ docs("androidx.compose.ui:ui-text-google-fonts:1.7.0-alpha03")
+ samples("androidx.compose.ui:ui-text-samples:1.7.0-alpha03")
+ kmpDocs("androidx.compose.ui:ui-tooling:1.7.0-alpha03")
+ kmpDocs("androidx.compose.ui:ui-tooling-data:1.7.0-alpha03")
+ kmpDocs("androidx.compose.ui:ui-tooling-preview:1.7.0-alpha03")
+ kmpDocs("androidx.compose.ui:ui-unit:1.7.0-alpha03")
+ samples("androidx.compose.ui:ui-unit-samples:1.7.0-alpha03")
+ kmpDocs("androidx.compose.ui:ui-util:1.7.0-alpha03")
+ docs("androidx.compose.ui:ui-viewbinding:1.7.0-alpha03")
+ samples("androidx.compose.ui:ui-viewbinding-samples:1.7.0-alpha03")
+ samples("androidx.compose.ui:ui-samples:1.7.0-alpha03")
docs("androidx.concurrent:concurrent-futures:1.2.0-alpha02")
docs("androidx.concurrent:concurrent-futures-ktx:1.2.0-alpha02")
docs("androidx.constraintlayout:constraintlayout:2.2.0-alpha13")
@@ -200,19 +200,19 @@
docs("androidx.glance:glance-wear-tiles-preview:1.0.0-alpha06")
docs("androidx.graphics:graphics-core:1.0.0-beta01")
samples("androidx.graphics:graphics-core-samples:1.0.0-beta01")
- docs("androidx.graphics:graphics-path:1.0.0-beta02")
+ docs("androidx.graphics:graphics-path:1.0.0-rc01")
kmpDocs("androidx.graphics:graphics-shapes:1.0.0-alpha05")
docs("androidx.gridlayout:gridlayout:1.1.0-beta01")
docs("androidx.health.connect:connect-client:1.1.0-alpha07")
samples("androidx.health.connect:connect-client-samples:1.1.0-alpha07")
docs("androidx.health:health-services-client:1.1.0-alpha02")
docs("androidx.heifwriter:heifwriter:1.1.0-alpha02")
- docs("androidx.hilt:hilt-common:1.2.0-rc01")
- docs("androidx.hilt:hilt-navigation:1.2.0-rc01")
- docs("androidx.hilt:hilt-navigation-compose:1.2.0-rc01")
- samples("androidx.hilt:hilt-navigation-compose-samples:1.2.0-rc01")
- docs("androidx.hilt:hilt-navigation-fragment:1.2.0-rc01")
- docs("androidx.hilt:hilt-work:1.2.0-rc01")
+ docs("androidx.hilt:hilt-common:1.2.0")
+ docs("androidx.hilt:hilt-navigation:1.2.0")
+ docs("androidx.hilt:hilt-navigation-compose:1.2.0")
+ samples("androidx.hilt:hilt-navigation-compose-samples:1.2.0")
+ docs("androidx.hilt:hilt-navigation-fragment:1.2.0")
+ docs("androidx.hilt:hilt-work:1.2.0")
docs("androidx.input:input-motionprediction:1.0.0-beta03")
docs("androidx.interpolator:interpolator:1.0.0")
docs("androidx.javascriptengine:javascriptengine:1.0.0-beta01")
@@ -276,22 +276,22 @@
docsWithoutApiSince("androidx.media3:media3-transformer:1.3.0-rc01")
docsWithoutApiSince("androidx.media3:media3-ui:1.3.0-rc01")
docsWithoutApiSince("androidx.media3:media3-ui-leanback:1.3.0-rc01")
- docs("androidx.mediarouter:mediarouter:1.7.0-alpha02")
- docs("androidx.mediarouter:mediarouter-testing:1.7.0-alpha02")
+ docs("androidx.mediarouter:mediarouter:1.7.0-beta01")
+ docs("androidx.mediarouter:mediarouter-testing:1.7.0-beta01")
docs("androidx.metrics:metrics-performance:1.0.0-beta01")
- docs("androidx.navigation:navigation-common:2.8.0-alpha02")
- docs("androidx.navigation:navigation-common-ktx:2.8.0-alpha02")
- docs("androidx.navigation:navigation-compose:2.8.0-alpha02")
- samples("androidx.navigation:navigation-compose-samples:2.8.0-alpha02")
- docs("androidx.navigation:navigation-dynamic-features-fragment:2.8.0-alpha02")
- docs("androidx.navigation:navigation-dynamic-features-runtime:2.8.0-alpha02")
- docs("androidx.navigation:navigation-fragment:2.8.0-alpha02")
- docs("androidx.navigation:navigation-fragment-ktx:2.8.0-alpha02")
- docs("androidx.navigation:navigation-runtime:2.8.0-alpha02")
- docs("androidx.navigation:navigation-runtime-ktx:2.8.0-alpha02")
- docs("androidx.navigation:navigation-testing:2.8.0-alpha02")
- docs("androidx.navigation:navigation-ui:2.8.0-alpha02")
- docs("androidx.navigation:navigation-ui-ktx:2.8.0-alpha02")
+ docs("androidx.navigation:navigation-common:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-common-ktx:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-compose:2.8.0-alpha03")
+ samples("androidx.navigation:navigation-compose-samples:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-dynamic-features-fragment:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-dynamic-features-runtime:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-fragment:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-fragment-ktx:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-runtime:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-runtime-ktx:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-testing:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-ui:2.8.0-alpha03")
+ docs("androidx.navigation:navigation-ui-ktx:2.8.0-alpha03")
kmpDocs("androidx.paging:paging-common:3.3.0-alpha03")
docs("androidx.paging:paging-common-ktx:3.3.0-alpha03")
kmpDocs("androidx.paging:paging-compose:3.3.0-alpha03")
@@ -378,7 +378,7 @@
docsWithoutApiSince("androidx.test.ext:junit-ktx:1.2.0-alpha03")
docsWithoutApiSince("androidx.test.ext:truth:1.6.0-alpha03")
docsWithoutApiSince("androidx.test.services:storage:1.5.0-alpha03")
- docsWithoutApiSince("androidx.test.uiautomator:uiautomator:2.3.0-rc01")
+ docsWithoutApiSince("androidx.test.uiautomator:uiautomator:2.3.0")
// androidx.textclassifier is not hosted in androidx
docsWithoutApiSince("androidx.textclassifier:textclassifier:1.0.0-alpha04")
docs("androidx.tracing:tracing:1.3.0-alpha02")
@@ -399,16 +399,16 @@
docs("androidx.versionedparcelable:versionedparcelable:1.2.0")
docs("androidx.viewpager2:viewpager2:1.1.0-beta02")
docs("androidx.viewpager:viewpager:1.1.0-alpha01")
- docs("androidx.wear.compose:compose-foundation:1.4.0-alpha02")
- samples("androidx.wear.compose:compose-foundation-samples:1.4.0-alpha02")
- docs("androidx.wear.compose:compose-material:1.4.0-alpha02")
- docs("androidx.wear.compose:compose-material-core:1.4.0-alpha02")
- samples("androidx.wear.compose:compose-material-samples:1.4.0-alpha02")
- docs("androidx.wear.compose:compose-material3:1.0.0-alpha17")
- samples("androidx.wear.compose:compose-material3-samples:1.4.0-alpha02")
- docs("androidx.wear.compose:compose-navigation:1.4.0-alpha02")
- samples("androidx.wear.compose:compose-navigation-samples:1.4.0-alpha02")
- docs("androidx.wear.compose:compose-ui-tooling:1.4.0-alpha02")
+ docs("androidx.wear.compose:compose-foundation:1.4.0-alpha03")
+ samples("androidx.wear.compose:compose-foundation-samples:1.4.0-alpha03")
+ docs("androidx.wear.compose:compose-material:1.4.0-alpha03")
+ docs("androidx.wear.compose:compose-material-core:1.4.0-alpha03")
+ samples("androidx.wear.compose:compose-material-samples:1.4.0-alpha03")
+ docs("androidx.wear.compose:compose-material3:1.0.0-alpha18")
+ samples("androidx.wear.compose:compose-material3-samples:1.4.0-alpha03")
+ docs("androidx.wear.compose:compose-navigation:1.4.0-alpha03")
+ samples("androidx.wear.compose:compose-navigation-samples:1.4.0-alpha03")
+ docs("androidx.wear.compose:compose-ui-tooling:1.4.0-alpha03")
docs("androidx.wear.protolayout:protolayout:1.1.0")
docs("androidx.wear.protolayout:protolayout-expression:1.1.0")
docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.1.0")
@@ -448,7 +448,7 @@
docs("androidx.wear:wear-remote-interactions:1.1.0-alpha02")
samples("androidx.wear:wear-remote-interactions-samples:1.1.0-alpha02")
docs("androidx.wear:wear-tooling-preview:1.0.0")
- docs("androidx.webkit:webkit:1.11.0-alpha01")
+ docs("androidx.webkit:webkit:1.11.0-alpha02")
docs("androidx.window.extensions.core:core:1.0.0")
docs("androidx.window:window:1.3.0-alpha02")
stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
diff --git a/docs-public/package-lists/checkerframework/package-list b/docs-public/package-lists/checkerframework/package-list
new file mode 100644
index 0000000..aeaec19
--- /dev/null
+++ b/docs-public/package-lists/checkerframework/package-list
@@ -0,0 +1,115 @@
+org.checkerframework.checker.builder.qual
+org.checkerframework.checker.calledmethods
+org.checkerframework.checker.calledmethods.builder
+org.checkerframework.checker.calledmethods.qual
+org.checkerframework.checker.compilermsgs
+org.checkerframework.checker.compilermsgs.qual
+org.checkerframework.checker.fenum
+org.checkerframework.checker.fenum.qual
+org.checkerframework.checker.formatter
+org.checkerframework.checker.formatter.qual
+org.checkerframework.checker.formatter.util
+org.checkerframework.checker.guieffect
+org.checkerframework.checker.guieffect.qual
+org.checkerframework.checker.i18n
+org.checkerframework.checker.i18n.qual
+org.checkerframework.checker.i18nformatter
+org.checkerframework.checker.i18nformatter.qual
+org.checkerframework.checker.i18nformatter.util
+org.checkerframework.checker.index
+org.checkerframework.checker.index.inequality
+org.checkerframework.checker.index.lowerbound
+org.checkerframework.checker.index.qual
+org.checkerframework.checker.index.samelen
+org.checkerframework.checker.index.searchindex
+org.checkerframework.checker.index.substringindex
+org.checkerframework.checker.index.upperbound
+org.checkerframework.checker.initialization
+org.checkerframework.checker.initialization.qual
+org.checkerframework.checker.interning
+org.checkerframework.checker.interning.qual
+org.checkerframework.checker.lock
+org.checkerframework.checker.lock.qual
+org.checkerframework.checker.mustcall
+org.checkerframework.checker.mustcall.qual
+org.checkerframework.checker.nullness
+org.checkerframework.checker.nullness.qual
+org.checkerframework.checker.nullness.util
+org.checkerframework.checker.optional
+org.checkerframework.checker.optional.qual
+org.checkerframework.checker.optional.util
+org.checkerframework.checker.propkey
+org.checkerframework.checker.propkey.qual
+org.checkerframework.checker.regex
+org.checkerframework.checker.regex.qual
+org.checkerframework.checker.regex.util
+org.checkerframework.checker.resourceleak
+org.checkerframework.checker.signature
+org.checkerframework.checker.signature.qual
+org.checkerframework.checker.signedness
+org.checkerframework.checker.signedness.qual
+org.checkerframework.checker.signedness.util
+org.checkerframework.checker.tainting
+org.checkerframework.checker.tainting.qual
+org.checkerframework.checker.units
+org.checkerframework.checker.units.qual
+org.checkerframework.checker.units.util
+org.checkerframework.common.accumulation
+org.checkerframework.common.aliasing
+org.checkerframework.common.aliasing.qual
+org.checkerframework.common.basetype
+org.checkerframework.common.initializedfields
+org.checkerframework.common.initializedfields.qual
+org.checkerframework.common.reflection
+org.checkerframework.common.reflection.qual
+org.checkerframework.common.returnsreceiver
+org.checkerframework.common.returnsreceiver.qual
+org.checkerframework.common.subtyping
+org.checkerframework.common.subtyping.qual
+org.checkerframework.common.util
+org.checkerframework.common.util.count
+org.checkerframework.common.util.count.report
+org.checkerframework.common.util.count.report.qual
+org.checkerframework.common.util.debug
+org.checkerframework.common.value
+org.checkerframework.common.value.qual
+org.checkerframework.common.value.util
+org.checkerframework.common.wholeprograminference
+org.checkerframework.common.wholeprograminference.scenelib
+org.checkerframework.dataflow.analysis
+org.checkerframework.dataflow.busyexpr
+org.checkerframework.dataflow.cfg
+org.checkerframework.dataflow.cfg.block
+org.checkerframework.dataflow.cfg.builder
+org.checkerframework.dataflow.cfg.node
+org.checkerframework.dataflow.cfg.playground
+org.checkerframework.dataflow.cfg.visualize
+org.checkerframework.dataflow.constantpropagation
+org.checkerframework.dataflow.expression
+org.checkerframework.dataflow.livevariable
+Classes using for live variable analysis.
+org.checkerframework.dataflow.qual
+org.checkerframework.dataflow.reachingdef
+org.checkerframework.dataflow.util
+org.checkerframework.framework.ajava
+org.checkerframework.framework.flow
+org.checkerframework.framework.qual
+org.checkerframework.framework.source
+org.checkerframework.framework.stub
+org.checkerframework.framework.test
+org.checkerframework.framework.test.diagnostics
+org.checkerframework.framework.type
+org.checkerframework.framework.type.poly
+org.checkerframework.framework.type.treeannotator
+org.checkerframework.framework.type.typeannotator
+org.checkerframework.framework.type.visitor
+org.checkerframework.framework.util
+org.checkerframework.framework.util.defaults
+org.checkerframework.framework.util.dependenttypes
+org.checkerframework.framework.util.element
+org.checkerframework.framework.util.typeinference
+org.checkerframework.framework.util.typeinference.constraint
+org.checkerframework.framework.util.typeinference.solver
+org.checkerframework.javacutil
+org.checkerframework.javacutil.trees
+org.checkerframework.taglet
diff --git a/docs-public/package-lists/chromium/package-list b/docs-public/package-lists/chromium/package-list
new file mode 100644
index 0000000..a36b61d
--- /dev/null
+++ b/docs-public/package-lists/chromium/package-list
@@ -0,0 +1,2 @@
+org.chromium.net
+org.chromium.net.apihelpers
diff --git a/docs-public/package-lists/errorprone/package-list b/docs-public/package-lists/errorprone/package-list
new file mode 100644
index 0000000..8d0f192
--- /dev/null
+++ b/docs-public/package-lists/errorprone/package-list
@@ -0,0 +1,35 @@
+com.google.errorprone
+com.google.errorprone.annotations
+com.google.errorprone.annotations.concurrent
+com.google.errorprone.apply
+com.google.errorprone.bugpatterns
+com.google.errorprone.bugpatterns.android
+com.google.errorprone.bugpatterns.apidiff
+com.google.errorprone.bugpatterns.argumentselectiondefects
+com.google.errorprone.bugpatterns.checkreturnvalue
+com.google.errorprone.bugpatterns.collectionincompatibletype
+com.google.errorprone.bugpatterns.flogger
+com.google.errorprone.bugpatterns.formatstring
+com.google.errorprone.bugpatterns.inject
+com.google.errorprone.bugpatterns.inject.dagger
+com.google.errorprone.bugpatterns.inject.guice
+com.google.errorprone.bugpatterns.inlineme
+com.google.errorprone.bugpatterns.javadoc
+com.google.errorprone.bugpatterns.nullness
+com.google.errorprone.bugpatterns.overloading
+com.google.errorprone.bugpatterns.threadsafety
+com.google.errorprone.bugpatterns.time
+com.google.errorprone.dataflow
+com.google.errorprone.dataflow.nullnesspropagation
+com.google.errorprone.dataflow.nullnesspropagation.inference
+com.google.errorprone.fixes
+com.google.errorprone.matchers
+com.google.errorprone.matchers.method
+com.google.errorprone.names
+com.google.errorprone.predicates
+com.google.errorprone.predicates.type
+com.google.errorprone.refaster
+com.google.errorprone.refaster.annotation
+com.google.errorprone.scanner
+com.google.errorprone.suppliers
+com.google.errorprone.util
diff --git a/docs-public/package-lists/findbugs/package-list b/docs-public/package-lists/findbugs/package-list
new file mode 100644
index 0000000..cc08202
--- /dev/null
+++ b/docs-public/package-lists/findbugs/package-list
@@ -0,0 +1,3 @@
+javax.annotation
+javax.annotation.concurrent
+javax.annotation.meta
diff --git a/docs-public/package-lists/gms/package-list b/docs-public/package-lists/gms/package-list
new file mode 100644
index 0000000..34cbf8b
--- /dev/null
+++ b/docs-public/package-lists/gms/package-list
@@ -0,0 +1,132 @@
+com.google.android.gms.actions
+com.google.android.gms.ads
+com.google.android.gms.ads.admanager
+com.google.android.gms.ads.appopen
+com.google.android.gms.ads.formats
+com.google.android.gms.ads.h5
+com.google.android.gms.ads.identifier
+com.google.android.gms.ads.initialization
+com.google.android.gms.ads.interstitial
+com.google.android.gms.ads.mediation
+com.google.android.gms.ads.mediation.customevent
+com.google.android.gms.ads.mediation.rtb
+com.google.android.gms.ads.nativead
+com.google.android.gms.ads.query
+com.google.android.gms.ads.rewarded
+com.google.android.gms.ads.rewardedinterstitial
+com.google.android.gms.ads.search
+com.google.android.gms.analytics
+com.google.android.gms.analytics
+com.google.android.gms.analytics.ecommerce
+com.google.android.gms.appindex
+com.google.android.gms.appindex.builders
+com.google.android.gms.appindexing
+com.google.android.gms.appset
+com.google.android.gms.auth
+com.google.android.gms.auth.account
+com.google.android.gms.auth.api
+com.google.android.gms.auth.api.accounttransfer
+com.google.android.gms.auth.api.credentials
+com.google.android.gms.auth.api.identity
+com.google.android.gms.auth.api.phone
+com.google.android.gms.auth.api.signin
+com.google.android.gms.auth.api.signin
+com.google.android.gms.auth.blockstore
+com.google.android.gms.auth.managed.password
+com.google.android.gms.awareness
+com.google.android.gms.awareness.fence
+com.google.android.gms.awareness.snapshot
+com.google.android.gms.awareness.state
+com.google.android.gms.cast
+com.google.android.gms.cast.framework
+com.google.android.gms.cast.framework.media
+com.google.android.gms.cast.framework.media.uicontroller
+com.google.android.gms.cast.framework.media.widget
+com.google.android.gms.cast.tv
+com.google.android.gms.cast.tv.cac
+com.google.android.gms.cast.tv.media
+com.google.android.gms.cloudmessaging
+com.google.android.gms.common
+com.google.android.gms.common
+com.google.android.gms.common.api
+com.google.android.gms.common.api
+com.google.android.gms.common.data
+com.google.android.gms.common.images
+com.google.android.gms.common.moduleinstall
+com.google.android.gms.common.testing
+com.google.android.gms.deviceperformance
+com.google.android.gms.drive
+com.google.android.gms.drive.events
+com.google.android.gms.drive.metadata
+com.google.android.gms.drive.query
+com.google.android.gms.drive.widget
+com.google.android.gms.dtdi
+com.google.android.gms.dtdi.analytics
+com.google.android.gms.dtdi.core
+com.google.android.gms.fido
+com.google.android.gms.fido.common
+com.google.android.gms.fido.fido2
+com.google.android.gms.fido.fido2.api.common
+com.google.android.gms.fido.u2f
+com.google.android.gms.fido.u2f.api.common
+com.google.android.gms.fido.u2f.api.messagebased
+com.google.android.gms.fitness
+com.google.android.gms.fitness.data
+com.google.android.gms.fitness.request
+com.google.android.gms.fitness.result
+com.google.android.gms.fitness.service
+com.google.android.gms.games
+com.google.android.gms.games
+com.google.android.gms.games.achievement
+com.google.android.gms.games.achievement
+com.google.android.gms.games.event
+com.google.android.gms.games.event
+com.google.android.gms.games.leaderboard
+com.google.android.gms.games.leaderboard
+com.google.android.gms.games.snapshot
+com.google.android.gms.games.snapshot
+com.google.android.gms.games.stats
+com.google.android.gms.games.stats
+com.google.android.gms.games.video
+com.google.android.gms.games.video
+com.google.android.gms.identity.intents
+com.google.android.gms.identity.intents.model
+com.google.android.gms.iid
+com.google.android.gms.instantapps
+com.google.android.gms.location
+com.google.android.gms.location.places
+com.google.android.gms.maps
+com.google.android.gms.maps.model
+com.google.android.gms.measurement
+com.google.android.gms.measurement
+com.google.android.gms.nearby
+com.google.android.gms.nearby.connection
+com.google.android.gms.nearby.exposurenotification
+com.google.android.gms.nearby.messages
+com.google.android.gms.nearby.messages.audio
+com.google.android.gms.nearby.uwb
+com.google.android.gms.net
+com.google.android.gms.oss.licenses
+com.google.android.gms.panorama
+com.google.android.gms.pay
+com.google.android.gms.recaptcha
+com.google.android.gms.safetynet
+com.google.android.gms.search
+com.google.android.gms.security
+com.google.android.gms.stats
+com.google.android.gms.streamprotect
+com.google.android.gms.tagmanager
+com.google.android.gms.tagmanager
+com.google.android.gms.tasks
+com.google.android.gms.tflite.acceleration
+com.google.android.gms.tflite.client
+com.google.android.gms.tflite.gpu.support
+com.google.android.gms.tflite.java
+com.google.android.gms.vision
+com.google.android.gms.vision.barcode
+com.google.android.gms.vision.face
+com.google.android.gms.vision.text
+com.google.android.gms.wallet
+com.google.android.gms.wallet.button
+com.google.android.gms.wallet.wobs
+com.google.android.gms.wearable
diff --git a/docs-public/package-lists/interactive-media/package-list b/docs-public/package-lists/interactive-media/package-list
new file mode 100644
index 0000000..421bf27
--- /dev/null
+++ b/docs-public/package-lists/interactive-media/package-list
@@ -0,0 +1,3 @@
+com.google.ads.interactivemedia.v3.api
+com.google.ads.interactivemedia.v3.api.player
+com.google.ads.interactivemedia.v3.api.signals
diff --git a/docs-public/package-lists/javaee7/package-list b/docs-public/package-lists/javaee7/package-list
new file mode 100644
index 0000000..f87ec74
--- /dev/null
+++ b/docs-public/package-lists/javaee7/package-list
@@ -0,0 +1,133 @@
+javax.annotation
+javax.annotation.security
+javax.annotation.sql
+javax.batch.api
+javax.batch.api.chunk
+javax.batch.api.chunk.listener
+javax.batch.api.listener
+javax.batch.api.partition
+javax.batch.operations
+javax.batch.runtime
+javax.batch.runtime.context
+javax.decorator
+javax.ejb
+javax.ejb.embeddable
+javax.ejb.spi
+javax.el
+javax.enterprise.concurrent
+javax.enterprise.context
+javax.enterprise.context.spi
+javax.enterprise.deploy.model
+javax.enterprise.deploy.model.exceptions
+javax.enterprise.deploy.shared
+javax.enterprise.deploy.shared.factories
+javax.enterprise.deploy.spi
+javax.enterprise.deploy.spi.exceptions
+javax.enterprise.deploy.spi.factories
+javax.enterprise.deploy.spi.status
+javax.enterprise.event
+javax.enterprise.inject
+javax.enterprise.inject.spi
+javax.enterprise.util
+javax.faces
+javax.faces.application
+javax.faces.bean
+javax.faces.component
+javax.faces.component.behavior
+javax.faces.component.html
+javax.faces.component.visit
+javax.faces.context
+javax.faces.convert
+javax.faces.el
+javax.faces.event
+javax.faces.flow
+javax.faces.flow.builder
+javax.faces.lifecycle
+javax.faces.model
+javax.faces.render
+javax.faces.validator
+javax.faces.view
+javax.faces.view.facelets
+javax.faces.webapp
+javax.inject
+javax.interceptor
+javax.jms
+javax.json
+javax.json.spi
+javax.json.stream
+javax.jws
+javax.jws.soap
+javax.mail
+javax.mail.event
+javax.mail.internet
+javax.mail.search
+javax.mail.util
+javax.management.j2ee
+javax.management.j2ee.statistics
+javax.persistence
+javax.persistence.criteria
+javax.persistence.metamodel
+javax.persistence.spi
+javax.resource
+javax.resource.cci
+javax.resource.spi
+javax.resource.spi.endpoint
+javax.resource.spi.security
+javax.resource.spi.work
+javax.security.auth.message
+javax.security.auth.message.callback
+javax.security.auth.message.config
+javax.security.auth.message.module
+javax.security.jacc
+javax.servlet
+javax.servlet.annotation
+javax.servlet.descriptor
+javax.servlet.http
+javax.servlet.jsp
+javax.servlet.jsp.el
+javax.servlet.jsp.jstl.core
+javax.servlet.jsp.jstl.fmt
+javax.servlet.jsp.jstl.sql
+javax.servlet.jsp.jstl.tlv
+javax.servlet.jsp.tagext
+javax.transaction
+javax.transaction.xa
+javax.validation
+javax.validation.bootstrap
+javax.validation.constraints
+javax.validation.constraintvalidation
+javax.validation.executable
+javax.validation.groups
+javax.validation.metadata
+javax.validation.spi
+javax.websocket
+javax.websocket.server
+javax.ws.rs
+javax.ws.rs.client
+javax.ws.rs.container
+javax.ws.rs.core
+javax.ws.rs.ext
+javax.xml.bind
+javax.xml.bind.annotation
+javax.xml.bind.annotation.adapters
+javax.xml.bind.attachment
+javax.xml.bind.helpers
+javax.xml.bind.util
+javax.xml.registry
+javax.xml.registry.infomodel
+javax.xml.rpc
+javax.xml.rpc.encoding
+javax.xml.rpc.handler
+javax.xml.rpc.handler.soap
+javax.xml.rpc.holders
+javax.xml.rpc.server
+javax.xml.rpc.soap
+javax.xml.soap
+javax.xml.ws
+javax.xml.ws.handler
+javax.xml.ws.handler.soap
+javax.xml.ws.http
+javax.xml.ws.soap
+javax.xml.ws.spi
+javax.xml.ws.spi.http
+javax.xml.ws.wsaddressing
diff --git a/docs-public/package-lists/javase8/package-list b/docs-public/package-lists/javase8/package-list
new file mode 100644
index 0000000..339ee9b
--- /dev/null
+++ b/docs-public/package-lists/javase8/package-list
@@ -0,0 +1,129 @@
+java.awt
+java.awt.color
+java.awt.datatransfer
+java.awt.dnd
+java.awt.event
+java.awt.font
+java.awt.geom
+java.awt.im
+java.awt.im.spi
+java.awt.image
+java.awt.image.renderable
+java.awt.print
+javax.accessibility
+javax.activation
+javax.activity
+javax.annotation
+javax.annotation.processing
+javax.crypto
+javax.crypto.interfaces
+javax.crypto.spec
+javax.imageio
+javax.imageio.event
+javax.imageio.metadata
+javax.imageio.plugins.bmp
+javax.imageio.plugins.jpeg
+javax.imageio.spi
+javax.imageio.stream
+javax.jws
+javax.jws.soap
+javax.lang.model
+javax.lang.model.element
+javax.lang.model.type
+javax.lang.model.util
+javax.management
+javax.management.loading
+javax.management.modelmbean
+javax.management.monitor
+javax.management.openmbean
+javax.management.relation
+javax.management.remote
+javax.management.remote.rmi
+javax.management.timer
+javax.naming
+javax.naming.directory
+javax.naming.event
+javax.naming.ldap
+javax.naming.spi
+javax.net
+javax.net.ssl
+javax.print
+javax.print.attribute
+javax.print.attribute.standard
+javax.print.event
+javax.rmi
+javax.rmi.CORBA
+javax.rmi.ssl
+javax.script
+javax.security.auth
+javax.security.auth.callback
+javax.security.auth.kerberos
+javax.security.auth.login
+javax.security.auth.spi
+javax.security.auth.x500
+javax.security.cert
+javax.security.sasl
+javax.sound.midi
+javax.sound.midi.spi
+javax.sound.sampled
+javax.sound.sampled.spi
+javax.sql
+javax.sql.rowset
+javax.sql.rowset.serial
+javax.sql.rowset.spi
+javax.swing
+javax.swing.border
+javax.swing.colorchooser
+javax.swing.event
+javax.swing.filechooser
+javax.swing.plaf
+javax.swing.plaf.basic
+javax.swing.plaf.metal
+javax.swing.plaf.multi
+javax.swing.plaf.nimbus
+javax.swing.plaf.synth
+javax.swing.table
+javax.swing.text
+javax.swing.text.html
+javax.swing.text.html.parser
+javax.swing.text.rtf
+javax.swing.tree
+javax.swing.undo
+javax.tools
+javax.transaction
+javax.transaction.xa
+javax.xml
+javax.xml.bind
+javax.xml.bind.annotation
+javax.xml.bind.annotation.adapters
+javax.xml.bind.attachment
+javax.xml.bind.helpers
+javax.xml.bind.util
+javax.xml.crypto
+javax.xml.crypto.dom
+javax.xml.crypto.dsig
+javax.xml.crypto.dsig.dom
+javax.xml.crypto.dsig.keyinfo
+javax.xml.crypto.dsig.spec
+javax.xml.datatype
+javax.xml.namespace
+javax.xml.parsers
+javax.xml.soap
+javax.xml.stream
+javax.xml.stream.events
+javax.xml.stream.util
+javax.xml.transform
+javax.xml.transform.dom
+javax.xml.transform.sax
+javax.xml.transform.stax
+javax.xml.transform.stream
+javax.xml.validation
+javax.xml.ws
+javax.xml.ws.handler
+javax.xml.ws.handler.soap
+javax.xml.ws.http
+javax.xml.ws.soap
+javax.xml.ws.spi
+javax.xml.ws.spi.http
+javax.xml.ws.wsaddressing
+javax.xml.xpath
diff --git a/docs-public/package-lists/okhttp3/package-list b/docs-public/package-lists/okhttp3/package-list
new file mode 100644
index 0000000..897e94e
--- /dev/null
+++ b/docs-public/package-lists/okhttp3/package-list
@@ -0,0 +1,43 @@
+$dokka.format:html-v1
+$dokka.linkExtension:html
+$dokka.location:okhttp3/Headers/byteCount/#/PointingToDeclaration/okhttp/okhttp3/-headers/[non-jvm]byte-count.html
+$dokka.location:okhttp3/Headers/hashCode/#/PointingToDeclaration/okhttp/okhttp3/-headers/[non-jvm]hash-code.html
+$dokka.location:okhttp3/HttpUrl.Builder/toString/#/PointingToDeclaration/okhttp/okhttp3/-http-url/-builder/[non-jvm]to-string.html
+$dokka.location:okhttp3/HttpUrl/equals/#kotlin.Any?/PointingToDeclaration/okhttp/okhttp3/-http-url/[non-jvm]equals.html
+$dokka.location:okhttp3/HttpUrl/hashCode/#/PointingToDeclaration/okhttp/okhttp3/-http-url/[non-jvm]hash-code.html
+$dokka.location:okhttp3/HttpUrl/toString/#/PointingToDeclaration/okhttp/okhttp3/-http-url/[non-jvm]to-string.html
+$dokka.location:okhttp3/MediaType/equals/#kotlin.Any?/PointingToDeclaration/okhttp/okhttp3/-media-type/[non-jvm]equals.html
+$dokka.location:okhttp3/MediaType/hashCode/#/PointingToDeclaration/okhttp/okhttp3/-media-type/[non-jvm]hash-code.html
+$dokka.location:okhttp3/Request/toString/#/PointingToDeclaration/okhttp/okhttp3/-request/[non-jvm]to-string.html
+$dokka.location:okhttp3/TlsVersion/entries/#/PointingToDeclaration/okhttp/okhttp3/-tls-version/[non-jvm]entries.html
+$dokka.location:okhttp3/TlsVersion/valueOf/#kotlin.String/PointingToDeclaration/okhttp/okhttp3/-tls-version/[non-jvm]value-of.html
+$dokka.location:okhttp3/TlsVersion/values/#/PointingToDeclaration/okhttp/okhttp3/-tls-version/[non-jvm]values.html
+module:logging-interceptor
+okhttp3.logging
+module:mockwebserver
+okhttp3.mockwebserver
+module:mockwebserver3
+mockwebserver3
+module:mockwebserver3-junit4
+mockwebserver3.junit4
+module:okcurl
+okhttp3.curl
+okhttp3.curl.logging
+module:okhttp
+okhttp3
+module:okhttp-android
+okhttp3.android
+module:okhttp-brotli
+okhttp3.brotli
+module:okhttp-coroutines
+okhttp3
+module:okhttp-dnsoverhttps
+okhttp3.dnsoverhttps
+module:okhttp-java-net-cookiejar
+okhttp3.java.net.cookiejar
+module:okhttp-sse
+okhttp3.sse
+module:okhttp-tls
+okhttp3.tls
+module:okhttp-urlconnection
+okhttp3
diff --git a/docs-public/package-lists/robolectric/package-list b/docs-public/package-lists/robolectric/package-list
new file mode 100644
index 0000000..be0c1f1
--- /dev/null
+++ b/docs-public/package-lists/robolectric/package-list
@@ -0,0 +1,41 @@
+org.robolectric
+org.robolectric.android
+org.robolectric.android.controller
+org.robolectric.android.internal
+org.robolectric.android.util.concurrent
+org.robolectric.annotation
+org.robolectric.annotation.experimental
+org.robolectric.annotation.internal
+org.robolectric.annotation.processing
+org.robolectric.annotation.processing.generator
+org.robolectric.annotation.processing.validator
+org.robolectric.config
+org.robolectric.errorprone.bugpatterns
+org.robolectric.fakes
+org.robolectric.integrationtests.jacoco
+org.robolectric.interceptors
+org.robolectric.internal
+org.robolectric.internal.bytecode
+org.robolectric.internal.dependency
+org.robolectric.junit.rules
+org.robolectric.manifest
+org.robolectric.nativeruntime
+org.robolectric.pluginapi
+org.robolectric.pluginapi.config
+org.robolectric.pluginapi.perf
+org.robolectric.plugins
+org.robolectric.preinstrumented
+org.robolectric.res
+org.robolectric.res.android
+org.robolectric.res.builder
+org.robolectric.sandbox
+org.robolectric.shadow.api
+org.robolectric.shadows
+org.robolectric.shadows.gms
+org.robolectric.shadows.gms.common
+org.robolectric.shadows.httpclient
+org.robolectric.shadows.multidex
+org.robolectric.shadows.util
+org.robolectric.util
+org.robolectric.util.inject
+org.robolectric.util.reflector
diff --git a/docs-public/package-lists/truth/package-list b/docs-public/package-lists/truth/package-list
new file mode 100644
index 0000000..32450c0
--- /dev/null
+++ b/docs-public/package-lists/truth/package-list
@@ -0,0 +1,3 @@
+com.google.common.truth
+com.google.common.truth.extensions.proto
+com.google.common.truth.extensions.re2j
diff --git a/docs-public/package-lists/wearable/package-list b/docs-public/package-lists/wearable/package-list
new file mode 100644
index 0000000..f35eeb4
--- /dev/null
+++ b/docs-public/package-lists/wearable/package-list
@@ -0,0 +1,17 @@
+android.support.wearable
+android.support.wearable.activity
+android.support.wearable.authentication
+android.support.wearable.companion
+android.support.wearable.complications
+android.support.wearable.complications.rendering
+android.support.wearable.input
+android.support.wearable.media
+android.support.wearable.notifications
+android.support.wearable.phone
+android.support.wearable.provider
+android.support.wearable.standalone
+android.support.wearable.view
+android.support.wearable.view.drawer
+android.support.wearable.watchface
+com.google.android.wearable.intent
+com.google.android.wearable.playstore
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index ef4b029..2022e17 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -98,6 +98,8 @@
kmpDocs(project(":compose:material:material-icons-core"))
samples(project(":compose:material:material-icons-core:material-icons-core-samples"))
kmpDocs(project(":compose:material:material-ripple"))
+ docs(project(":compose:material:material-navigation"))
+ samples(project(":compose:material:material-navigation-samples"))
samples(project(":compose:material:material:material-samples"))
kmpDocs(project(":compose:runtime:runtime"))
samples(project(":compose:runtime:runtime:runtime-samples"))
@@ -162,6 +164,7 @@
docs(project(":credentials:credentials-fido"))
samples(project(":credentials:credentials-samples"))
docs(project(":credentials:credentials-play-services-auth"))
+ docs(project(":credentials:credentials-e2ee"))
docs(project(":cursoradapter:cursoradapter"))
docs(project(":customview:customview"))
docs(project(":customview:customview-poolingcontainer"))
diff --git a/docs/api_guidelines/deprecation.md b/docs/api_guidelines/deprecation.md
index 209a465..1ead3d2 100644
--- a/docs/api_guidelines/deprecation.md
+++ b/docs/api_guidelines/deprecation.md
@@ -44,9 +44,9 @@
Soft removals **must** do the following:
* Mark the API as deprecated for at least one stable release prior to removal.
-* In Java sources, mark the API with a `@RestrictTo(LIBRARY)` Java annotation
- as well as a `@removed <reason>` docs annotation explaining why the API was
- removed.
+* In Java sources, mark the API with a `@RestrictTo(LIBRARY_GROUP_PREFIX)`
+ Java annotation as well as a `@removed <reason>` docs annotation explaining
+ why the API was removed.
* In Kotlin sources, mark the API with `@Deprecated(message = <reason>,
level = DeprecationLevel.HIDDEN)` explaining why the API was removed.
* Maintain binary compatibility, as the API may still be called by existing
diff --git a/dynamicanimation/dynamicanimation-ktx/build.gradle b/dynamicanimation/dynamicanimation-ktx/build.gradle
index 8fde43d..3271478 100644
--- a/dynamicanimation/dynamicanimation-ktx/build.gradle
+++ b/dynamicanimation/dynamicanimation-ktx/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -45,7 +45,7 @@
androidx {
name = "Dynamic animation Kotlin Extensions"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
mavenVersion = LibraryVersions.DYNAMICANIMATION_KTX
inceptionYear = "2018"
description = "Kotlin extensions for 'dynamicanimation' artifact"
diff --git a/dynamicanimation/dynamicanimation/src/androidTest/java/androidx/dynamicanimation/tests/FlingTests.java b/dynamicanimation/dynamicanimation/src/androidTest/java/androidx/dynamicanimation/tests/FlingTests.java
index 00d39a9..70fe119 100644
--- a/dynamicanimation/dynamicanimation/src/androidTest/java/androidx/dynamicanimation/tests/FlingTests.java
+++ b/dynamicanimation/dynamicanimation/src/androidTest/java/androidx/dynamicanimation/tests/FlingTests.java
@@ -36,6 +36,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,6 +67,7 @@
/**
* Test that custom properties are supported.
*/
+ @Ignore // b/325549946
@Test
public void testCustomProperties() {
final Object animObj = new Object();
diff --git a/emoji2/emoji2-emojipicker/samples/build.gradle b/emoji2/emoji2-emojipicker/samples/build.gradle
index 5ef572c..a38eac3 100644
--- a/emoji2/emoji2-emojipicker/samples/build.gradle
+++ b/emoji2/emoji2-emojipicker/samples/build.gradle
@@ -17,6 +17,7 @@
import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
+ id("AndroidXComposePlugin")
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
@@ -36,11 +37,12 @@
defaultConfig {
minSdkVersion 21
}
- buildFeatures {
- compose = true
- }
- composeOptions {
- kotlinCompilerExtensionVersion = '1.5.8'
- }
namespace "androidx.emoji2.emojipicker.samples"
}
+
+androidx {
+ name = "Emoji Picker Samples"
+ type = LibraryType.SAMPLES
+ inceptionYear = "2022"
+ description = "Contains sample code for the Androidx Emoji Picker"
+}
diff --git a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerHeaderAdapter.kt b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerHeaderAdapter.kt
index 75921ca4..78c47b0 100644
--- a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerHeaderAdapter.kt
+++ b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerHeaderAdapter.kt
@@ -61,7 +61,10 @@
isSelected = isItemSelected
contentDescription = emojiPickerItems.getHeaderIconDescription(i)
}
- viewHolder.itemView.setOnClickListener { onHeaderIconClicked(i) }
+ viewHolder.itemView.setOnClickListener {
+ onHeaderIconClicked(i)
+ selectedGroupIndex = i
+ }
if (isItemSelected) {
headerIcon.post {
headerIcon.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER)
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-af/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-af/strings.xml
index 8216c83..e4840e4 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-af/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-af/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"VLAE"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Geen emosiekone beskikbaar nie"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Jy het nog geen emosiekone gebruik nie"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emosiekoon-tweerigtingoorskakelaar"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emosiekoonvariantkieser"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s en %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"skadu"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"ligte velkleur"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"mediumligte velkleur"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"medium velkleur"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"mediumdonker velkleur"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"donker velkleur"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-am/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-am/strings.xml
index f42beb0..6f838f3 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-am/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-am/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"የስሜት ገላጭ ምስል ባለሁለት አቅጣጫ መቀያየሪያ"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"መካከለኛ ነጣ ያለ የቆዳ ቀለም"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"መካከለኛ የቆዳ ቀለም"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"መካከለኛ ጠቆር ያለ የቆዳ ቀለም"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ጠቆር ያለ የቆዳ ቀለም"</string>
</resources>
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 3567ded..d345acd 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"مفتاح ثنائي الاتجاه للرموز التعبيرية"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"بشرة فاتحة متوسّطة"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"بشرة متوسّطة"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"بشرة داكنة متوسّطة"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"بشرة داكنة"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-as/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-as/strings.xml
index dea449f..dfdb76c 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-as/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-as/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ইম’জি বাইডাইৰেকশ্বনেল ছুইচ্চাৰ"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"মধ্যমীয়া পাতল ছালৰ ৰং"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"মিঠাবৰণীয়া ছালৰ ৰং"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"মধ্যমীয়া গাঢ় ছালৰ ৰং"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"গাঢ় ছালৰ ৰং"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-az/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-az/strings.xml
index 41d1392..3816c2a 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-az/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-az/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BAYRAQLAR"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Əlçatan emoji yoxdur"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Hələ heç bir emojidən istifadə etməməsiniz"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ikitərəfli emoji dəyişdirici"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emoji variant seçicisi"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s və %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"kölgə"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"açıq dəri rəngi"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"orta açıq dəri rəngi"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"orta dəri rəngi"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"orta tünd dəri rəngi"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tünd dəri rəngi"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-b+sr+Latn/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-b+sr+Latn/strings.xml
index 6b1da7f..9325eeb 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-b+sr+Latn/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-b+sr+Latn/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"ZASTAVE"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Emodžiji nisu dostupni"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Još niste koristili emodžije"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"dvosmerni prebacivač emodžija"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"birač varijanti emodžija"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s i %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"senka"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"koža svetle puti"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"koža srednjesvetle puti"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"koža srednje puti"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"koža srednjetamne puti"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"koža tamne puti"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml
index 771b4d0e..8c1d50a 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"пераключальнік кірунку для эмодзі"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"умерана светлы колер скуры"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"нейтральны колер скуры"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"умерана цёмны колер скуры"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"цёмны колер скуры"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-bg/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-bg/strings.xml
index 9dd5152..c1c4050 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-bg/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-bg/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"двупосочен превключвател на емоджи"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"средно светъл цвят на кожата"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"междинен цвят на кожата"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"средно тъмен цвят на кожата"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"тъмен цвят на кожата"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-bn/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-bn/strings.xml
index 115e0c4..ca8f374 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-bn/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-bn/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ইমোজি দ্বিমুখী সুইচার"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"মাঝারি-হাল্কা স্কিন টোন"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"মাঝারি স্কিন টোন"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"মাঝারি-গাঢ় স্কিন টোন"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"গাঢ় স্কিন টোন"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml
index 73d1e87..b619963 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"ZASTAVE"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Emoji sličice nisu dostupne"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Još niste koristili nijednu emoji sličicu"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"dvosmjerni prebacivač emodžija"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"birač varijanti emodžija"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s i %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"sjenka"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"svijetla boja kože"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"srednje svijetla boja kože"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"srednja boja kože"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"srednje tamna boja kože"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tamna boja kože"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ca/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ca/strings.xml
index 4d25e6f..31d1289 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ca/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ca/strings.xml
@@ -30,4 +30,12 @@
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No hi ha cap emoji disponible"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Encara no has fet servir cap emoji"</string>
<string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"selector bidireccional d\'emojis"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"selector de variants d\'emojis"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s i %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"ombra"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"to de pell clar"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"to de pell mitjà-clar"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"to de pell mitjà"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"to de pell mitjà-fosc"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"to de pell fosc"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-cs/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-cs/strings.xml
index dc70333..92e63e5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-cs/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-cs/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"VLAJKY"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nejsou k dispozici žádné smajlíky"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Zatím jste žádná emodži nepoužili"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"dvousměrný přepínač smajlíků"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"výběr variant emodži"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s a %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"stín"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"světlý tón pleti"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"středně světlý tón pleti"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"střední tón pleti"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"středně tmavý tón pleti"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tmavý tón pleti"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-da/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-da/strings.xml
index b09dbe4..b2146f9 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-da/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-da/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"FLAG"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Der er ingen tilgængelige emojis"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Du har ikke brugt nogen emojis endnu"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"tovejsskifter til emojis"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"vælger for emojivariant"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s og %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"skygge"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"lys hudfarve"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"mellemlys hudfarve"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"medium hudfarve"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"mellemmørk hudfarve"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"mørk hudfarve"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml
index 9071a38..95fd48a 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"FLAGGEN"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Keine Emojis verfügbar"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Du hast noch keine Emojis verwendet"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"Bidirektionale Emoji-Auswahl"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"Emojivarianten-Auswahl"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s und %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"Hautton"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"Heller Hautton"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"Mittelheller Hautton"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"Mittlerer Hautton"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"Mitteldunkler Hautton"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"Dunkler Hautton"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-el/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-el/strings.xml
index e12a7b7..48d189f 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-el/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-el/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"ΣΗΜΑΙΕΣ"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Δεν υπάρχουν διαθέσιμα emoji"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Δεν έχετε χρησιμοποιήσει κανένα emoji ακόμα"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"αμφίδρομο στοιχείο εναλλαγής emoji"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"επιλογέας παραλλαγής emoji"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"μεσαίος προς ανοιχτός τόνος επιδερμίδας"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"μεσαίος τόνος επιδερμίδας"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"μεσαίος προς σκούρος τόνος επιδερμίδας"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"σκούρος τόνος επιδερμίδας"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-en-rAU/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-en-rAU/strings.xml
index 244c602..64aafcd 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-en-rAU/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-en-rAU/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"FLAGS"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No emojis available"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"You haven\'t used any emoji yet"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emoji bidirectional switcher"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emoji variant selector"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s and %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"shadow"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"light skin tone"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"medium-light skin tone"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"medium skin tone"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"medium-dark skin tone"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"dark skin tone"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-en-rCA/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-en-rCA/strings.xml
index 7e406c9..0aacec5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-en-rCA/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-en-rCA/strings.xml
@@ -30,4 +30,12 @@
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No emojis available"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"You haven\'t used any emojis yet"</string>
<string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emoji bidirectional switcher"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emoji variant selector"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s and %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"shadow"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"light skin tone"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"medium light skin tone"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"medium skin tone"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"medium dark skin tone"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"dark skin tone"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-en-rGB/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-en-rGB/strings.xml
index 244c602..64aafcd 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-en-rGB/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-en-rGB/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"FLAGS"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No emojis available"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"You haven\'t used any emoji yet"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emoji bidirectional switcher"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emoji variant selector"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s and %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"shadow"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"light skin tone"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"medium-light skin tone"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"medium skin tone"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"medium-dark skin tone"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"dark skin tone"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-en-rIN/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-en-rIN/strings.xml
index 244c602..64aafcd 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-en-rIN/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-en-rIN/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"FLAGS"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No emojis available"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"You haven\'t used any emoji yet"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emoji bidirectional switcher"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emoji variant selector"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s and %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"shadow"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"light skin tone"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"medium-light skin tone"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"medium skin tone"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"medium-dark skin tone"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"dark skin tone"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-en-rXC/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-en-rXC/strings.xml
index c1af752..3cda27a 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-en-rXC/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-en-rXC/strings.xml
@@ -30,4 +30,12 @@
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No emojis available"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"You haven\'t used any emojis yet"</string>
<string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emoji bidirectional switcher"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emoji variant selector"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s and %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"shadow"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"light skin tone"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"medium light skin tone"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"medium skin tone"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"medium dark skin tone"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"dark skin tone"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-es-rUS/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-es-rUS/strings.xml
index 3879270..852b61a 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-es-rUS/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-es-rUS/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BANDERAS"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No hay ningún emoji disponible"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Todavía no usaste ningún emoji"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"selector bidireccional de emojis"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"selector de variantes de emojis"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s y %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"sombra"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"tono de piel claro"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"tono de piel medio claro"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"tono de piel intermedio"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"tono de piel medio oscuro"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tono de piel oscuro"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml
index f438f76..ec695ad 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BANDERAS"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No hay emojis disponibles"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Aún no has usado ningún emoji"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"cambio bidireccional de emojis"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"selector de variantes de emojis"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s y %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"sombra"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"tono de piel claro"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"tono de piel medio claro"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"tono de piel medio"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"tono de piel medio oscuro"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tono de piel oscuro"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-et/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-et/strings.xml
index da92264..c7c7d04 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-et/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-et/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"LIPUD"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Ühtegi emotikoni pole saadaval"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Te pole veel ühtegi emotikoni kasutanud"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emotikoni kahesuunaline lüliti"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emotikoni variandi valija"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s ja %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"vari"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"hele nahatoon"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"keskmiselt hele nahatoon"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"keskmine nahatoon"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"keskmiselt tume nahatoon"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tume nahatoon"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-eu/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-eu/strings.xml
index d54498e..6edea1d 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-eu/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-eu/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BANDERAK"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Ez dago emotikonorik erabilgarri"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Ez duzu erabili emojirik oraingoz"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"noranzko biko emoji-aldatzailea"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emojien aldaeren hautatzailea"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s eta %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"itzala"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"azalaren tonu argia"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"azalaren tonu argixka"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"azalaren tarteko tonua"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"azalaren tonu ilunxkoa"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"azalaren tonu iluna"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml
index 4d5c29f..d917197 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"تغییردهنده دوسویه اموجی"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"رنگمایه پوست ملایم روشن"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"رنگمایه پوست ملایم"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"رنگمایه پوست ملایم تیره"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"رنگمایه پوست تیره"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-fi/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-fi/strings.xml
index c0c08c4..4d68349 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-fi/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-fi/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"LIPUT"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Ei emojeita saatavilla"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Et ole vielä käyttänyt emojeita"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emoji kaksisuuntainen vaihtaja"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emojivalitsin"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s ja %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"varjostus"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"vaalea ihonväri"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"melko vaalea ihonväri"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"keskimääräinen ihonväri"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"melko tumma ihonväri"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tumma ihonväri"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-fr-rCA/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-fr-rCA/strings.xml
index 13e9ac7..c8fc499 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-fr-rCA/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-fr-rCA/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"DRAPEAUX"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Aucun émoji proposé"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Vous n\'avez encore utilisé aucun émoji"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"sélecteur bidirectionnel d\'émoji"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"sélecteur de variantes d\'émoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s et %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"ombre"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"teint clair"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"teint moyennement clair"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"teint moyen"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"teint moyennement foncé"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"teint foncé"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-fr/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-fr/strings.xml
index 65307fb..fad386d 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-fr/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-fr/strings.xml
@@ -30,4 +30,12 @@
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Aucun emoji disponible"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Vous n\'avez pas encore utilisé d\'emoji"</string>
<string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"sélecteur d\'emoji bidirectionnel"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"sélecteur de variante d\'emoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s et %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"ombre"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"teint clair"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"teint intermédiaire à clair"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"teint intermédiaire"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"teint intermédiaire à foncé"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"teint foncé"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-gl/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-gl/strings.xml
index 2bb0803..91b3073 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-gl/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-gl/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BANDEIRAS"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Non hai ningún emoji dispoñible"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Aínda non utilizaches ningún emoji"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"selector bidireccional de emojis"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"selector de variantes de emojis"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s e %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"sombra"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"ton de pel claro"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"ton de pel lixeiramente claro"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"ton de pel medio"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"ton de pel lixeiramente escuro"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ton de pel escuro"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-gu/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-gu/strings.xml
index a316e9f..0960d6e 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-gu/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-gu/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"બે દિશામાં સ્વિચ થઈ શકતું ઇમોજી સ્વિચર"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"ત્વચાનો મધ્યમ હળવો ટોન"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"ત્વચાનો મધ્યમ ટોન"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"ત્વચાનો મધ્યમ ઘેરો ટોન"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ત્વચાનો ઘેરો ટોન"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-hi/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-hi/strings.xml
index a09653d..ab9a466 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-hi/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-hi/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"दोनों तरफ़ ले जा सकने वाले स्विचर का इमोजी"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"थोड़े हल्के रंग की त्वचा"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"सामान्य रंग की त्वचा"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"थोड़े गहरे रंग की त्वचा"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"गहरे रंग की त्वचा"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-hr/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-hr/strings.xml
index d2ee647..00be2dc 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-hr/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-hr/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"ZASTAVE"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nije dostupan nijedan emoji"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Još niste upotrijebili emojije"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"dvosmjerni izmjenjivač emojija"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"alat za odabir varijante emojija"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s i %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"sjena"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"svijetla boja kože"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"srednje svijetla boja kože"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"srednja boja kože"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"srednje tamna boja kože"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tamna boja kože"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-hu/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-hu/strings.xml
index 3303bef..727f33c 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-hu/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-hu/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"ZÁSZLÓK"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nincsenek rendelkezésre álló emojik"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Még nem használt emojikat"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"kétirányú emojiváltó"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emojiváltozat-választó"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s és %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"árnyék"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"világos bőrtónus"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"közepesen világos bőrtónus"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"közepes bőrtónus"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"közepesen sötét bőrtónus"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"sötét bőrtónus"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-hy/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-hy/strings.xml
index 86852f1..414e304 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-hy/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-hy/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"էմոջիների երկկողմանի փոխանջատիչ"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"մաշկի չափավոր բաց երանգ"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"մաշկի չեզոք երանգ"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"մաշկի չափավոր մուգ երանգ"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"մաշկի մուգ երանգ"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-in/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-in/strings.xml
index 053a26f..84d9bd5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-in/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-in/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BENDERA"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Tidak ada emoji yang tersedia"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Anda belum menggunakan emoji apa pun"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"pengalih dua arah emoji"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"pemilih varian emoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s dan %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"bayangan"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"warna kulit cerah"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"warna kulit kuning langsat"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"warna kulit sawo matang"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"warna kulit cokelat"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"warna kulit gelap"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml
index 8986e91..60e2844 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"FÁNAR"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Engin emoji-tákn í boði"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Þú hefur ekki notað nein emoji enn"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emoji-val í báðar áttir"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"val emoji-afbrigðis"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s og %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"skuggi"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"ljós húðlitur"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"meðalljós húðlitur"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"húðlitur í meðallagi"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"meðaldökkur húðlitur"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"dökkur húðlitur"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-it/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-it/strings.xml
index 4307ffd..a2fee10 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-it/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-it/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BANDIERE"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nessuna emoji disponibile"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Non hai ancora usato alcuna emoji"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"selettore bidirezionale di emoji"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"selettore variante emoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s e %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"ombra"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"carnagione chiara"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"carnagione medio-chiara"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"carnagione media"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"carnagione medio-scura"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"carnagione scura"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-iw/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-iw/strings.xml
index e155d4c..c8c10cd 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-iw/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-iw/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"לחצן דו-כיווני למעבר לאמוג\'י"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"גוון עור בינוני-בהיר"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"גוון עור בינוני"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"גוון עור בינוני-כהה"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"גוון עור כהה"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ja/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ja/strings.xml
index 600e821..128f95b 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ja/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ja/strings.xml
@@ -30,4 +30,12 @@
<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_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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"やや明るい肌の色"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"中間の明るさの肌の色"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"やや濃い肌の色"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"濃い肌の色"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ka/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ka/strings.xml
index 3662c4f..4434f10 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ka/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ka/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"დროშები"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Emoji-ები მიუწვდომელია"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Emoji-ებით ჯერ არ გისარგებლიათ"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emoji-ს ორმიმართულებიანი გადამრთველი"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emoji-ს ვარიანტის ამომრჩევი"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"კანის ღია საშუალო ტონი"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"კანის საშუალო ტონი"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"კანის მუქი საშუალო ტონი"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"კანის მუქი ტონი"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml
index f172f31..173b655 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"екіжақты эмоджи ауыстырғыш"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"терінің орташа ақшыл реңі"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"терінің орташа реңі"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"терінің орташа қараторы реңі"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"терінің қараторы реңі"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml
index 5b3c2e4..f202d60 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"មុខងារប្ដូរទ្វេទិសនៃរូបអារម្មណ៍"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"សម្បុរស្បែកសល្មម"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"សម្បុរស្បែកល្មម"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"សម្បុរស្បែកខ្មៅល្មម"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"សម្បុរស្បែកខ្មៅ"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-kn/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-kn/strings.xml
index c36e87d..3e6397e 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-kn/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-kn/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ಎಮೋಜಿ ಬೈಡೈರೆಕ್ಷನಲ್ ಸ್ವಿಚರ್"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"ಮೀಡಿಯಮ್ ಲೈಟ್ ಸ್ಕಿನ್ ಟೋನ್"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"ಮೀಡಿಯಮ್ ಸ್ಕಿನ್ ಟೋನ್"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"ಮೀಡಿಯಮ್ ಡಾರ್ಕ್ ಸ್ಕಿನ್ ಟೋನ್"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ಡಾರ್ಕ್ ಸ್ಕಿನ್ ಟೋನ್"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ko/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ko/strings.xml
index 2ac1df7..b0fd567 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ko/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ko/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"그림 이모티콘 양방향 전환기"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"약간 밝은 피부색"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"중간 피부색"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"약간 어두운 피부색"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"어두운 피부색"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ky/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ky/strings.xml
index 2ca75f5b..e5e69cc1 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ky/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ky/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"эки тараптуу быйтыкча которгуч"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"агыраак түстүү тери"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"орточо түстүү тери"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"орточо кара тору түстүү тери"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"кара тору түстүү тери"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-lo/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-lo/strings.xml
index 21cfe9c..7203809 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-lo/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-lo/strings.xml
@@ -30,4 +30,12 @@
<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">"ຕົວສະຫຼັບອີໂມຈິແບບ 2 ທິດທາງ"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"ສະກິນໂທນແຈ້ງປານກາງ"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"ສະກິນໂທນປານກາງ"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"ສະກິນໂທນມືດປານກາງ"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ສະກິນໂທນມືດ"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-lt/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-lt/strings.xml
index 5ab16c7..6c7ed80 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-lt/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-lt/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"VĖLIAVOS"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nėra jokių pasiekiamų jaustukų"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Dar nenaudojote jokių jaustukų"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"dvikryptis jaustukų perjungikli"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"jaustuko varianto parinkiklis"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s ir %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"šešėlis"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"šviesi odos spalva"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"vidutiniškai šviesi odos spalva"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"nei tamsi, nei šviesi odos spalva"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"vidutiniškai tamsi odos spalva"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tamsi odos spalva"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-lv/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-lv/strings.xml
index b163279..43ed60e 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-lv/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-lv/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"KAROGI"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nav pieejamu emocijzīmju"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Jūs vēl neesat izmantojis nevienu emocijzīmi"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"emocijzīmju divvirzienu pārslēdzējs"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emocijzīmes varianta atlasītājs"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s un %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"ēna"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"gaišs ādas tonis"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"vidēji gaišs ādas tonis"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"vidējs ādas tonis"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"vidēji tumšs ādas tonis"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tumšs ādas tonis"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml
index c65fcfb..756205f 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"двонасочен менувач на емоџија"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"средно светол тон на кожата"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"среден тон на кожата"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"средно темен тон на кожата"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"темен тон на кожата"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ml/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ml/strings.xml
index 327d8a0..5eaa499 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ml/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ml/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ഇമോജി ദ്വിദിശ സ്വിച്ചർ"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"മീഡിയം ലൈറ്റ് സ്കിൻ ടോൺ"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"മീഡിയം സ്കിൻ ടോൺ"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"മീഡിയം ഡാർക്ക് സ്കിൻ ടോൺ"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ഡാർക്ക് സ്കിൻ ടോൺ"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-mn/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-mn/strings.xml
index f36da6c..8a10449 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-mn/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-mn/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"эможигийн хоёр чиглэлтэй сэлгүүр"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"дунд зэргийн цайвар арьсны өнгө"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"дунд зэргийн арьсны өнгө"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"дунд зэргийн бараан арьсны өнгө"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"бараан арьсны өнгө"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-mr/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-mr/strings.xml
index 10853bd4..ecebaed 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-mr/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-mr/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"इमोजीचा द्विदिश स्विचर"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"मध्यम उजळ रंगाची त्वचा"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"मध्यम रंगाची त्वचा"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"मध्यम गडद रंगाची त्वचा"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"गडद रंगाची त्वचा"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ms/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ms/strings.xml
index 9e1cca5..616b9ce 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ms/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ms/strings.xml
@@ -30,4 +30,12 @@
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Tiada emoji tersedia"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Anda belum menggunakan mana-mana emoji lagi"</string>
<string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"penukar dwiarah emoji"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"pemilih varian emoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s dan %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"bebayang"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"ton kulit cerah"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"ton kulit sederhana cerah"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"ton kulit sederhana"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"ton kulit sederhana gelap"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ton kulit gelap"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-my/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-my/strings.xml
index 0ebe3f6..3a8a920 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-my/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-my/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"အီမိုဂျီ လမ်းကြောင်းနှစ်ခုပြောင်းစနစ်"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"အနည်းငယ်ဖြူသည့် အသားအရောင်"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"အလယ်အလတ် အသားအရောင်"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"အနည်းငယ်ညိုသည့် အသားအရောင်"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ညိုသည့် အသားအရောင်"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-nb/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-nb/strings.xml
index 909485c..7283de8 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-nb/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-nb/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"FLAGG"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Ingen emojier er tilgjengelige"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Du har ikke brukt noen emojier ennå"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"toveisvelger for emoji"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"velger for emojivariant"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s og %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"skygge"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"lys hudtone"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"middels lys hudtone"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"middels hudtone"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"middels mørk hudtone"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"mørk hudtone"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ne/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ne/strings.xml
index 6fdffd0..63a95e5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ne/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ne/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"दुवै दिशामा लैजान सकिने स्विचरको इमोजी"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"छालाको मध्यम फिक्का रङ"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"छालाको मध्यम रङ"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"छालाको मध्यम गाढा रङ"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"छालाको गाढा रङ"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-nl/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-nl/strings.xml
index 2a7b9b9..1e9aaff 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-nl/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-nl/strings.xml
@@ -30,4 +30,12 @@
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Geen emoji\'s beschikbaar"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Je hebt nog geen emoji\'s gebruikt"</string>
<string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"bidirectionele emoji-schakelaar"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emoji-variantkiezer"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s en %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"schaduw"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"lichte huidskleur"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"middellichte huidskleur"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"medium huidskleur"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"middeldonkere huidskleur"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"donkere huidskleur"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-or/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-or/strings.xml
index 55ba407..fe11974 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-or/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-or/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ଇମୋଜିର ବାଇଡାଇରେକ୍ସନାଲ ସୁଇଚର"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"ମଧ୍ୟମ ଲାଇଟ ସ୍କିନ ଟୋନ"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"ମଧ୍ୟମ ସ୍କିନ ଟୋନ"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"ମଧ୍ୟମ ଡାର୍କ ସ୍କିନ ଟୋନ"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ଡାର୍କ ସ୍କିନ ଟୋନ"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-pa/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-pa/strings.xml
index f9ddc80..2f6c890 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-pa/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-pa/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ਇਮੋਜੀ ਬਾਇਡਾਇਰੈਕਸ਼ਨਲ ਸਵਿੱਚਰ"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"ਚਮੜੀ ਦਾ ਦਰਮਿਆਨਾ ਹਲਕਾ ਰੰਗ"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"ਚਮੜੀ ਦਾ ਦਰਮਿਆਨਾ ਰੰਗ"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"ਚਮੜੀ ਦਾ ਦਰਮਿਆਨਾ ਗੂੜ੍ਹਾ ਰੰਗ"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ਚਮੜੀ ਦਾ ਗੂੜ੍ਹਾ ਰੰਗ"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-pl/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-pl/strings.xml
index cc87d09..5505626 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-pl/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-pl/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"FLAGI"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Brak dostępnych emotikonów"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Żadne emotikony nie zostały jeszcze użyte"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"dwukierunkowy przełącznik emotikonów"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"selektor wariantu emotikona"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s i %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"cień"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"jasny odcień skóry"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"średnio jasny odcień skóry"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"pośredni odcień skóry"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"średnio ciemny odcień skóry"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ciemny odcień skóry"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-pt-rBR/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-pt-rBR/strings.xml
index 14187a3..caa37c5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-pt-rBR/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-pt-rBR/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BANDEIRAS"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Não há emojis disponíveis"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Você ainda não usou emojis"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"seletor bidirecional de emojis"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"seletor de variante do emoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s e %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"sombra"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"tom de pele claro"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"tom de pele médio-claro"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"tom de pele médio"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"tom de pele médio-escuro"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tom de pele escuro"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-pt-rPT/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-pt-rPT/strings.xml
index 8af463d..ad29a54 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-pt-rPT/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-pt-rPT/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BANDEIRAS"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nenhum emoji disponível"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Ainda não utilizou emojis"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"comutador bidirecional de emojis"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"seletor de variantes de emojis"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s e %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"sombra"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"tom de pele claro"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"tom de pele claro médio"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"tom de pele médio"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"tom de pele escuro médio"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tom de pele escuro"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-pt/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-pt/strings.xml
index 14187a3..caa37c5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-pt/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-pt/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BANDEIRAS"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Não há emojis disponíveis"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Você ainda não usou emojis"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"seletor bidirecional de emojis"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"seletor de variante do emoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s e %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"sombra"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"tom de pele claro"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"tom de pele médio-claro"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"tom de pele médio"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"tom de pele médio-escuro"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tom de pele escuro"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ro/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ro/strings.xml
index 4491530..c06f239 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ro/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ro/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"STEAGURI"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nu sunt disponibile emoji-uri"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Încă nu ai folosit emoji"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"comutator bidirecțional de emojiuri"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"selector de variante de emoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s și %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"umbră"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"nuanță deschisă a pielii"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"nuanță deschisă medie a pielii"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"nuanță medie a pielii"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"nuanță închisă medie a pielii"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"nuanță închisă a pielii"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ru/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ru/strings.xml
index 79317ee..e2fea1f 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ru/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ru/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"Двухсторонний переключатель эмодзи"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"умеренно светлый оттенок кожи"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"нейтральный оттенок кожи"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"умеренно темный оттенок кожи"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"темный оттенок кожи"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-si/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-si/strings.xml
index b451d73..b906785 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-si/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-si/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ද්විත්ව දිශා ඉමොජි මාරුකරණය"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"මධ්යම ලා සම් වර්ණය"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"මධ්යම සම් වර්ණය"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"මධ්යම අඳුරු සම් වර්ණය"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"අඳුරු සම් වර්ණය"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml
index c4e79a5..96fa6ca 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"VLAJKY"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nie sú k dispozícii žiadne emodži"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Zatiaľ ste nepoužili žiadne emodži"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"obojsmerný prepínač emodži"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"selektor variantu emodži"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s a %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"tieň"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"svetlý odtieň pokožky"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"stredne svetlý odtieň pokožky"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"stredný odtieň pokožky"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"stredne tmavý odtieň pokožky"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tmavý odtieň pokožky"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sl/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sl/strings.xml
index a104b96..02f0e10 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sl/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sl/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"ZASTAVE"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Ni emodžijev"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Uporabili niste še nobenega emodžija."</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"dvosmerni preklopnik emodžijev"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"Izbirnik različice emodžija"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s in %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"senčenje"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"svetel odtenek kože"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"srednje svetel odtenek kože"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"srednji odtenek kože"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"srednje temen odtenek kože"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"temen odtenek kože"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sq/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sq/strings.xml
index c1d871b..68d70e5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sq/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sq/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"FLAMUJ"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nuk ofrohen emoji"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Nuk ke përdorur ende asnjë emoji"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ndërruesi me dy drejtime për emoji-t"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"përzgjedhësi i variantit të emoji-t"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s dhe %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"hije"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"ton lëkure i zbehtë"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"ton lëkure mesatarisht i zbehtë"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"ton lëkure mesatar"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"ton lëkure mesatarisht i errët"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ton lëkure i errët"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sr/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sr/strings.xml
index a85908b..712a714 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sr/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sr/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"двосмерни пребацивач емоџија"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"кожа средњесветле пути"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"кожа средње пути"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"кожа средњетамне пути"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"кожа тамне пути"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sv/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sv/strings.xml
index 4fb6a64..2ce0e0f 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sv/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sv/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"FLAGGOR"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Inga emojier tillgängliga"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Du har ännu inte använt emojis"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"dubbelriktad emojiväxlare"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"Väljare av emoji-varianter"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s och %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"skugga"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"ljus hudfärg"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"medelljus hudfärg"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"medelmörk hudfärg"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"mellanmörk hudfärg"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"mörk hudfärg"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml
index 6a95579..1f95731 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BENDERA"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Hakuna emoji zinazopatikana"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Bado hujatumia emoji zozote"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"kibadilishaji cha emoji cha pande mbili"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"kiteuzi cha kibadala cha emoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s na %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"kivuli"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"ngozi ya rangi nyeupe"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"ngozi ya rangi nyeupe kiasi"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"ngozi ya rangi ya maji ya kunde"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"ngozi ya rangi nyeusi kiasi"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ngozi ya rangi nyeusi"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ta/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ta/strings.xml
index 80a6dca..dff24f7 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ta/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ta/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ஈமோஜி இருபக்க மாற்றி"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"கொஞ்சம் வெள்ளை நிறம்"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"மாநிறம்"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"கொஞ்சம் கருநிறம்"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"கருநிறம்"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-te/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-te/strings.xml
index f872dc9..0d14b90 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-te/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-te/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ఎమోజీ ద్విదిశాత్మక స్విచ్చర్"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"చామనఛాయ లైట్ స్కిన్ రంగు"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"చామనఛాయ స్కిన్ రంగు"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"చామనఛాయ డార్క్ స్కిన్ రంగు"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"డార్క్ స్కిన్ రంగు"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml
index 31ea744..432627d 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ตัวสลับอีโมจิแบบ 2 ทาง"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"โทนผิวสีอ่อนปานกลาง"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"โทนผิวสีปานกลาง"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"โทนผิวสีเข้มปานกลาง"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"โทนผิวสีเข้ม"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-tl/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-tl/strings.xml
index 56c87ca..611e2ce 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-tl/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-tl/strings.xml
@@ -30,4 +30,12 @@
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Walang available na emoji"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Hindi ka pa gumamit ng anumang emoji"</string>
<string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"bidirectional na switcher ng emoji"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"selector ng variant ng emoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s at %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"shadow"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"maputing kulay ng balat"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"katamtamang maputing kulay ng balat"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"katamtamang kulay ng balat"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"katamtamang maitim na kulay ng balat"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"maitim na kulay ng balat"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-tr/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-tr/strings.xml
index a3f6281..57960f3 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-tr/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-tr/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BAYRAKLAR"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Kullanılabilir emoji yok"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Henüz emoji kullanmadınız"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"çift yönlü emoji değiştirici"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emoji varyant seçici"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s ve %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"gölge"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"açık ten rengi"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"orta-açık ten rengi"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"orta ten rengi"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"orta-koyu ten rengi"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"koyu ten rengi"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml
index 1f54b13..1d599ff 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"двосторонній перемикач смайлів"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"помірно світлий відтінок шкіри"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"помірний відтінок шкіри"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"помірно темний відтінок шкіри"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"темний відтінок шкіри"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ur/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ur/strings.xml
index ee8f10a..7ed0acd 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ur/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ur/strings.xml
@@ -30,4 +30,12 @@
<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_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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"جلد کا متوسط ہلکا ٹون"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"جلد کا متوسط ٹون"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"جلد کا متوسط گہرا ٹون"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"جلد کا گہرا ٹون"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml
index a6de5d3..f4ff0d5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"BAYROQCHALAR"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Hech qanday emoji mavjud emas"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Hanuz birorta emoji ishlatmagansiz"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"ikki tomonlama emoji almashtirgich"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"emoji variant tanlagich"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s va %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"soya"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"och rang tusli"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"oʻrtacha och rang tusli"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"neytral rang tusli"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"oʻrtacha toʻq rang tusli"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"toʻq rang tusli"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-vi/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-vi/strings.xml
index 691cb25..f765385 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-vi/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-vi/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"CỜ"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Không có biểu tượng cảm xúc nào"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Bạn chưa sử dụng biểu tượng cảm xúc nào"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"trình chuyển đổi hai chiều biểu tượng cảm xúc"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"bộ chọn biến thể biểu tượng cảm xúc"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"%1$s và %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"bóng"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"tông màu da sáng"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"tông màu da sáng trung bình"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"tông màu da trung bình"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"tông màu da tối trung bình"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"tông màu da tối"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rCN/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rCN/strings.xml
index 30c282f..9774a91 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rCN/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rCN/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"表情符号双向切换器"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"中等偏浅肤色"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"中等肤色"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"中等偏深肤色"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"深肤色"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rHK/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rHK/strings.xml
index 99faec7..4db6d89 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rHK/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rHK/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"旗幟"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"沒有可用的 Emoji"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"你尚未使用任何 Emoji"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"Emoji 雙向切換工具"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"Emoji 變化版本選取器"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"偏淺膚色"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"中等膚色"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"偏深膚色"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"深膚色"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rTW/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rTW/strings.xml
index 228d97b..bc57f72 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rTW/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rTW/strings.xml
@@ -29,6 +29,13 @@
<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>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"表情符號雙向切換器"</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>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"偏淺膚色"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"中等膚色"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"偏深膚色"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"深膚色"</string>
</resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-zu/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-zu/strings.xml
index 2ec492c..adf8cba 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-zu/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-zu/strings.xml
@@ -29,6 +29,13 @@
<string name="emoji_category_flags" msgid="6185639503532784871">"AMAFULEGI"</string>
<string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Awekho ama-emoji atholakalayo"</string>
<string name="emoji_empty_recent_category" msgid="7863877827879290200">"Awukasebenzisi noma yimaphi ama-emoji okwamanje"</string>
- <!-- no translation found for emoji_bidirectional_switcher_content_desc (5084600168354220605) -->
- <skip />
+ <string name="emoji_bidirectional_switcher_content_desc" msgid="5084600168354220605">"isishintshi se-emoji ye-bidirectional"</string>
+ <string name="emoji_variant_selector_content_desc" msgid="2898934883418401376">"isikhethi esihlukile se-emoji"</string>
+ <string name="emoji_variant_content_desc_template" msgid="6381933050671041489">"Okuthi %1$s nokuthi %2$s"</string>
+ <string name="emoji_skin_tone_shadow_content_desc" msgid="1759906883307507376">"isithunzi"</string>
+ <string name="emoji_skin_tone_light_content_desc" msgid="1052239040923092881">"ibala lesikhumba elikhanyayo"</string>
+ <string name="emoji_skin_tone_medium_light_content_desc" msgid="3968357909948619626">"ibala lesikhumba elikhanya ngokumaphakathi"</string>
+ <string name="emoji_skin_tone_medium_content_desc" msgid="8880212664699697283">"ibala lesikhumba eliphakathi nendawo"</string>
+ <string name="emoji_skin_tone_medium_dark_content_desc" msgid="5500271767005002413">"ibala lesikhumba elinsundu ngokumaphakathi"</string>
+ <string name="emoji_skin_tone_dark_content_desc" msgid="943874297162865134">"ibala lesikhumba elimnyama"</string>
</resources>
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index 727f57b..43c17e8 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -29,18 +29,17 @@
import static org.junit.Assert.fail;
import android.Manifest;
-import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.os.Build;
-import android.os.Environment;
import android.os.StrictMode;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.exifinterface.test.R;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -48,15 +47,17 @@
import androidx.test.filters.SmallTest;
import androidx.test.rule.GrantPermissionRule;
-import org.junit.After;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
-import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileDescriptor;
@@ -64,7 +65,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
@@ -89,75 +89,13 @@
public GrantPermissionRule mRuntimePermissionRule =
GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE);
- private static final String JPEG_WITH_EXIF_BYTE_ORDER_II = "jpeg_with_exif_byte_order_ii.jpg";
- private static final String JPEG_WITH_EXIF_BYTE_ORDER_MM = "jpeg_with_exif_byte_order_mm.jpg";
- private static final String JPEG_WITH_EXIF_INVALID_OFFSET = "jpeg_with_exif_invalid_offset.jpg";
- private static final String JPEG_WITH_EXIF_FULL_APP1_SEGMENT =
- "jpeg_with_exif_full_app1_segment.jpg";
-
- private static final String DNG_WITH_EXIF_WITH_XMP = "dng_with_exif_with_xmp.dng";
- private static final String JPEG_WITH_EXIF_WITH_XMP = "jpeg_with_exif_with_xmp.jpg";
- private static final String PNG_WITH_EXIF_BYTE_ORDER_II = "png_with_exif_byte_order_ii.png";
- private static final String PNG_WITHOUT_EXIF = "png_without_exif.png";
- private static final String WEBP_WITH_EXIF = "webp_with_exif.webp";
- private static final String INVALID_WEBP_WITH_JPEG_APP1_MARKER =
- "invalid_webp_with_jpeg_app1_marker.webp";
private static final String WEBP_WITHOUT_EXIF_WITH_ANIM_DATA =
"webp_with_anim_without_exif.webp";
- private static final String WEBP_WITHOUT_EXIF = "webp_without_exif.webp";
- private static final String WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING =
- "webp_lossless_without_exif.webp";
- private static final String WEBP_WITHOUT_EXIF_WITH_LOSSLESS_AND_ALPHA =
- "webp_lossless_alpha_without_exif.webp";
- private static final String JPEG_WITH_DATETIME_TAG_PRIMARY_FORMAT =
- "jpeg_with_datetime_tag_primary_format.jpg";
- private static final String JPEG_WITH_DATETIME_TAG_SECONDARY_FORMAT =
- "jpeg_with_datetime_tag_secondary_format.jpg";
- private static final String HEIF_WITH_EXIF = "heif_with_exif.heic";
- private static final int[] IMAGE_RESOURCES = new int[] {
- R.raw.jpeg_with_exif_byte_order_ii,
- R.raw.jpeg_with_exif_byte_order_mm,
- R.raw.jpeg_with_exif_invalid_offset,
- R.raw.jpeg_with_exif_full_app1_segment,
- R.raw.dng_with_exif_with_xmp,
- R.raw.jpeg_with_exif_with_xmp,
- R.raw.png_with_exif_byte_order_ii,
- R.raw.png_without_exif,
- R.raw.webp_with_exif,
- R.raw.invalid_webp_with_jpeg_app1_marker,
- R.raw.webp_with_anim_without_exif,
- R.raw.webp_without_exif,
- R.raw.webp_lossless_without_exif,
- R.raw.webp_lossless_alpha_without_exif,
- R.raw.jpeg_with_datetime_tag_primary_format,
- R.raw.jpeg_with_datetime_tag_secondary_format,
- R.raw.heif_with_exif};
- private static final String[] IMAGE_FILENAMES = new String[] {
- JPEG_WITH_EXIF_BYTE_ORDER_II,
- JPEG_WITH_EXIF_BYTE_ORDER_MM,
- JPEG_WITH_EXIF_INVALID_OFFSET,
- JPEG_WITH_EXIF_FULL_APP1_SEGMENT,
- DNG_WITH_EXIF_WITH_XMP,
- JPEG_WITH_EXIF_WITH_XMP,
- PNG_WITH_EXIF_BYTE_ORDER_II,
- PNG_WITHOUT_EXIF,
- WEBP_WITH_EXIF,
- INVALID_WEBP_WITH_JPEG_APP1_MARKER,
- WEBP_WITHOUT_EXIF_WITH_ANIM_DATA,
- WEBP_WITHOUT_EXIF,
- WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING,
- WEBP_WITHOUT_EXIF_WITH_LOSSLESS_AND_ALPHA,
- JPEG_WITH_DATETIME_TAG_PRIMARY_FORMAT,
- JPEG_WITH_DATETIME_TAG_SECONDARY_FORMAT,
- HEIF_WITH_EXIF};
- private static final int USER_READ_WRITE = 0600;
- private static final String TEST_TEMP_FILE_NAME = "testImage";
private static final double DELTA = 1e-8;
// We translate double to rational in a 1/10000 precision.
private static final double RATIONAL_DELTA = 0.0001;
private static final int TEST_LAT_LONG_VALUES_ARRAY_LENGTH = 8;
- private static final int TEST_NUMBER_OF_CORRUPTED_IMAGE_STREAMS = 30;
private static final double[] TEST_LATITUDE_VALID_VALUES = new double[]
{0, 45, 90, -60, 0.00000001, -89.999999999, 14.2465923626, -68.3434534737};
private static final double[] TEST_LONGITUDE_VALID_VALUES = new double[]
@@ -300,17 +238,506 @@
ExifInterface.TAG_WHITE_BALANCE
};
+ // TODO: b/270554381 - Rename this to ExpectedAttributes, make it final, and move it to the
+ // bottom of the file.
private static class ExpectedValue {
+
+ /** Expected attributes for {@link R.raw#jpeg_with_exif_byte_order_ii}. */
+ public static final ExpectedValue JPEG_WITH_EXIF_BYTE_ORDER_II =
+ new Builder()
+ .setThumbnailOffsetAndLength(3500, 6265)
+ .setThumbnailSize(512, 288)
+ .setIsThumbnailCompressed(true)
+ .setMake("SAMSUNG")
+ .setMakeOffsetAndLength(160, 8)
+ .setModel("SM-N900S")
+ .setAperture(2.2f)
+ .setDateTimeOriginal("2016:01:29 18:32:27")
+ .setExposureTime(0.033f)
+ .setFocalLength("413/100")
+ .setImageSize(640, 480)
+ .setIso("50")
+ .setOrientation(ExifInterface.ORIENTATION_ROTATE_90)
+ .build();
+
+ /**
+ * Expected attributes for {@link R.raw#jpeg_with_exif_byte_order_ii} when only the Exif
+ * data is read using {@link ExifInterface#STREAM_TYPE_EXIF_DATA_ONLY}.
+ */
+ public static final ExpectedValue JPEG_WITH_EXIF_BYTE_ORDER_II_STANDALONE =
+ JPEG_WITH_EXIF_BYTE_ORDER_II
+ .buildUpon()
+ .setThumbnailOffset(JPEG_WITH_EXIF_BYTE_ORDER_II.thumbnailOffset - 6)
+ .setMakeOffset(JPEG_WITH_EXIF_BYTE_ORDER_II.makeOffset - 6)
+ .build();
+
+ /** Expected attributes for {@link R.raw#jpeg_with_exif_byte_order_mm}. */
+ public static final ExpectedValue JPEG_WITH_EXIF_BYTE_ORDER_MM =
+ new Builder()
+ .setLatitudeOffsetAndLength(584, 24)
+ .setLatLong(0, 0)
+ .setAltitude(0)
+ .setMake("LGE")
+ .setMakeOffsetAndLength(414, 4)
+ .setModel("Nexus 5")
+ .setAperture(2.4f)
+ .setDateTimeOriginal("2016:01:29 15:44:58")
+ .setExposureTime(0.017f)
+ .setFocalLength("3970/1000")
+ .setGpsAltitude("0/1000")
+ .setGpsAltitudeRef("0")
+ .setGpsDatestamp("1970:01:01")
+ .setGpsLatitude("0/1,0/1,0/10000")
+ .setGpsLatitudeRef("N")
+ .setGpsLongitude("0/1,0/1,0/10000")
+ .setGpsLongitudeRef("E")
+ .setGpsProcessingMethod("GPS")
+ .setGpsTimestamp("00:00:00")
+ .setImageSize(144, 176)
+ .setIso("146")
+ .build();
+
+ /**
+ * Expected attributes for {@link R.raw#jpeg_with_exif_byte_order_mm} when only the Exif
+ * data is read using {@link ExifInterface#STREAM_TYPE_EXIF_DATA_ONLY}.
+ */
+ public static final ExpectedValue JPEG_WITH_EXIF_BYTE_ORDER_MM_STANDALONE =
+ JPEG_WITH_EXIF_BYTE_ORDER_MM
+ .buildUpon()
+ .setLatitudeOffset(JPEG_WITH_EXIF_BYTE_ORDER_MM.latitudeOffset - 6)
+ .setMakeOffset(JPEG_WITH_EXIF_BYTE_ORDER_MM.makeOffset - 6)
+ .setImageSize(0, 0)
+ .build();
+
+ /** Expected attributes for {@link R.raw#jpeg_with_exif_invalid_offset}. */
+ public static final ExpectedValue JPEG_WITH_EXIF_INVALID_OFFSET =
+ JPEG_WITH_EXIF_BYTE_ORDER_MM
+ .buildUpon()
+ .setAperture(0)
+ .setDateTimeOriginal(null)
+ .setExposureTime(0)
+ .setFocalLength(null)
+ .setIso(null)
+ .build();
+
+ /** Expected attributes for {@link R.raw#dng_with_exif_with_xmp}. */
+ public static final ExpectedValue DNG_WITH_EXIF_WITH_XMP =
+ new Builder()
+ .setThumbnailOffsetAndLength(12570, 15179)
+ .setThumbnailSize(256, 144)
+ .setIsThumbnailCompressed(true)
+ .setLatitudeOffsetAndLength(12486, 24)
+ .setLatLong(53.834507f, 10.69585f)
+ .setAltitude(0)
+ .setMake("LGE")
+ .setMakeOffsetAndLength(102, 4)
+ .setModel("LG-H815")
+ .setAperture(1.8f)
+ .setDateTimeOriginal("2015:11:12 16:46:18")
+ .setExposureTime(0.0040f)
+ .setFocalLength("442/100")
+ .setGpsDatestamp("1970:01:17")
+ .setGpsLatitude("53/1,50/1,423/100")
+ .setGpsLatitudeRef("N")
+ .setGpsLongitude("10/1,41/1,4506/100")
+ .setGpsLongitudeRef("E")
+ .setGpsTimestamp("18:08:10")
+ .setImageSize(600, 337)
+ .setIso("800")
+ .setXmpOffsetAndLength(826, 10067)
+ .build();
+
+ /** Expected attributes for {@link R.raw#jpeg_with_exif_with_xmp}. */
+ public static final ExpectedValue JPEG_WITH_EXIF_WITH_XMP =
+ DNG_WITH_EXIF_WITH_XMP
+ .buildUpon()
+ .clearThumbnail()
+ .setLatitudeOffset(1692)
+ .setMakeOffset(84)
+ .setOrientation(ExifInterface.ORIENTATION_NORMAL)
+ .setXmpOffsetAndLength(1809, 13197)
+ .build();
+
+ /** Expected attributes for {@link R.raw#png_with_exif_byte_order_ii}. */
+ public static final ExpectedValue PNG_WITH_EXIF_BYTE_ORDER_II =
+ JPEG_WITH_EXIF_BYTE_ORDER_II
+ .buildUpon()
+ .setThumbnailOffset(212271)
+ .setMakeOffset(211525)
+ .setFocalLength("41/10")
+ .build();
+
+ /** Expected attributes for {@link R.raw#webp_with_exif}. */
+ public static final ExpectedValue WEBP_WITH_EXIF =
+ JPEG_WITH_EXIF_BYTE_ORDER_II
+ .buildUpon()
+ .setThumbnailOffset(9646)
+ .setMakeOffset(6306)
+ .build();
+
+ /** Expected attributes for {@link R.raw#invalid_webp_with_jpeg_app1_marker}. */
+ public static final ExpectedValue INVALID_WEBP_WITH_JPEG_APP1_MARKER =
+ new Builder().setOrientation(ExifInterface.ORIENTATION_ROTATE_270).build();
+
+ /**
+ * Expected attributes for {@link R.raw#heif_with_exif} when read on a device below API 31.
+ */
+ public static final ExpectedValue HEIF_WITH_EXIF_BELOW_API_31 =
+ new Builder()
+ .setMake("LGE")
+ .setMakeOffsetAndLength(3519, 4)
+ .setModel("Nexus 5")
+ .setImageSize(1920, 1080)
+ .setOrientation(ExifInterface.ORIENTATION_NORMAL)
+ .build();
+
+ /**
+ * Expected attributes for {@link R.raw#heif_with_exif} when read on a device running API 31
+ * or above.
+ */
+ public static final ExpectedValue HEIF_WITH_EXIF_API_31_AND_ABOVE =
+ HEIF_WITH_EXIF_BELOW_API_31.buildUpon().setXmpOffsetAndLength(3721, 3020).build();
+
+ public static class Builder {
+ // Thumbnail information.
+ private boolean mHasThumbnail;
+ private int mThumbnailOffset;
+ private int mThumbnailLength;
+ private int mThumbnailWidth;
+ private int mThumbnailHeight;
+ private boolean mIsThumbnailCompressed;
+
+ // GPS information.
+ private boolean mHasLatLong;
+ private int mLatitudeOffset;
+ private int mLatitudeLength;
+ private float mLatitude;
+ private float mLongitude;
+ private float mAltitude;
+
+ // Make information
+ private boolean mHasMake;
+ private int mMakeOffset;
+ private int mMakeLength;
+ @Nullable private String mMake;
+
+ // Values.
+ @Nullable private String mModel;
+ private float mAperture;
+ @Nullable private String mDateTimeOriginal;
+ private float mExposureTime;
+ private float mFlash;
+ @Nullable private String mFocalLength;
+ @Nullable private String mGpsAltitude;
+ @Nullable private String mGpsAltitudeRef;
+ @Nullable private String mGpsDatestamp;
+ @Nullable private String mGpsLatitude;
+ @Nullable private String mGpsLatitudeRef;
+ @Nullable private String mGpsLongitude;
+ @Nullable private String mGpsLongitudeRef;
+ @Nullable private String mGpsProcessingMethod;
+ @Nullable private String mGpsTimestamp;
+ private int mImageLength;
+ private int mImageWidth;
+ @Nullable private String mIso;
+ private int mOrientation;
+ private int mWhiteBalance;
+
+ // XMP information.
+ private boolean mHasXmp;
+ private int mXmpOffset;
+ private int mXmpLength;
+
+ Builder() {}
+
+ private Builder(ExpectedValue attributes) {
+ mHasThumbnail = attributes.hasThumbnail;
+ mThumbnailOffset = attributes.thumbnailOffset;
+ mThumbnailLength = attributes.thumbnailLength;
+ mThumbnailWidth = attributes.thumbnailWidth;
+ mThumbnailHeight = attributes.thumbnailHeight;
+ mIsThumbnailCompressed = attributes.isThumbnailCompressed;
+ mHasLatLong = attributes.hasLatLong;
+ mLatitude = attributes.latitude;
+ mLatitudeOffset = attributes.latitudeOffset;
+ mLatitudeLength = attributes.latitudeLength;
+ mLongitude = attributes.longitude;
+ mAltitude = attributes.altitude;
+ mHasMake = attributes.hasMake;
+ mMakeOffset = attributes.makeOffset;
+ mMakeLength = attributes.makeLength;
+ mMake = attributes.make;
+ mModel = attributes.model;
+ mAperture = attributes.aperture;
+ mDateTimeOriginal = attributes.dateTimeOriginal;
+ mExposureTime = attributes.exposureTime;
+ mFocalLength = attributes.focalLength;
+ mGpsAltitude = attributes.gpsAltitude;
+ mGpsAltitudeRef = attributes.gpsAltitudeRef;
+ mGpsDatestamp = attributes.gpsDatestamp;
+ mGpsLatitude = attributes.gpsLatitude;
+ mGpsLatitudeRef = attributes.gpsLatitudeRef;
+ mGpsLongitude = attributes.gpsLongitude;
+ mGpsLongitudeRef = attributes.gpsLongitudeRef;
+ mGpsProcessingMethod = attributes.gpsProcessingMethod;
+ mGpsTimestamp = attributes.gpsTimestamp;
+ mImageLength = attributes.imageLength;
+ mImageWidth = attributes.imageWidth;
+ mIso = attributes.iso;
+ mOrientation = attributes.orientation;
+ mHasXmp = attributes.hasXmp;
+ mXmpOffset = attributes.xmpOffset;
+ mXmpLength = attributes.xmpLength;
+ }
+
+ public Builder setThumbnailSize(int width, int height) {
+ mHasThumbnail = true;
+ mThumbnailWidth = width;
+ mThumbnailHeight = height;
+ return this;
+ }
+
+ public Builder setIsThumbnailCompressed(boolean isThumbnailCompressed) {
+ mHasThumbnail = true;
+ mIsThumbnailCompressed = isThumbnailCompressed;
+ return this;
+ }
+
+ public Builder setThumbnailOffsetAndLength(int offset, int length) {
+ mHasThumbnail = true;
+ mThumbnailOffset = offset;
+ mThumbnailLength = length;
+ return this;
+ }
+
+ public Builder setThumbnailOffset(int offset) {
+ if (!mHasThumbnail) {
+ throw new IllegalStateException(
+ "Thumbnail position in the file must first be set with "
+ + "setThumbnailOffsetAndLength(int, int)");
+ }
+ mThumbnailOffset = offset;
+ return this;
+ }
+
+ public Builder clearThumbnail() {
+ mHasThumbnail = false;
+ mThumbnailWidth = 0;
+ mThumbnailHeight = 0;
+ mThumbnailOffset = 0;
+ mThumbnailLength = 0;
+ mIsThumbnailCompressed = false;
+ return this;
+ }
+
+ public Builder setLatLong(float latitude, float longitude) {
+ mHasLatLong = true;
+ mLatitude = latitude;
+ mLongitude = longitude;
+ return this;
+ }
+
+ public Builder setLatitudeOffsetAndLength(int offset, int length) {
+ mHasLatLong = true;
+ mLatitudeOffset = offset;
+ mLatitudeLength = length;
+ return this;
+ }
+
+ public Builder setLatitudeOffset(int offset) {
+ if (!mHasLatLong) {
+ throw new IllegalStateException(
+ "Latitude position in the file must first be "
+ + "set with setLatitudeOffsetAndLength(int, int)");
+ }
+ mLatitudeOffset = offset;
+ return this;
+ }
+
+ public Builder clearLatLong() {
+ mHasLatLong = false;
+ mLatitude = 0;
+ mLongitude = 0;
+ return this;
+ }
+
+ public Builder setAltitude(float altitude) {
+ mAltitude = altitude;
+ return this;
+ }
+
+ public Builder setMake(@Nullable String make) {
+ if (make == null) {
+ mHasMake = false;
+ mMakeOffset = 0;
+ mMakeLength = 0;
+ } else {
+ mHasMake = true;
+ mMake = make;
+ }
+ return this;
+ }
+
+ // TODO: b/270554381 - consider deriving length automatically from `make.length() + 1`
+ // (since the string is null-terminated in the format).
+ public Builder setMakeOffsetAndLength(int offset, int length) {
+ mHasMake = true;
+ mMakeOffset = offset;
+ mMakeLength = length;
+ return this;
+ }
+
+ public Builder setMakeOffset(int offset) {
+ if (!mHasMake) {
+ throw new IllegalStateException(
+ "Make position in the file must first be set with"
+ + " setMakeOffsetAndLength(int, int)");
+ }
+ mMakeOffset = offset;
+ return this;
+ }
+
+ public Builder setModel(@Nullable String model) {
+ mModel = model;
+ return this;
+ }
+
+ public Builder setAperture(float aperture) {
+ mAperture = aperture;
+ return this;
+ }
+
+ public Builder setDateTimeOriginal(@Nullable String dateTimeOriginal) {
+ mDateTimeOriginal = dateTimeOriginal;
+ return this;
+ }
+
+ public Builder setExposureTime(float exposureTime) {
+ mExposureTime = exposureTime;
+ return this;
+ }
+
+ public Builder setFlash(float flash) {
+ mFlash = flash;
+ return this;
+ }
+
+ public Builder setFocalLength(@Nullable String focalLength) {
+ mFocalLength = focalLength;
+ return this;
+ }
+
+ public Builder setGpsAltitude(@Nullable String gpsAltitude) {
+ mGpsAltitude = gpsAltitude;
+ return this;
+ }
+
+ public Builder setGpsAltitudeRef(@Nullable String gpsAltitudeRef) {
+ mGpsAltitudeRef = gpsAltitudeRef;
+ return this;
+ }
+
+ public Builder setGpsDatestamp(@Nullable String gpsDatestamp) {
+ mGpsDatestamp = gpsDatestamp;
+ return this;
+ }
+
+ public Builder setGpsLatitude(@Nullable String gpsLatitude) {
+ mGpsLatitude = gpsLatitude;
+ return this;
+ }
+
+ public Builder setGpsLatitudeRef(@Nullable String gpsLatitudeRef) {
+ mGpsLatitudeRef = gpsLatitudeRef;
+ return this;
+ }
+
+ public Builder setGpsLongitude(@Nullable String gpsLongitude) {
+ mGpsLongitude = gpsLongitude;
+ return this;
+ }
+
+ public Builder setGpsLongitudeRef(@Nullable String gpsLongitudeRef) {
+ mGpsLongitudeRef = gpsLongitudeRef;
+ return this;
+ }
+
+ public Builder setGpsProcessingMethod(@Nullable String gpsProcessingMethod) {
+ mGpsProcessingMethod = gpsProcessingMethod;
+ return this;
+ }
+
+ public Builder setGpsTimestamp(@Nullable String gpsTimestamp) {
+ mGpsTimestamp = gpsTimestamp;
+ return this;
+ }
+
+ public Builder setImageSize(int imageWidth, int imageLength) {
+ mImageWidth = imageWidth;
+ mImageLength = imageLength;
+ return this;
+ }
+
+ public Builder setIso(@Nullable String iso) {
+ mIso = iso;
+ return this;
+ }
+
+ public Builder setOrientation(int orientation) {
+ mOrientation = orientation;
+ return this;
+ }
+
+ public Builder setWhiteBalance(int whiteBalance) {
+ mWhiteBalance = whiteBalance;
+ return this;
+ }
+
+ public Builder setXmpOffsetAndLength(int offset, int length) {
+ mHasXmp = true;
+ mXmpOffset = offset;
+ mXmpLength = length;
+ return this;
+ }
+
+ public Builder setXmpOffset(int offset) {
+ if (!mHasXmp) {
+ throw new IllegalStateException(
+ "XMP position in the file must first be set with"
+ + " setXmpOffsetAndLength(int, int)");
+ }
+ mXmpOffset = offset;
+ return this;
+ }
+
+ public Builder clearXmp() {
+ mHasXmp = false;
+ mXmpOffset = 0;
+ mXmpLength = 0;
+ return this;
+ }
+
+ public ExpectedValue build() {
+ return new ExpectedValue(this);
+ }
+ }
+
+ // TODO: b/270554381 - Add nullability annotations below.
+
// Thumbnail information.
public final boolean hasThumbnail;
public final int thumbnailWidth;
public final int thumbnailHeight;
public final boolean isThumbnailCompressed;
+ // TODO: b/270554381 - Merge these offset and length (and others) into long[] arrays, and
+ // move them down to their own section. This may also allow removing some of the hasXXX
+ // fields.
public final int thumbnailOffset;
public final int thumbnailLength;
// GPS information.
public final boolean hasLatLong;
+ // TODO: b/270554381 - Merge this and longitude into a double[]
public final float latitude;
public final int latitudeOffset;
public final int latitudeLength;
@@ -328,8 +755,11 @@
public final float aperture;
public final String dateTimeOriginal;
public final float exposureTime;
- public final float flash;
public final String focalLength;
+ // TODO: b/270554381 - Rename these to make them clear they're strings, or original values,
+ // and move them closer to the (computed) latitude/longitude/altitude values. Consider
+ // also having a verification check that they are consistent with latitude/longitude (but
+ // not sure how to reconcile that with "don't duplicate business logic in tests").
public final String gpsAltitude;
public final String gpsAltitudeRef;
public final String gpsDatestamp;
@@ -343,146 +773,120 @@
public final int imageWidth;
public final String iso;
public final int orientation;
- public final int whiteBalance;
// XMP information.
public final boolean hasXmp;
public final int xmpOffset;
public final int xmpLength;
- private static String getString(TypedArray typedArray, int index) {
- String stringValue = typedArray.getString(index);
- if (stringValue == null || stringValue.equals("")) {
- return null;
- }
- return stringValue.trim();
+ private ExpectedValue(Builder builder) {
+ // TODO: b/270554381 - Re-order these assignments to match the fields above.
+ hasThumbnail = builder.mHasThumbnail;
+ thumbnailOffset = builder.mThumbnailOffset;
+ thumbnailLength = builder.mThumbnailLength;
+ thumbnailWidth = builder.mThumbnailWidth;
+ thumbnailHeight = builder.mThumbnailHeight;
+ isThumbnailCompressed = builder.mIsThumbnailCompressed;
+ hasLatLong = builder.mHasLatLong;
+ latitudeOffset = builder.mLatitudeOffset;
+ latitudeLength = builder.mLatitudeLength;
+ latitude = builder.mLatitude;
+ longitude = builder.mLongitude;
+ altitude = builder.mAltitude;
+ hasMake = builder.mHasMake;
+ makeOffset = builder.mMakeOffset;
+ makeLength = builder.mMakeLength;
+ make = builder.mMake;
+ model = builder.mModel;
+ aperture = builder.mAperture;
+ dateTimeOriginal = builder.mDateTimeOriginal;
+ exposureTime = builder.mExposureTime;
+ focalLength = builder.mFocalLength;
+ gpsAltitude = builder.mGpsAltitude;
+ gpsAltitudeRef = builder.mGpsAltitudeRef;
+ gpsDatestamp = builder.mGpsDatestamp;
+ gpsLatitude = builder.mGpsLatitude;
+ gpsLatitudeRef = builder.mGpsLatitudeRef;
+ gpsLongitude = builder.mGpsLongitude;
+ gpsLongitudeRef = builder.mGpsLongitudeRef;
+ gpsProcessingMethod = builder.mGpsProcessingMethod;
+ gpsTimestamp = builder.mGpsTimestamp;
+ imageLength = builder.mImageLength;
+ imageWidth = builder.mImageWidth;
+ iso = builder.mIso;
+ orientation = builder.mOrientation;
+ hasXmp = builder.mHasXmp;
+ xmpOffset = builder.mXmpOffset;
+ xmpLength = builder.mXmpLength;
}
- ExpectedValue(TypedArray typedArray) {
- int index = 0;
-
- // Reads thumbnail information.
- hasThumbnail = typedArray.getBoolean(index++, false);
- thumbnailOffset = typedArray.getInt(index++, -1);
- thumbnailLength = typedArray.getInt(index++, -1);
- thumbnailWidth = typedArray.getInt(index++, 0);
- thumbnailHeight = typedArray.getInt(index++, 0);
- isThumbnailCompressed = typedArray.getBoolean(index++, false);
-
- // Reads GPS information.
- hasLatLong = typedArray.getBoolean(index++, false);
- latitudeOffset = typedArray.getInt(index++, -1);
- latitudeLength = typedArray.getInt(index++, -1);
- latitude = typedArray.getFloat(index++, 0f);
- longitude = typedArray.getFloat(index++, 0f);
- altitude = typedArray.getFloat(index++, 0f);
-
- // Reads Make information.
- hasMake = typedArray.getBoolean(index++, false);
- makeOffset = typedArray.getInt(index++, -1);
- makeLength = typedArray.getInt(index++, -1);
- make = getString(typedArray, index++);
-
- // Reads values.
- model = getString(typedArray, index++);
- aperture = typedArray.getFloat(index++, 0f);
- dateTimeOriginal = getString(typedArray, index++);
- exposureTime = typedArray.getFloat(index++, 0f);
- flash = typedArray.getFloat(index++, 0f);
- focalLength = getString(typedArray, index++);
- gpsAltitude = getString(typedArray, index++);
- gpsAltitudeRef = getString(typedArray, index++);
- gpsDatestamp = getString(typedArray, index++);
- gpsLatitude = getString(typedArray, index++);
- gpsLatitudeRef = getString(typedArray, index++);
- gpsLongitude = getString(typedArray, index++);
- gpsLongitudeRef = getString(typedArray, index++);
- gpsProcessingMethod = getString(typedArray, index++);
- gpsTimestamp = getString(typedArray, index++);
- imageLength = typedArray.getInt(index++, 0);
- imageWidth = typedArray.getInt(index++, 0);
- iso = getString(typedArray, index++);
- orientation = typedArray.getInt(index++, 0);
- whiteBalance = typedArray.getInt(index++, 0);
-
- // Reads XMP information.
- hasXmp = typedArray.getBoolean(index++, false);
- xmpOffset = typedArray.getInt(index++, 0);
- xmpLength = typedArray.getInt(index++, 0);
-
- typedArray.recycle();
+ public Builder buildUpon() {
+ return new Builder(this);
}
}
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
@Before
- public void setUp() throws Exception {
+ public void setUp() {
if (ENABLE_STRICT_MODE_FOR_UNBUFFERED_IO && Build.VERSION.SDK_INT >= 26) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectUnbufferedIo()
.penaltyDeath()
.build());
}
-
- for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
- File file = getFileFromExternalDir(IMAGE_FILENAMES[i]);
- InputStream inputStream = null;
- FileOutputStream outputStream = null;
- try {
- inputStream = getApplicationContext()
- .getResources().openRawResource(IMAGE_RESOURCES[i]);
- outputStream = new FileOutputStream(file);
- copy(inputStream, outputStream);
- } finally {
- closeQuietly(inputStream);
- closeQuietly(outputStream);
- }
- }
- }
-
- @After
- public void tearDown() throws Exception {
- for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
- File imageFile = getFileFromExternalDir(IMAGE_FILENAMES[i]);
- if (imageFile.exists()) {
- imageFile.delete();
- }
- }
}
@Test
@LargeTest
public void testJpegWithExifIntelByteOrder() throws Throwable {
- readFromFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, R.array.jpeg_with_exif_byte_order_ii);
- writeToFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, R.array.jpeg_with_exif_byte_order_ii);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
+ readFromFilesWithExif(imageFile, ExpectedValue.JPEG_WITH_EXIF_BYTE_ORDER_II);
+ writeToFilesWithExif(imageFile, ExpectedValue.JPEG_WITH_EXIF_BYTE_ORDER_II);
}
@Test
@LargeTest
public void testJpegWithExifMotorolaByteOrder() throws Throwable {
- readFromFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, R.array.jpeg_with_exif_byte_order_mm);
- writeToFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, R.array.jpeg_with_exif_byte_order_mm);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_exif_byte_order_mm, "jpeg_with_exif_byte_order_mm.jpg");
+ readFromFilesWithExif(imageFile, ExpectedValue.JPEG_WITH_EXIF_BYTE_ORDER_MM);
+ writeToFilesWithExif(imageFile, ExpectedValue.JPEG_WITH_EXIF_BYTE_ORDER_MM);
}
@Test
@LargeTest
public void testJpegWithExifAndXmp() throws Throwable {
- readFromFilesWithExif(JPEG_WITH_EXIF_WITH_XMP, R.array.jpeg_with_exif_with_xmp);
- writeToFilesWithExif(JPEG_WITH_EXIF_WITH_XMP, R.array.jpeg_with_exif_with_xmp);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_exif_with_xmp, "jpeg_with_exif_with_xmp.jpg");
+ readFromFilesWithExif(imageFile, ExpectedValue.JPEG_WITH_EXIF_WITH_XMP);
+ writeToFilesWithExif(imageFile, ExpectedValue.JPEG_WITH_EXIF_WITH_XMP);
}
// https://issuetracker.google.com/264729367
@Test
@LargeTest
public void testJpegWithInvalidOffset() throws Throwable {
- readFromFilesWithExif(JPEG_WITH_EXIF_INVALID_OFFSET, R.array.jpeg_with_exif_invalid_offset);
- writeToFilesWithExif(JPEG_WITH_EXIF_INVALID_OFFSET, R.array.jpeg_with_exif_invalid_offset);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_exif_invalid_offset, "jpeg_with_exif_invalid_offset.jpg");
+ readFromFilesWithExif(imageFile, ExpectedValue.JPEG_WITH_EXIF_INVALID_OFFSET);
+ writeToFilesWithExif(imageFile, ExpectedValue.JPEG_WITH_EXIF_INVALID_OFFSET);
}
// https://issuetracker.google.com/263747161
@Test
@LargeTest
public void testJpegWithFullApp1Segment() throws Throwable {
- File srcFile = getFileFromExternalDir(JPEG_WITH_EXIF_FULL_APP1_SEGMENT);
+ File srcFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_exif_full_app1_segment,
+ "jpeg_with_exif_full_app1_segment.jpg");
File imageFile = clone(srcFile);
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
// Add a really long string that makes the Exif data too large for the JPEG APP1 segment.
@@ -503,68 +907,97 @@
@Test
@LargeTest
public void testDngWithExifAndXmp() throws Throwable {
- readFromFilesWithExif(DNG_WITH_EXIF_WITH_XMP, R.array.dng_with_exif_with_xmp);
+ File imageFile =
+ copyFromResourceToFile(R.raw.dng_with_exif_with_xmp, "dng_with_exif_with_xmp.dng");
+ readFromFilesWithExif(imageFile, ExpectedValue.DNG_WITH_EXIF_WITH_XMP);
}
@Test
@LargeTest
public void testPngWithExif() throws Throwable {
- readFromFilesWithExif(PNG_WITH_EXIF_BYTE_ORDER_II, R.array.png_with_exif_byte_order_ii);
- writeToFilesWithExif(PNG_WITH_EXIF_BYTE_ORDER_II, R.array.png_with_exif_byte_order_ii);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.png_with_exif_byte_order_ii, "png_with_exif_byte_order_ii.png");
+ readFromFilesWithExif(imageFile, ExpectedValue.PNG_WITH_EXIF_BYTE_ORDER_II);
+ writeToFilesWithExif(imageFile, ExpectedValue.PNG_WITH_EXIF_BYTE_ORDER_II);
}
@Test
@LargeTest
public void testPngWithoutExif() throws Throwable {
- writeToFilesWithoutExif(PNG_WITHOUT_EXIF);
+ File imageFile =
+ copyFromResourceToFile(R.raw.png_with_exif_byte_order_ii, "png_without_exif.png");
+ writeToFilesWithoutExif(imageFile);
}
@Test
@LargeTest
public void testStandaloneData() throws Throwable {
- readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II,
- R.array.standalone_data_with_exif_byte_order_ii);
- readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM,
- R.array.standalone_data_with_exif_byte_order_mm);
+ File jpegIiImageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
+ readFromStandaloneDataWithExif(
+ jpegIiImageFile, ExpectedValue.JPEG_WITH_EXIF_BYTE_ORDER_II_STANDALONE);
+
+ File jpegMmImageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_exif_byte_order_mm, "jpeg_with_exif_byte_order_mm.jpg");
+ readFromStandaloneDataWithExif(
+ jpegMmImageFile, ExpectedValue.JPEG_WITH_EXIF_BYTE_ORDER_MM_STANDALONE);
}
@Test
@LargeTest
public void testWebpWithExif() throws Throwable {
- readFromFilesWithExif(WEBP_WITH_EXIF, R.array.webp_with_exif);
- writeToFilesWithExif(WEBP_WITH_EXIF, R.array.webp_with_exif);
+ File imageFile = copyFromResourceToFile(R.raw.webp_with_exif, "webp_with_exif.jpg");
+ readFromFilesWithExif(imageFile, ExpectedValue.WEBP_WITH_EXIF);
+ writeToFilesWithExif(imageFile, ExpectedValue.WEBP_WITH_EXIF);
}
+ // https://issuetracker.google.com/281638358
@Test
@LargeTest
public void testWebpWithExifApp1() throws Throwable {
- readFromFilesWithExif(INVALID_WEBP_WITH_JPEG_APP1_MARKER,
- R.array.invalid_webp_with_jpeg_app1_marker);
- writeToFilesWithExif(INVALID_WEBP_WITH_JPEG_APP1_MARKER,
- R.array.invalid_webp_with_jpeg_app1_marker);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.invalid_webp_with_jpeg_app1_marker,
+ "invalid_webp_with_jpeg_app1_marker.webp");
+ readFromFilesWithExif(imageFile, ExpectedValue.INVALID_WEBP_WITH_JPEG_APP1_MARKER);
+ writeToFilesWithExif(imageFile, ExpectedValue.INVALID_WEBP_WITH_JPEG_APP1_MARKER);
}
@Test
@LargeTest
public void testWebpWithoutExif() throws Throwable {
- writeToFilesWithoutExif(WEBP_WITHOUT_EXIF);
+ File imageFile = copyFromResourceToFile(R.raw.webp_without_exif, "webp_without_exif.webp");
+ writeToFilesWithoutExif(imageFile);
}
@Test
@LargeTest
public void testWebpWithoutExifWithAnimData() throws Throwable {
- writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_ANIM_DATA);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.webp_with_anim_without_exif, WEBP_WITHOUT_EXIF_WITH_ANIM_DATA);
+ writeToFilesWithoutExif(imageFile);
}
@Test
@LargeTest
public void testWebpWithoutExifWithLosslessEncoding() throws Throwable {
- writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.webp_lossless_without_exif, "webp_lossless_without_exif.webp");
+ writeToFilesWithoutExif(imageFile);
}
@Test
@LargeTest
public void testWebpWithoutExifWithLosslessEncodingAndAlpha() throws Throwable {
- writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_LOSSLESS_AND_ALPHA);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.webp_lossless_alpha_without_exif,
+ "webp_lossless_alpha_without_exif.webp");
+ writeToFilesWithoutExif(imageFile);
}
/**
@@ -573,16 +1006,17 @@
@Test
@LargeTest
public void testHeifFile() throws Throwable {
+ File imageFile = copyFromResourceToFile(R.raw.heif_with_exif, "heif_with_exif.heic");
if (Build.VERSION.SDK_INT >= 28) {
// Reading XMP data from HEIF was added in SDK 31.
- readFromFilesWithExif(HEIF_WITH_EXIF,
+ readFromFilesWithExif(
+ imageFile,
Build.VERSION.SDK_INT >= 31
- ? R.array.heif_with_exif_31_and_above
- : R.array.heif_with_exif);
+ ? ExpectedValue.HEIF_WITH_EXIF_API_31_AND_ABOVE
+ : ExpectedValue.HEIF_WITH_EXIF_BELOW_API_31);
} else {
// Make sure that an exception is not thrown and that image length/width tag values
// return default values, not the actual values.
- File imageFile = getFileFromExternalDir(HEIF_WITH_EXIF);
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
String defaultTagValue = "0";
assertEquals(defaultTagValue, exif.getAttribute(ExifInterface.TAG_IMAGE_LENGTH));
@@ -723,7 +1157,10 @@
+ 32400000L /* TAG_OFFSET_TIME value ("+09:00") converted to msec */;
final String expectedDatetimeOffsetStringValue = "+09:00";
- File imageFile = getFileFromExternalDir(JPEG_WITH_DATETIME_TAG_PRIMARY_FORMAT);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_datetime_tag_primary_format,
+ "jpeg_with_datetime_tag_primary_format.jpg");
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
// Test getting datetime values
assertEquals(expectedGetDatetimeValue, (long) exif.getDateTime());
@@ -788,7 +1225,10 @@
+ 32400000L /* TAG_OFFSET_TIME value ("+09:00") converted to msec */;
final String expectedDateTimeStringValue = "2016-01-29 18:32:27";
- File imageFile = getFileFromExternalDir(JPEG_WITH_DATETIME_TAG_SECONDARY_FORMAT);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_datetime_tag_secondary_format,
+ "jpeg_with_datetime_tag_secondary_format.jpg");
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
assertEquals(expectedDateTimeStringValue,
exif.getAttribute(ExifInterface.TAG_DATETIME));
@@ -812,7 +1252,10 @@
@Test
@LargeTest
public void testAddDefaultValuesForCompatibility() throws Exception {
- File imageFile = getFileFromExternalDir(JPEG_WITH_DATETIME_TAG_PRIMARY_FORMAT);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_datetime_tag_primary_format,
+ "jpeg_with_datetime_tag_primary_format.jpg");
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
// 1. Check that the TAG_DATETIME value is not overwritten by TAG_DATETIME_ORIGINAL's value
@@ -836,7 +1279,10 @@
@Test
@LargeTest
public void testSubsec() throws IOException {
- File imageFile = getFileFromExternalDir(JPEG_WITH_DATETIME_TAG_PRIMARY_FORMAT);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_datetime_tag_primary_format,
+ "jpeg_with_datetime_tag_primary_format.jpg");
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
// Set initial value to 0
@@ -912,7 +1358,9 @@
@Test
@LargeTest
public void testRotation() throws IOException {
- File imageFile = getFileFromExternalDir(JPEG_WITH_EXIF_BYTE_ORDER_II);
+ File imageFile =
+ copyFromResourceToFile(
+ R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
int num;
@@ -1130,7 +1578,6 @@
assertStringTag(exifInterface, ExifInterface.TAG_DATETIME_ORIGINAL,
expectedValue.dateTimeOriginal);
assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
- assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
@@ -1150,7 +1597,6 @@
assertStringTag(exifInterface, ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
expectedValue.iso);
assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
- assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
if (expectedValue.hasXmp) {
assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
@@ -1173,21 +1619,19 @@
}
}
- private void readFromStandaloneDataWithExif(String fileName, int typedArrayResourceId)
+ private void readFromStandaloneDataWithExif(File imageFile, ExpectedValue expectedValue)
throws IOException {
- ExpectedValue expectedValue = new ExpectedValue(
- getApplicationContext().getResources().obtainTypedArray(typedArrayResourceId));
-
- File imageFile = getFileFromExternalDir(fileName);
String verboseTag = imageFile.getName();
- FileInputStream fis = new FileInputStream(imageFile);
- // Skip the following marker bytes (0xff, 0xd8, 0xff, 0xe1)
- fis.skip(4);
- // Read the value of the length of the exif data
- short length = readShort(fis);
- byte[] exifBytes = new byte[length];
- fis.read(exifBytes);
+ byte[] exifBytes;
+ try (FileInputStream fis = new FileInputStream(imageFile)) {
+ // Skip the following marker bytes (0xff, 0xd8, 0xff, 0xe1)
+ ByteStreams.skipFully(fis, 4);
+ // Read the value of the length of the exif data
+ short length = readShort(fis);
+ exifBytes = new byte[length];
+ ByteStreams.readFully(fis, exifBytes);
+ }
ByteArrayInputStream bin = new ByteArrayInputStream(exifBytes);
ExifInterface exifInterface =
@@ -1195,9 +1639,8 @@
compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
}
- private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)
+ private void testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)
throws IOException {
- File imageFile = getFileFromExternalDir(fileName);
String verboseTag = imageFile.getName();
// Creates via file.
@@ -1210,14 +1653,11 @@
assertNotNull(exifInterface);
compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
- InputStream in = null;
// Creates via InputStream.
- try {
- in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+ try (InputStream in =
+ new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()))) {
exifInterface = new ExifInterface(in);
compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
- } finally {
- closeQuietly(in);
}
// Creates via FileDescriptor.
@@ -1236,19 +1676,14 @@
}
}
- private void testExifInterfaceRange(String fileName, ExpectedValue expectedValue)
+ private void testExifInterfaceRange(File imageFile, ExpectedValue expectedValue)
throws IOException {
- File imageFile = getFileFromExternalDir(fileName);
-
- InputStream in = null;
- try {
- in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+ try (InputStream in =
+ new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()))) {
if (expectedValue.hasThumbnail) {
- in.skip(expectedValue.thumbnailOffset);
+ ByteStreams.skipFully(in, expectedValue.thumbnailOffset);
byte[] thumbnailBytes = new byte[expectedValue.thumbnailLength];
- if (in.read(thumbnailBytes) != expectedValue.thumbnailLength) {
- throw new IOException("Failed to read the expected thumbnail length");
- }
+ ByteStreams.readFully(in, thumbnailBytes);
// TODO: Need a way to check uncompressed thumbnail file
Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnailBytes, 0,
thumbnailBytes.length);
@@ -1256,46 +1691,40 @@
assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
}
+ }
- // TODO: Creating a new input stream is a temporary
- // workaround for BufferedInputStream#mark/reset not working properly for
- // LG_G4_ISO_800_DNG. Need to investigate cause.
- in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+ // TODO: Creating a new input stream is a temporary
+ // workaround for BufferedInputStream#mark/reset not working properly for
+ // LG_G4_ISO_800_DNG. Need to investigate cause.
+ try (InputStream in =
+ new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()))) {
if (expectedValue.hasMake) {
- in.skip(expectedValue.makeOffset);
+ ByteStreams.skipFully(in, expectedValue.makeOffset);
byte[] makeBytes = new byte[expectedValue.makeLength];
- if (in.read(makeBytes) != expectedValue.makeLength) {
- throw new IOException("Failed to read the expected make length");
- }
+ ByteStreams.readFully(in, makeBytes);
String makeString = new String(makeBytes);
// Remove null bytes
makeString = makeString.replaceAll("\u0000.*", "");
assertEquals(expectedValue.make, makeString);
}
+ }
- in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+ try (InputStream in =
+ new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()))) {
if (expectedValue.hasXmp) {
- in.skip(expectedValue.xmpOffset);
+ ByteStreams.skipFully(in, expectedValue.xmpOffset);
byte[] identifierBytes = new byte[expectedValue.xmpLength];
- if (in.read(identifierBytes) != expectedValue.xmpLength) {
- throw new IOException("Failed to read the expected xmp length");
- }
+ ByteStreams.readFully(in, identifierBytes);
final String xmpIdentifier = "<?xpacket begin=";
assertTrue(new String(identifierBytes, Charset.forName("UTF-8"))
.startsWith(xmpIdentifier));
}
// TODO: Add code for retrieving raw latitude data using offset and length
- } finally {
- closeQuietly(in);
}
}
- private void writeToFilesWithExif(String fileName, int typedArrayResourceId)
+ private void writeToFilesWithExif(File srcFile, ExpectedValue expectedValue)
throws IOException {
- ExpectedValue expectedValue = new ExpectedValue(
- getApplicationContext().getResources().obtainTypedArray(typedArrayResourceId));
-
- File srcFile = getFileFromExternalDir(fileName);
File imageFile = clone(srcFile);
String verboseTag = imageFile.getName();
@@ -1345,20 +1774,17 @@
}
}
- private void readFromFilesWithExif(String fileName, int typedArrayResourceId)
+ private void readFromFilesWithExif(File imageFile, ExpectedValue expectedValue)
throws IOException {
- ExpectedValue expectedValue = new ExpectedValue(
- getApplicationContext().getResources().obtainTypedArray(typedArrayResourceId));
// Test for reading from external data storage.
- testExifInterfaceCommon(fileName, expectedValue);
+ testExifInterfaceCommon(imageFile, expectedValue);
// Test for checking expected range by retrieving raw data with given offset and length.
- testExifInterfaceRange(fileName, expectedValue);
+ testExifInterfaceRange(imageFile, expectedValue);
}
- private void writeToFilesWithoutExif(String fileName) throws IOException {
- File srcFile = getFileFromExternalDir(fileName);
+ private void writeToFilesWithoutExif(File srcFile) throws IOException {
File imageFile = clone(srcFile);
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
@@ -1383,17 +1809,6 @@
assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
}
- private void closeQuietly(Closeable closeable) {
- if (closeable != null) {
- try {
- closeable.close();
- } catch (RuntimeException rethrown) {
- throw rethrown;
- } catch (Exception ignored) {
- }
- }
- }
-
@RequiresApi(21)
private void closeQuietly(FileDescriptor fd) {
if (fd != null) {
@@ -1406,17 +1821,6 @@
}
}
- private int copy(InputStream in, OutputStream out) throws IOException {
- int total = 0;
- byte[] buffer = new byte[8192];
- int c;
- while ((c = in.read(buffer)) != -1) {
- total += c;
- out.write(buffer, 0, c);
- }
- return total;
- }
-
private void assertLatLongValuesAreNotSet(ExifInterface exif) {
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
@@ -1425,9 +1829,12 @@
}
private ExifInterface createTestExifInterface() throws IOException {
- File image = File.createTempFile(TEST_TEMP_FILE_NAME, ".jpg");
- image.deleteOnExit();
- return new ExifInterface(image.getAbsolutePath());
+ File originalFile = tempFolder.newFile();
+ File jpgFile = new File(originalFile.getAbsolutePath() + ".jpg");
+ if (!originalFile.renameTo(jpgFile)) {
+ throw new IOException("Rename from " + originalFile + " to " + jpgFile + " failed.");
+ }
+ return new ExifInterface(jpgFile.getAbsolutePath());
}
private short readShort(InputStream is) throws IOException {
@@ -1439,11 +1846,6 @@
return (short) ((ch1 << 8) + (ch2));
}
- private File getFileFromExternalDir(String fileName) {
- return new File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES),
- fileName);
- }
-
/**
* Asserts that {@code expectedImageFile} and {@code actualImageFile} can be decoded by
* {@link BitmapFactory} and the results have the same width, height and MIME type.
@@ -1514,10 +1916,17 @@
private File clone(File original) throws IOException {
File cloned =
File.createTempFile("tmp_", System.nanoTime() + "_" + original.getName());
- try (FileInputStream inputStream = new FileInputStream(original);
- FileOutputStream outputStream = new FileOutputStream(cloned)) {
- copy(inputStream, outputStream);
- }
+ Files.copy(original, cloned);
return cloned;
}
+
+ private File copyFromResourceToFile(int resourceId, String filename) throws IOException {
+ File file = tempFolder.newFile(filename);
+ try (InputStream inputStream =
+ getApplicationContext().getResources().openRawResource(resourceId);
+ FileOutputStream outputStream = new FileOutputStream(file)) {
+ ByteStreams.copy(inputStream, outputStream);
+ }
+ return file;
+ }
}
diff --git a/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml b/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
deleted file mode 100644
index 6e69297..0000000
--- a/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
+++ /dev/null
@@ -1,546 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <array name="jpeg_with_exif_byte_order_ii">
- <!--Whether thumbnail exists-->
- <item>true</item>
- <item>3500</item>
- <item>6265</item>
- <item>512</item>
- <item>288</item>
- <item>true</item>
- <!--Whether GPS LatLong information exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0.0</item>
- <item>0.0</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>160</item>
- <item>8</item>
- <item>SAMSUNG</item>
- <item>SM-N900S</item>
- <item>2.200</item>
- <item>2016:01:29 18:32:27</item>
- <item>0.033</item>
- <item>0</item>
- <item>413/100</item>
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item>480</item>
- <item>640</item>
- <item>50</item>
- <item>6</item>
- <item>0</item>
- <item>false</item>
- <item>0</item>
- <item>0</item>
- </array>
- <array name="standalone_data_with_exif_byte_order_ii">
- <!--Whether thumbnail exists-->
- <item>true</item>
- <item>3494</item>
- <item>6265</item>
- <item>512</item>
- <item>288</item>
- <item>true</item>
- <!--Whether GPS LatLong information exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0.0</item>
- <item>0.0</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>154</item>
- <item>8</item>
- <item>SAMSUNG</item>
- <item>SM-N900S</item>
- <item>2.200</item>
- <item>2016:01:29 18:32:27</item>
- <item>0.033</item>
- <item>0</item>
- <item>413/100</item>
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item>480</item>
- <item>640</item>
- <item>50</item>
- <item>6</item>
- <item>0</item>
- <item>false</item>
- <item>0</item>
- <item>0</item>
- </array>
- <array name="jpeg_with_exif_byte_order_mm">
- <!--Whether thumbnail exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>false</item>
- <!--Whether GPS LatLong information exists-->
- <item>true</item>
- <item>584</item>
- <item>24</item>
- <item>0.0</item>
- <item>0.0</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>414</item>
- <item>4</item>
- <item>LGE</item>
- <item>Nexus 5</item>
- <item>2.400</item>
- <item>2016:01:29 15:44:58</item>
- <item>0.017</item>
- <item>0</item>
- <item>3970/1000</item>
- <item>0/1000</item>
- <item>0</item>
- <item>1970:01:01</item>
- <item>0/1,0/1,0/10000</item>
- <item>N</item>
- <item>0/1,0/1,0/10000</item>
- <item>E</item>
- <item>GPS</item>
- <item>00:00:00</item>
- <item>176</item>
- <item>144</item>
- <item>146</item>
- <item>0</item>
- <item>0</item>
- <item>false</item>
- <item>0</item>
- <item>0</item>
- </array>
- <array name="standalone_data_with_exif_byte_order_mm">
- <!--Whether thumbnail exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>false</item>
- <!--Whether GPS LatLong information exists-->
- <item>true</item>
- <item>578</item>
- <item>24</item>
- <item>0.0</item>
- <item>0.0</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>408</item>
- <item>4</item>
- <item>LGE</item>
- <item>Nexus 5</item>
- <item>2.400</item>
- <item>2016:01:29 15:44:58</item>
- <item>0.017</item>
- <item>0</item>
- <item>3970/1000</item>
- <item>0/1000</item>
- <item>0</item>
- <item>1970:01:01</item>
- <item>0/1,0/1,0/10000</item>
- <item>N</item>
- <item>0/1,0/1,0/10000</item>
- <item>E</item>
- <item>GPS</item>
- <item>00:00:00</item>
- <item>0</item>
- <item>0</item>
- <item>146</item>
- <item>0</item>
- <item>0</item>
- <item>false</item>
- <item>0</item>
- <item>0</item>
- </array>
- <array name="jpeg_with_exif_invalid_offset">
- <!--Whether thumbnail exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>false</item>
- <!--Whether GPS LatLong information exists-->
- <item>true</item>
- <item>584</item>
- <item>24</item>
- <item>0.0</item>
- <item>0.0</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>414</item>
- <item>4</item>
- <item>LGE</item>
- <item>Nexus 5</item>
- <item>0.0</item>
- <item />
- <item>0.0</item>
- <item>0</item>
- <item />
- <item>0/1000</item>
- <item>0</item>
- <item>1970:01:01</item>
- <item>0/1,0/1,0/10000</item>
- <item>N</item>
- <item>0/1,0/1,0/10000</item>
- <item>E</item>
- <item>GPS</item>
- <item>00:00:00</item>
- <item>176</item>
- <item>144</item>
- <item />
- <item>0</item>
- <item>0</item>
- <item>false</item>
- <item>0</item>
- <item>0</item>
- </array>
- <array name="dng_with_exif_with_xmp">
- <!--Whether thumbnail exists-->
- <item>true</item>
- <item>12570</item>
- <item>15179</item>
- <item>256</item>
- <item>144</item>
- <item>true</item>
- <!--Whether GPS LatLong information exists-->
- <item>true</item>
- <item>12486</item>
- <item>24</item>
- <item>53.834507</item>
- <item>10.69585</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>102</item>
- <item>4</item>
- <item>LGE</item>
- <item>LG-H815</item>
- <item>1.800</item>
- <item>2015:11:12 16:46:18</item>
- <item>0.0040</item>
- <item>0.0</item>
- <item>442/100</item>
- <item />
- <item />
- <item>1970:01:17</item>
- <item>53/1,50/1,423/100</item>
- <item>N</item>
- <item>10/1,41/1,4506/100</item>
- <item>E</item>
- <item />
- <item>18:08:10</item>
- <item>337</item>
- <item>600</item>
- <item>800</item>
- <item>0</item>
- <item>0</item>
- <item>true</item>
- <item>826</item>
- <item>10067</item>
- </array>
- <array name="jpeg_with_exif_with_xmp">
- <!--Whether thumbnail exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>false</item>
- <!--Whether GPS LatLong information exists-->
- <item>true</item>
- <item>1692</item>
- <item>24</item>
- <item>53.834507</item>
- <item>10.69585</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>84</item>
- <item>4</item>
- <item>LGE</item>
- <item>LG-H815</item>
- <item>1.800</item>
- <item>2015:11:12 16:46:18</item>
- <item>0.0040</item>
- <item>0.0</item>
- <item>442/100</item>
- <item />
- <item />
- <item>1970:01:17</item>
- <item>53/1,50/1,423/100</item>
- <item>N</item>
- <item>10/1,41/1,4506/100</item>
- <item>E</item>
- <item />
- <item>18:08:10</item>
- <item>337</item>
- <item>600</item>
- <item>800</item>
- <item>1</item>
- <item>0</item>
- <item>true</item>
- <item>1809</item>
- <item>13197</item>
- </array>
- <array name="png_with_exif_byte_order_ii">
- <!--Whether thumbnail exists-->
- <item>true</item>
- <item>212271</item>
- <item>6265</item>
- <item>512</item>
- <item>288</item>
- <item>true</item>
- <!--Whether GPS LatLong information exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0.0</item>
- <item>0.0</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>211525</item>
- <item>8</item>
- <item>SAMSUNG</item>
- <item>SM-N900S</item>
- <item>2.200</item>
- <item>2016:01:29 18:32:27</item>
- <item>0.033</item>
- <item>0</item>
- <item>41/10</item>
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item>480</item>
- <item>640</item>
- <item>50</item>
- <item>6</item>
- <item>0</item>
- <item>false</item>
- <item>0</item>
- <item>0</item>
- </array>
- <array name="webp_with_exif">
- <!--Whether thumbnail exists-->
- <item>true</item>
- <item>9646</item>
- <item>6265</item>
- <item>512</item>
- <item>288</item>
- <item>true</item>
- <!--Whether GPS LatLong information exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0.0</item>
- <item>0.0</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>6306</item>
- <item>8</item>
- <item>SAMSUNG</item>
- <item>SM-N900S</item>
- <item>2.200</item>
- <item>2016:01:29 18:32:27</item>
- <item>0.033</item>
- <item>0</item>
- <item>413/100</item>
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item>480</item>
- <item>640</item>
- <item>50</item>
- <item>6</item>
- <item>0</item>
- <item>false</item>
- <item>0</item>
- <item>0</item>
- </array>
- <array name="invalid_webp_with_jpeg_app1_marker">
- <!--Whether thumbnail exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>false</item>
- <!--Whether GPS LatLong information exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0.0</item>
- <item>0.0</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item />
- <item />
- <item>0.0</item>
- <item />
- <item>0.0</item>
- <item>0.0</item>
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item>0</item>
- <item>0</item>
- <item />
- <item>8</item>
- <item>0</item>
- <item>false</item>
- <item>0</item>
- <item>0</item>
- </array>
- <array name="heif_with_exif">
- <!--Whether thumbnail exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>false</item>
- <!--Whether GPS LatLong information exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0.0</item>
- <item>0.0</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>3519</item>
- <item>4</item>
- <item>LGE</item>
- <item>Nexus 5</item>
- <item>0.0</item>
- <item />
- <item>0.0</item>
- <item>0</item>
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item>1080</item>
- <item>1920</item>
- <item />
- <item>1</item>
- <item>0</item>
- <item>false</item>
- <item>0</item>
- <item>0</item>
- </array>
- <array name="heif_with_exif_31_and_above">
- <!--Whether thumbnail exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>0</item>
- <item>false</item>
- <!--Whether GPS LatLong information exists-->
- <item>false</item>
- <item>0</item>
- <item>0</item>
- <item>0.0</item>
- <item>0.0</item>
- <item>0.0</item>
- <!--Whether Make information exists-->
- <item>true</item>
- <item>3519</item>
- <item>4</item>
- <item>LGE</item>
- <item>Nexus 5</item>
- <item>0.0</item>
- <item />
- <item>0.0</item>
- <item>0</item>
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item />
- <item>1080</item>
- <item>1920</item>
- <item />
- <item>1</item>
- <item>0</item>
- <item>true</item>
- <item>3721</item>
- <item>3020</item>
- </array>
-</resources>
\ No newline at end of file
diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index 97770e9..3257f21 100644
--- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -4603,7 +4603,7 @@
// Ignore exceptions in order to keep the compatibility with the old versions of
// ExifInterface.
if (DEBUG) {
- Log.w(TAG, "Invalid image: ExifInterface got an unsupported image format file"
+ Log.w(TAG, "Invalid image: ExifInterface got an unsupported image format file "
+ "(ExifInterface supports JPEG and some RAW image formats only) "
+ "or a corrupted JPEG file to ExifInterface.", e);
}
@@ -5224,7 +5224,8 @@
}
/**
- * Returns number of milliseconds since Jan. 1, 1970, midnight UTC.
+ * Returns number of milliseconds since 1970-01-01 00:00:00 UTC.
+ *
* @return null if the date time information is not available.
*/
@SuppressLint("AutoBoxing") /* Not a performance-critical call, thus not a big concern. */
diff --git a/fragment/fragment-ktx/api/current.ignore b/fragment/fragment-ktx/api/current.ignore
deleted file mode 100644
index fd7e670..0000000
--- a/fragment/fragment-ktx/api/current.ignore
+++ /dev/null
@@ -1,11 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#activityViewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method androidx.fragment.app.FragmentViewModelLazyKt.activityViewModels has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#activityViewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method androidx.fragment.app.FragmentViewModelLazyKt.activityViewModels has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method androidx.fragment.app.FragmentViewModelLazyKt.createViewModelLazy has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#viewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method androidx.fragment.app.FragmentViewModelLazyKt.viewModels has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#viewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method androidx.fragment.app.FragmentViewModelLazyKt.viewModels has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
diff --git a/fragment/fragment-ktx/api/restricted_current.ignore b/fragment/fragment-ktx/api/restricted_current.ignore
deleted file mode 100644
index fd7e670..0000000
--- a/fragment/fragment-ktx/api/restricted_current.ignore
+++ /dev/null
@@ -1,11 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#activityViewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method androidx.fragment.app.FragmentViewModelLazyKt.activityViewModels has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#activityViewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method androidx.fragment.app.FragmentViewModelLazyKt.activityViewModels has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method androidx.fragment.app.FragmentViewModelLazyKt.createViewModelLazy has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#viewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method androidx.fragment.app.FragmentViewModelLazyKt.viewModels has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#viewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
- Method androidx.fragment.app.FragmentViewModelLazyKt.viewModels has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
diff --git a/fragment/fragment-testing/build.gradle b/fragment/fragment-testing/build.gradle
index 46298de..703dd1d 100644
--- a/fragment/fragment-testing/build.gradle
+++ b/fragment/fragment-testing/build.gradle
@@ -35,7 +35,6 @@
api("androidx.test:core:1.5.0")
api(libs.kotlinStdlib)
api(project(":fragment:fragment-testing-manifest"))
- androidTestImplementation("androidx.core:core-ktx:1.12.0")
androidTestImplementation(libs.kotlinStdlib)
androidTestImplementation(libs.espressoCore)
androidTestImplementation(libs.testExtJunit)
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentDismissTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentDismissTest.kt
index e449520..2bba751 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentDismissTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentDismissTest.kt
@@ -24,7 +24,9 @@
import androidx.fragment.app.test.EmptyFragmentTestActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.LargeTest
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertWithMessage
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -100,20 +102,19 @@
}
}
- @Suppress("DEPRECATION")
- val activityTestRule =
- androidx.test.rule.ActivityTestRule(EmptyFragmentTestActivity::class.java)
+ @get:Rule
+ val activityScenarioTestRule = ActivityScenarioRule(EmptyFragmentTestActivity::class.java)
// Detect leaks BEFORE and AFTER activity is destroyed
@get:Rule
val ruleChain: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess())
- .around(activityTestRule)
+ .around(activityScenarioTestRule)
@Test
fun testDialogFragmentDismiss() {
val fragment = TestDialogFragment()
- activityTestRule.runOnUiThread {
- fragment.showNow(activityTestRule.activity.supportFragmentManager, null)
+ activityScenarioTestRule.withActivity {
+ fragment.showNow(supportFragmentManager, null)
}
assertWithMessage("Dialog was not being shown")
@@ -126,7 +127,7 @@
val onStopCountDownLatch = CountDownLatch(1)
val onDestroyCountDownLatch = CountDownLatch(1)
val dismissCountDownLatch = CountDownLatch(1)
- activityTestRule.runOnUiThread {
+ activityScenarioTestRule.withActivity {
fragment.lifecycle.addObserver(
LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_STOP) {
@@ -148,7 +149,7 @@
}
if (mainThread) {
- activityTestRule.runOnUiThread {
+ activityScenarioTestRule.withActivity {
operation.run(fragment)
}
} else {
@@ -182,9 +183,9 @@
.that(isShowing)
.isTrue()
} else {
- assertWithMessage("Dialog should not be showing in onStop() when manually dismissed")
- .that(isShowing)
- .isFalse()
+ assertWithMessage(
+ "Dialog should not be showing in onStop() when manually dismissed"
+ ).that(isShowing).isFalse()
assertWithMessage("Dialog should be null after dismiss()")
.that(fragment.dialog)
@@ -195,9 +196,9 @@
@Test
fun testDismissDestroyedDialog() {
val dialogFragment = TestDialogFragment()
- val fm = activityTestRule.activity.supportFragmentManager
+ val fm = activityScenarioTestRule.withActivity { supportFragmentManager }
- activityTestRule.runOnUiThread {
+ activityScenarioTestRule.withActivity {
fm.beginTransaction()
.add(dialogFragment, null)
.commitNow()
@@ -205,14 +206,14 @@
val dialog = dialogFragment.requireDialog()
- activityTestRule.runOnUiThread {
+ activityScenarioTestRule.withActivity {
dialog.dismiss()
fm.beginTransaction()
.remove(dialogFragment)
.commitNow()
}
- activityTestRule.runOnUiThread {
+ activityScenarioTestRule.withActivity {
assertWithMessage("onDismiss should only have been called once")
.that(dialogFragment.onDismissCalledCount)
.isEqualTo(1)
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
index e50a9d2..2bb7357 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
@@ -42,6 +42,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.waitForExecution
import androidx.testutils.withActivity
+import androidx.testutils.withUse
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -1065,6 +1066,54 @@
assertThat(fragment3.loadedAnimation).isEqualTo(EXIT_OTHER)
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun predictiveBackNoAnimation() {
+ withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ withActivity { setContentView(R.layout.simple_container) }
+ val fragment1 = StrictViewFragment()
+ val fragment2 = StrictViewFragment()
+
+ val fm = withActivity { supportFragmentManager }
+
+ withActivity {
+ fm.beginTransaction()
+ .setReorderingAllowed(true)
+ .add(R.id.fragmentContainer, fragment1, "fragment1")
+ .addToBackStack("fragment1")
+ .commit()
+ }
+ waitForExecution()
+
+ withActivity {
+ fm.beginTransaction()
+ .setReorderingAllowed(true)
+ .replace(R.id.fragmentContainer, fragment2, "fragment2")
+ .addToBackStack("fragment2")
+ .commit()
+ }
+ waitForExecution()
+
+ fragment1.mContainer = null
+ fragment2.mContainer = null
+
+ val dispatcher = activityRule.activity.onBackPressedDispatcher
+ withActivity {
+ dispatcher.dispatchOnBackStarted(
+ BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT)
+ )
+ }
+ executePendingTransactions()
+
+ withActivity {
+ dispatcher.onBackPressed()
+ }
+ executePendingTransactions()
+
+ assertThat(fragment2.calledOnDestroy).isTrue()
+ }
+ }
+
private fun assertEnterPopExit(fragment: AnimationFragment) {
assertFragmentAnimation(fragment, 1, true, ENTER)
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
index ffbc8a1..69d8bf6 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
@@ -65,6 +65,10 @@
return ret
}
+inline fun <reified A : FragmentActivity> ActivityScenario<A>.popBackStackImmediate() {
+ withActivity { supportFragmentManager.popBackStackImmediate() }
+}
+
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.popBackStackImmediate(
id: Int,
@@ -117,16 +121,28 @@
return activity.findViewById(R.id.greenSquare)
}
+inline fun <reified A : FragmentActivity> ActivityScenario<A>.findGreen(): View {
+ return withActivity { findViewById(R.id.greenSquare) }
+}
+
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.findBlue(): View {
return activity.findViewById(R.id.blueSquare)
}
+inline fun <reified A : FragmentActivity> ActivityScenario<A>.findBlue(): View {
+ return withActivity { findViewById(R.id.blueSquare) }
+}
+
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.findRed(): View? {
return activity.findViewById(R.id.redSquare)
}
+inline fun <reified A : FragmentActivity> ActivityScenario<A>.findRed(): View {
+ return withActivity { findViewById(R.id.redSquare) }
+}
+
val View.boundsOnScreen: Rect
get() {
val loc = IntArray(2)
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
index 8825c5e..7661dc2 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
@@ -1278,49 +1278,57 @@
// When there is no matching shared element, the transition name should not be changed
@Test
fun noMatchingSharedElementRetainName() {
- val fragmentManager = activityRule.activity.supportFragmentManager
- val fragment1 = setupInitialFragment()
+ withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ val fragmentManager = withActivity {
+ setContentView(R.layout.simple_container)
+ supportFragmentManager
+ }
+ fragmentManager.addOnBackStackChangedListener(
+ onBackStackChangedListener
+ )
+ val fragment1 = setupInitialFragmentWithScenario()
- val startBlue = activityRule.findBlue()
- val startGreen = activityRule.findGreen()
- val startGreenBounds = startGreen.boundsOnScreen
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+ val startGreenBounds = startGreen.boundsOnScreen
- val fragment2 = TransitionFragment(R.layout.scene3)
+ val fragment2 = TransitionFragment(R.layout.scene3)
- fragmentManager.beginTransaction()
- .setReorderingAllowed(reorderingAllowed)
- .addSharedElement(startGreen, "greenSquare")
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .commit()
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .addSharedElement(startGreen, "greenSquare")
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .commit()
- fragment2.waitForTransition()
- val midGreen = activityRule.findGreen()
- val midBlue = activityRule.findBlue()
- val midRed = activityRule.findRed()
- val midGreenBounds = midGreen.boundsOnScreen
+ fragment2.waitForTransition()
+ val midGreen = findGreen()
+ val midBlue = findBlue()
+ val midRed = findRed()
+ val midGreenBounds = midGreen.boundsOnScreen
- fragment2.sharedElementEnter.verifyAndClearTransition {
- epicenter = startGreenBounds
- exitingViews += startGreen
- enteringViews += midGreen
+ fragment2.sharedElementEnter.verifyAndClearTransition {
+ epicenter = startGreenBounds
+ exitingViews += startGreen
+ enteringViews += midGreen
+ }
+ fragment2.enterTransition.verifyAndClearTransition {
+ epicenter = midGreenBounds
+ enteringViews += listOfNotNull(midBlue, midRed)
+ }
+ verifyNoOtherTransitions(fragment2)
+
+ popBackStackImmediate()
+ fragment2.waitForTransition()
+ fragment1.waitForTransition()
+
+ val endBlue = findBlue()
+ val endGreen = findGreen()
+
+ assertThat(endBlue.transitionName).isEqualTo("blueSquare")
+ assertThat(endGreen.transitionName).isEqualTo("greenSquare")
}
- fragment2.enterTransition.verifyAndClearTransition {
- epicenter = midGreenBounds
- enteringViews += listOfNotNull(midBlue, midRed)
- }
- verifyNoOtherTransitions(fragment2)
-
- activityRule.popBackStackImmediate()
- fragment2.waitForTransition()
- fragment1.waitForTransition()
-
- val endBlue = activityRule.findBlue()
- val endGreen = activityRule.findGreen()
-
- assertThat(endBlue.transitionName).isEqualTo("blueSquare")
- assertThat(endGreen.transitionName).isEqualTo("greenSquare")
}
// Test to ensure fragments don't leak in the container's tags
@@ -1496,6 +1504,29 @@
return fragment1
}
+ private inline fun <reified A : FragmentActivity>
+ ActivityScenario<A>.setupInitialFragmentWithScenario(): TransitionFragment {
+ val fragmentManager = withActivity {
+ supportFragmentManager
+ }
+ val fragment1 = TransitionFragment(R.layout.scene1)
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .commit()
+ waitForExecution()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ fragment1.waitForTransition()
+ val blueSquare1 = findBlue()
+ val greenSquare1 = findGreen()
+ fragment1.enterTransition.verifyAndClearTransition {
+ enteringViews += listOf(blueSquare1, greenSquare1)
+ }
+ verifyNoOtherTransitions(fragment1)
+ return fragment1
+ }
+
private fun findViewById(fragment: Fragment, id: Int): View {
return fragment.requireView().findViewById(id)
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 8ca098c..169fe25 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -488,6 +488,7 @@
);
}
if (USE_PREDICTIVE_BACK) {
+ endAnimatingAwayFragments();
prepareBackStackTransition();
}
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
index 32d40bc..c134cd2 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
@@ -238,7 +238,7 @@
}
// Fragments that are transitioning are part of a seeking effect and must be at least
// AWAITING_EXIT_EFFECTS
- if (mFragment.mTransitioning) {
+ if (mFragment.mTransitioning && mFragment.mContainer != null) {
maxState = Math.max(maxState, Fragment.AWAITING_EXIT_EFFECTS);
}
if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
diff --git a/glance/glance-appwidget-testing/build.gradle b/glance/glance-appwidget-testing/build.gradle
index cd6ff3c..95bbf25 100644
--- a/glance/glance-appwidget-testing/build.gradle
+++ b/glance/glance-appwidget-testing/build.gradle
@@ -62,8 +62,7 @@
androidx {
name = "androidx.glance:glance-appwidget-testing"
- type = LibraryType.PUBLISHED_LIBRARY
- targetsJavaConsumers = false
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2023"
description = "This library provides APIs for developers to use for testing their appWidget specific Glance composables."
samples(projectOrArtifact(":glance:glance-appwidget-testing:glance-appwidget-testing-samples"))
diff --git a/glance/glance-appwidget-testing/samples/build.gradle b/glance/glance-appwidget-testing/samples/build.gradle
index 23365ef..f07b53e 100644
--- a/glance/glance-appwidget-testing/samples/build.gradle
+++ b/glance/glance-appwidget-testing/samples/build.gradle
@@ -47,7 +47,6 @@
androidx {
name = "Glance AppWidget Testing Samples"
type = LibraryType.SAMPLES
- targetsJavaConsumers = false
inceptionYear = "2023"
description = "Contains the sample code for testing the Glance AppWidget Composables"
}
diff --git a/glance/glance-appwidget/api/current.ignore b/glance/glance-appwidget/api/current.ignore
index 72eabcf..10d49a9 100644
--- a/glance/glance-appwidget/api/current.ignore
+++ b/glance/glance-appwidget/api/current.ignore
@@ -1,6 +1,4 @@
// Baseline format: 1.0
-BecameUnchecked: Field GlanceAppWidgetReceiver.coroutineContext:
- Removed Field GlanceAppWidgetReceiver.coroutineContext from compatibility checked API surface
BecameUnchecked: androidx.glance.appwidget.CheckBoxKt#CheckBox(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.glance.GlanceModifier, String, androidx.glance.text.TextStyle, androidx.glance.appwidget.CheckBoxColors, int, String):
Removed method androidx.glance.appwidget.CheckBoxKt.CheckBox(boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,androidx.glance.GlanceModifier,String,androidx.glance.text.TextStyle,androidx.glance.appwidget.CheckBoxColors,int,String) from compatibility checked API surface
BecameUnchecked: androidx.glance.appwidget.CheckBoxKt#CheckBox(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.glance.GlanceModifier, String, androidx.glance.text.TextStyle, androidx.glance.appwidget.CheckBoxColors, int, String) parameter #0:
@@ -89,6 +87,8 @@
Removed parameter horizontalAlignment in androidx.glance.appwidget.lazy.LazyVerticalGridKt.LazyVerticalGrid(androidx.glance.appwidget.lazy.GridCells gridCells, android.os.Bundle activityOptions, androidx.glance.GlanceModifier modifier, int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyVerticalGridScope,kotlin.Unit> content) from compatibility checked API surface
BecameUnchecked: androidx.glance.appwidget.lazy.LazyVerticalGridKt#LazyVerticalGrid(androidx.glance.appwidget.lazy.GridCells, android.os.Bundle, androidx.glance.GlanceModifier, int, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyVerticalGridScope,kotlin.Unit>) parameter #4:
Removed parameter content in androidx.glance.appwidget.lazy.LazyVerticalGridKt.LazyVerticalGrid(androidx.glance.appwidget.lazy.GridCells gridCells, android.os.Bundle activityOptions, androidx.glance.GlanceModifier modifier, int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyVerticalGridScope,kotlin.Unit> content) from compatibility checked API surface
+BecameUnchecked: property GlanceAppWidgetReceiver.coroutineContext:
+ Removed property GlanceAppWidgetReceiver.coroutineContext from compatibility checked API surface
DefaultValueChange: androidx.glance.appwidget.AppWidgetComposerKt#compose(androidx.glance.appwidget.GlanceAppWidget, android.content.Context, androidx.glance.GlanceId, android.os.Bundle, androidx.compose.ui.unit.DpSize, Object, kotlin.coroutines.Continuation<? super android.widget.RemoteViews>) parameter #6:
diff --git a/glance/glance-appwidget/api/restricted_current.ignore b/glance/glance-appwidget/api/restricted_current.ignore
index 72eabcf..10d49a9 100644
--- a/glance/glance-appwidget/api/restricted_current.ignore
+++ b/glance/glance-appwidget/api/restricted_current.ignore
@@ -1,6 +1,4 @@
// Baseline format: 1.0
-BecameUnchecked: Field GlanceAppWidgetReceiver.coroutineContext:
- Removed Field GlanceAppWidgetReceiver.coroutineContext from compatibility checked API surface
BecameUnchecked: androidx.glance.appwidget.CheckBoxKt#CheckBox(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.glance.GlanceModifier, String, androidx.glance.text.TextStyle, androidx.glance.appwidget.CheckBoxColors, int, String):
Removed method androidx.glance.appwidget.CheckBoxKt.CheckBox(boolean,kotlin.jvm.functions.Function0<kotlin.Unit>,androidx.glance.GlanceModifier,String,androidx.glance.text.TextStyle,androidx.glance.appwidget.CheckBoxColors,int,String) from compatibility checked API surface
BecameUnchecked: androidx.glance.appwidget.CheckBoxKt#CheckBox(boolean, kotlin.jvm.functions.Function0<kotlin.Unit>, androidx.glance.GlanceModifier, String, androidx.glance.text.TextStyle, androidx.glance.appwidget.CheckBoxColors, int, String) parameter #0:
@@ -89,6 +87,8 @@
Removed parameter horizontalAlignment in androidx.glance.appwidget.lazy.LazyVerticalGridKt.LazyVerticalGrid(androidx.glance.appwidget.lazy.GridCells gridCells, android.os.Bundle activityOptions, androidx.glance.GlanceModifier modifier, int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyVerticalGridScope,kotlin.Unit> content) from compatibility checked API surface
BecameUnchecked: androidx.glance.appwidget.lazy.LazyVerticalGridKt#LazyVerticalGrid(androidx.glance.appwidget.lazy.GridCells, android.os.Bundle, androidx.glance.GlanceModifier, int, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyVerticalGridScope,kotlin.Unit>) parameter #4:
Removed parameter content in androidx.glance.appwidget.lazy.LazyVerticalGridKt.LazyVerticalGrid(androidx.glance.appwidget.lazy.GridCells gridCells, android.os.Bundle activityOptions, androidx.glance.GlanceModifier modifier, int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyVerticalGridScope,kotlin.Unit> content) from compatibility checked API surface
+BecameUnchecked: property GlanceAppWidgetReceiver.coroutineContext:
+ Removed property GlanceAppWidgetReceiver.coroutineContext from compatibility checked API surface
DefaultValueChange: androidx.glance.appwidget.AppWidgetComposerKt#compose(androidx.glance.appwidget.GlanceAppWidget, android.content.Context, androidx.glance.GlanceId, android.os.Bundle, androidx.compose.ui.unit.DpSize, Object, kotlin.coroutines.Continuation<? super android.widget.RemoteViews>) parameter #6:
diff --git a/glance/glance-appwidget/build.gradle b/glance/glance-appwidget/build.gradle
index de7d996..a268da9 100644
--- a/glance/glance-appwidget/build.gradle
+++ b/glance/glance-appwidget/build.gradle
@@ -117,11 +117,10 @@
androidx {
name = "Glance For App Widgets"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2021"
description = "Glance-appwidgets allows developers to build layouts for Android AppWidgets " +
"using a Jetpack Compose-style API."
- targetsJavaConsumers = false
samples(projectOrArtifact(":glance:glance-appwidget:glance-appwidget-samples"))
}
diff --git a/glance/glance-material3/build.gradle b/glance/glance-material3/build.gradle
index 893f5b8..10e4fe4 100644
--- a/glance/glance-material3/build.gradle
+++ b/glance/glance-material3/build.gradle
@@ -32,11 +32,10 @@
androidx {
name = "Glance Material"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2022"
description = "Glance Material integration library." +
" This library provides interop APIs with Material 3."
metalavaK2UastEnabled = true
- targetsJavaConsumers = false
}
diff --git a/glance/glance-template/api/current.txt b/glance/glance-template/api/current.txt
index 98082dc..ad3e3e3 100644
--- a/glance/glance-template/api/current.txt
+++ b/glance/glance-template/api/current.txt
@@ -202,8 +202,6 @@
}
public enum TemplateMode {
- method public static androidx.glance.template.TemplateMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.glance.template.TemplateMode[] values();
enum_constant public static final androidx.glance.template.TemplateMode Collapsed;
enum_constant public static final androidx.glance.template.TemplateMode Horizontal;
enum_constant public static final androidx.glance.template.TemplateMode Vertical;
diff --git a/glance/glance-template/api/restricted_current.txt b/glance/glance-template/api/restricted_current.txt
index 98082dc..ad3e3e3 100644
--- a/glance/glance-template/api/restricted_current.txt
+++ b/glance/glance-template/api/restricted_current.txt
@@ -202,8 +202,6 @@
}
public enum TemplateMode {
- method public static androidx.glance.template.TemplateMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.glance.template.TemplateMode[] values();
enum_constant public static final androidx.glance.template.TemplateMode Collapsed;
enum_constant public static final androidx.glance.template.TemplateMode Horizontal;
enum_constant public static final androidx.glance.template.TemplateMode Vertical;
diff --git a/glance/glance-template/build.gradle b/glance/glance-template/build.gradle
index cc9ddd7..0b87855 100644
--- a/glance/glance-template/build.gradle
+++ b/glance/glance-template/build.gradle
@@ -79,10 +79,9 @@
androidx {
name = "Glance Templates"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
mavenVersion = LibraryVersions.GLANCE_TEMPLATE
inceptionYear = "2021"
description = "Glance allows developers to build layouts for remote surfaces using a Jetpack " +
"Compose-style API."
- targetsJavaConsumers = false
}
diff --git a/glance/glance-testing/build.gradle b/glance/glance-testing/build.gradle
index 80a720d..de552f57 100644
--- a/glance/glance-testing/build.gradle
+++ b/glance/glance-testing/build.gradle
@@ -59,9 +59,8 @@
androidx {
name = "Glance Testing"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2023"
description = "This library provides base APIs to enable testing Glance"
- targetsJavaConsumers = false
metalavaK2UastEnabled = true
}
diff --git a/glance/glance-wear-tiles/build.gradle b/glance/glance-wear-tiles/build.gradle
index 9d6ce410..7d8a4d5 100644
--- a/glance/glance-wear-tiles/build.gradle
+++ b/glance/glance-wear-tiles/build.gradle
@@ -93,10 +93,9 @@
androidx {
name = "Glance for Wear Tiles"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
mavenVersion = LibraryVersions.GLANCE_WEAR_TILES
inceptionYear = "2021"
description = "Glance allows developers to build layouts for Wear Tiles using a Jetpack " +
"Compose-style API."
- targetsJavaConsumers = false
}
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index dd31b97..d7d9a71 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -109,8 +109,6 @@
}
public enum Visibility {
- method public static androidx.glance.Visibility valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.glance.Visibility[] values();
enum_constant public static final androidx.glance.Visibility Gone;
enum_constant public static final androidx.glance.Visibility Invisible;
enum_constant public static final androidx.glance.Visibility Visible;
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index dd31b97..d7d9a71 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -109,8 +109,6 @@
}
public enum Visibility {
- method public static androidx.glance.Visibility valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.glance.Visibility[] values();
enum_constant public static final androidx.glance.Visibility Gone;
enum_constant public static final androidx.glance.Visibility Invisible;
enum_constant public static final androidx.glance.Visibility Visible;
diff --git a/glance/glance/build.gradle b/glance/glance/build.gradle
index 60593dc..a15d0fa 100644
--- a/glance/glance/build.gradle
+++ b/glance/glance/build.gradle
@@ -92,9 +92,8 @@
androidx {
name = "Glance"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2021"
description = "Glance allows developers to build layouts for remote surfaces using a Jetpack " +
"Compose-style API."
- targetsJavaConsumers = false
}
diff --git a/gradle.properties b/gradle.properties
index 4df9576..db8b07e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -27,6 +27,8 @@
android.nonTransitiveRClass=true
android.experimental.lint.missingBaselineIsEmptyBaseline=true
android.experimental.lint.reservedMemoryPerTask=1g
+# Remove when AGP defaults to 2.1.0
+android.prefabVersion=2.1.0
# Do generate versioned API files
androidx.writeVersionedApiFiles=true
@@ -54,7 +56,7 @@
android.experimental.dependency.excludeLibraryComponentsFromConstraints=true
# Disallow resolving dependencies at configuration time, which is a slight performance problem
android.dependencyResolutionAtConfigurationTime.disallow=true
-android.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarnings,android.dependencyResolutionAtConfigurationTime.disallow,android.experimental.lint.missingBaselineIsEmptyBaseline,android.lint.printStackTrace,android.lint.baselineOmitLineNumbers,android.experimental.disableCompileSdkChecks,android.overrideVersionCheck,android.r8.maxWorkers,android.experimental.privacysandboxsdk.enable,android.experimental.lint.reservedMemoryPerTask,android.experimental.dependency.excludeLibraryComponentsFromConstraints
+android.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarnings,android.dependencyResolutionAtConfigurationTime.disallow,android.experimental.lint.missingBaselineIsEmptyBaseline,android.lint.printStackTrace,android.lint.baselineOmitLineNumbers,android.experimental.disableCompileSdkChecks,android.overrideVersionCheck,android.r8.maxWorkers,android.experimental.privacysandboxsdk.enable,android.experimental.lint.reservedMemoryPerTask,android.experimental.dependency.excludeLibraryComponentsFromConstraints,android.prefabVersion
# Workaround for b/162074215
android.includeDependencyInfoInApks=false
# Allow multiple r8 tasks at once because otherwise they can make the critical path longer: b/256187923
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 37da84a..725b643 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -57,7 +57,7 @@
moshi = "1.13.0"
protobuf = "3.22.3"
skiko = "0.7.7"
-spdxGradlePlugin = "0.3.0"
+spdxGradlePlugin = "0.6.0"
sqldelight = "1.3.0"
retrofit = "2.7.2"
wire = "4.7.0"
@@ -147,7 +147,6 @@
jcodec = { module = "org.jcodec:jcodec", version.ref = "jcodec" }
jcodecJavaSe = { module = "org.jcodec:jcodec-javase", version.ref = "jcodec" }
json = { module = "org.json:json", version = "20180813" }
-jsonSimple = { module = "com.googlecode.json-simple:json-simple", version = "1.1" }
jsoup = { module = "org.jsoup:jsoup", version = "1.16.2" }
jsqlparser = { module = "com.github.jsqlparser:jsqlparser", version = "3.1" }
jsr250 = { module = "javax.annotation:javax.annotation-api", version = "1.2" }
diff --git a/gradle/verification-keyring.keys b/gradle/verification-keyring.keys
index 7c4300e..02dc99e 100644
--- a/gradle/verification-keyring.keys
+++ b/gradle/verification-keyring.keys
@@ -3783,51 +3783,6 @@
=V/CK
-----END PGP PUBLIC KEY BLOCK-----
-pub EB9D04A9A679FE18
-uid Uber Technologies Inc. (Uber Technologies Inc.) <developer@uber.com>
-
-sub 34CF779A8B04720E
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: BCPG v1.68
-
-mQINBFYC8RoBEADCz+haHo3OuO5kVDhQ1W+GtVcYLuORKvKPaymrD7VSkYyUdkNS
-EGkrk7g5ih/SpgrOxND+Sw3gjwzFr8pjktVkh2bkc1QLRdBcFgqpbmVpVXNodAt0
-4Ie16av2YJpE4tqNaSX8BiBGRdMi1NvbR+EzKQMC5VLUAlbIBPVzHsR6ppbCWPBv
-/Do+QP9OnjiSRUoDlJ2ddCnWiXSM2dcoyd/UOGCS6uXybIJxtGRG1QgbhCYdDc3b
-9XbKcJ/TC9C3hFAUkCQEyqMry5mSl16KTVvYeKacKsTOQV/W5PFbxkVNjGWF3k9q
-f+7IWVi26COlg1Dp5xxY+L5QbFUANNQK221Sk29hHy4hRKqRz5ozOFFI8z7t4VSa
-p2J5cyuN8ojti8DMssGrX83dvN8HLnKPXBGTZBXErBJIRPKMcX0W+nFnYJxOi0O1
-18rVm+7GNUMhV6lsgq8xUZ3lOxQZo3XMbO2aof+31KLDRpVzdvnp/OCZMWmSyP77
-SurcrS0IHaJy9c07WVkcx89KnXiaXp8WesP4nfCB07JZAifIypB0j5I2Jv0WsCJz
-cVWYk4reMmuW/hVZHrGairelhAaTw6Vc5eixouNa5X4rCqCuK714lNjmbVelrFVp
-5YNaMNA6WPAgM5wv2UwG7k5tQI5pFQER60g8SwHr1obK+OrrCyh6/kMQGwARAQAB
-tERVYmVyIFRlY2hub2xvZ2llcyBJbmMuIChVYmVyIFRlY2hub2xvZ2llcyBJbmMu
-KSA8ZGV2ZWxvcGVyQHViZXIuY29tPrkCDQRWAvEaARAA2AVeuRCudSAfIYC06WKO
-KyJtqPHL4LBkGtRHKg0+xZ8P5pWjE49WYG7TqD8meIJIlujH8qu6oPWDZoWQdGq7
-rdk88gHgOeAXSLsDwNBIrRxlyH6lBbhaNz9JGUYdt2jvaALlSMRSn4zvB2GiWTMc
-f5oPfYpUViW2/u2jh9bxePCmdOGVZ7Sl+mdhNmYAuoYjfMQgKjlbDxnj03lsOET1
-eNsJWVmYXcPHbCHTK/C9xgfhDZjjG7lUj5b/2vx4TohGayt6xZ3QtuJymEQxSL5E
-HXKzPjNR7rqoa3qZ5gQDjN6TOddzBhnmL/FEHzL2jn/M4xKRLes25/CKJngQ0MCB
-ML/jT/LmqVAucf6p4jtKmh81yhp0xCjgxnsf+wwoDYY7C1GutEbncRfZ/bc+j+wT
-BPu4yqg2aMRaeR2bsXGetEKc7VwXKgeb99YazSBI+NfV8UHWVeZWmw0+tk0OqsId
-IXW3XqszYVE59iDV0hYp+eM4rWWU/ynx86aXt19fBLh1BKKL4tV0XloY/9uBq/gB
-yoLTJdykhF1U8cBaLwfexa5uxOpFs14w6Lj63HXGDnLo19LQpG4wwzQmF2ArfyQh
-AzypP5pUZhO9n7rc+Ri1lKMjYupI7nhzM7VMQN5IudyNvAVuLM0m8HkWe6LAW9kj
-VkDBCXEfVIdA5Zg2stMTP1MAEQEAAYkCJQQYAQoADwUCVgLxGgIbDAUJB4YfgAAK
-CRDrnQSppnn+GKn5D/wJ4fNRN5ec8Q77piuiXmb+kHWDZZZ118iPY4NA4q4N2yVg
-BPZZcxZ0EX+c9Qz4ecgnbExlXSH05jmhotKsBTHAlncnR0bHYAUEdPXsPzH88hjO
-w2vDT9ff15WieBTvRSxL+Fmib8plYyCBCchPyQVNHErN00CjUnqVXl2HwrI8Wkwb
-KUANC0wRobpumE9/4K3su9CqH7CdrPFtcKPKZKkjynXrwUFvvzH2GhynsvCJHXB4
-9l0iIWiNJCr2+qjLrO/eqp4IIXyKsHIHSNmhtFL+z6KlKBZVHZrOzjjm0LFiMS2+
-3bJYtSFOALk+sbz5wG+xji0hubq/66pITjRBEQGltmcKCVq10xngbVjconF8PE3s
-2yHBbF7I98mIBZtWHiqQhcuw/IkkGA4SoaWCbJ34C9YmvZ+Z1GNyMbnhRnHH+uYY
-iMHVFwZpAg3L/zh7oSOkrg9iM0LxzqM1QtclNbUF0rT5d+D5ZEUaWit6YOm8mIPc
-2nGND+IORdoHZ7sqeEfq+htLi7p3rmhUiL7bF3SsDyw6ZoxLtJ8YsDgvzh72TA+m
-bf2DKPVnQBE8bz0//jGlZ3c1pOtYacN2iq6MfD4tfqbxe23qknVYpnI4ehSvqY5U
-6+mL342Ap7SCJzQwZWPu4sRdpA3nQPZBuPy9CT/MaGaGYxJ6+62lw9q97L99vw==
-=IUIB
------END PGP PUBLIC KEY BLOCK-----
-
pub ECDFEA3CB4493B94
sub 3BD211F725778C36
-----BEGIN PGP PUBLIC KEY BLOCK-----
@@ -4149,6 +4104,22 @@
=Q4rB
-----END PGP PUBLIC KEY BLOCK-----
+pub FD116C1969FCCFF3
+uid Sean Leary <stleary@gmail.com>
+
+sub 012F07EDD27CB2E2
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+mDMEZI8otBYJKwYBBAHaRw8BAQdAL5VNS8O2NJbsTZaMphHGdxsSaLUj0tZLI6+R
+/pve51q0HlNlYW4gTGVhcnkgPHN0bGVhcnlAZ21haWwuY29tPrg4BGSPKLQSCisG
+AQQBl1UBBQEBB0AFhS6K/+f89I7wA9kYnS7kXdFrCDXJbS2yu92EBH/AZAMBCAeI
+fgQYFgoAJhYhBPs1yNArRyTa2iPeCv0RbBlp/M/zBQJkjyi0AhsMBQkDwmcAAAoJ
+EP0RbBlp/M/znrgBAJzCXJYXxO6q5s+TijkaBHKQlp4gmES9lWbbAgXptup+AP0f
+MGwYCAJDgXPx5vc1B0y9XtKm3mQS0zNQjv6ffStsCg==
+=0XS7
+-----END PGP PUBLIC KEY BLOCK-----
+
pub FD5DEA07FCB690A8
uid Baptiste Mathus <baptiste@codehaus.org>
@@ -7595,35 +7566,6 @@
=wBmj
-----END PGP PUBLIC KEY BLOCK-----
-pub 5208812E1E4A6DB0
-uid Gradle Inc. <info@gradle.com>
-
-sub 33AEBC1F01C98081
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: BCPG v1.68
-
-mQENBFxaxWYBCADI4/gRCJYfXwZYdUoEGlAlCfRgABy90rvebzcs8MKtolAbPVkG
-iqnjftXd28sZhEDx9YJrUfmzspdrYmU7hy1kgV1/WGIcWyTExTH3bqlnaIWnnTxA
-HD0x4NJ2AzmX5VO8LxhqGID+BErrv7uGZvVmJT6trqUIcKeNEq7mzdDJKqTBY4cw
-q+Dm8P0vs4IFTD8q5f1Vr78FmUth2srIBmsIH1wNV1nAUTmQppNBFlCmcvnWTYI1
-0UMcsFFrJ2pFT1yP2AEGUNl4Lgj4hmVHZwX38/lu9pQ7iWtHSLOwZsfuC34/goS+
-ldFt63JqDV7ZaqwAgk7Iq6jbr4pSVsB4VdglABEBAAG0HUdyYWRsZSBJbmMuIDxp
-bmZvQGdyYWRsZS5jb20+uQENBFxaxWYBCADe16jph/XeYDGdbg2WhAZTpoxa1xMB
-ti29uLMXQTRJx6mq2FwVui7gUY375hBTSPN/sS1zSGYf+DGbNFhJ0DvaVKbnyFbU
-iS+RjUlBSf5VP/00KwA/++dJ8FvkvkmQL6C1+DbqkgEl2YBA0Ar8hhTYLiAMqnxa
-Ik/sO/szoi4Q6eNGaQy8fB3IWMwq9MmWdLKV2mxzoRxeUXnnNCfjE3RzMP9t7TWI
-fwHVJsVQskbV29eYdNAH6dNUGRj6ttFQgFWrP1mhy5N8l4tnocOVzF9umM9fY08l
-WqEMoBWae6G6R67modMyBQCnEDeogKnPGSnQ2IvASmZ8Qeb/zZpJkxcJABEBAAGJ
-ATYEGAEIACACGwwWIQQxT+guWkxTd7yi7exSCIEuHkptsAUCXFrGrwAKCRBSCIEu
-HkptsEXdB/9m9GutEADMthk9kQi/Zd3RNt27qdYDGlGX9iILeoNJXM8m9piNzE92
-kNhIW2k2Yupuh69OpKP11E1EzGbdOdbwB2yKIhCOJxNb2QiZoxikdcD4vE2n0e5S
-gSq0H2pDt4v9Dy0pWOtyyi3muo+P28k/IgY4nRd3DR2FaBiXXl863kpPt8c1aTo5
-y2u1qDWfNNPtpkfmQcBNOigT/jrqzHjgeTRqtSPWppPl0H0bElerBcTBK7+AX7wL
-kXtlCgFZ7fWs32+gMhKJXVhsefwgjAfKBIRS7oOmZtmlWA6gC2HXpXkcn4xDOQo1
-wc/ZbMFjyklLBAm1WETBiqR+k5uwXmJ8
-=Ubrp
------END PGP PUBLIC KEY BLOCK-----
-
pub 55C7E5E701832382
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: BCPG v1.68
@@ -8187,32 +8129,6 @@
=w18e
-----END PGP PUBLIC KEY BLOCK-----
-pub 604F437C1682DDE5
-sub F664BA5FCF7560B9
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: BCPG v1.68
-
-mQENBFv2vg0BCADQzzfU42MkbydOEese82s5wyfs/qkUZOQatjwPMyEMlK/H7LZc
-Bk2ZOgBMaDDdEZv2Vq+6oUa/rD/Xmr71gCEk5U1rqeZZ4HuvyEZjjIisjnVrMCT8
-py93im2MJb1zifvY+rgiveiVBNCjSkfqX1/g7HmGiYL30romA/io3jvtWOo6PkQf
-GYzifOKz/5j9oWm1yqdXuhVmPD1aUkpXZeoxfWDpaPS+iUm4Db38umVj7GY55okB
-rrfQAdAUuOG9dyseIgI4HPZMB562Qy2tww5q+PvSxs9ydgSbFHFu1ejCmlViSeXL
-leGPJEoiRdyu9zMW5sU68lxhErtFRpa3IORjABEBAAG5AQ0EW/a+DQEIAMiknjyN
-qHzzG07PNE68DSlUuMvraoyJSkcA6Sjsg1nTgcp9jubqekXW+ZHnNYWZJSxZx4RZ
-wloo3+A4skthmDVh2UN/FO9Vwx1EGkJuyaKELmIVBRSC4IBrVrQ/4/nUXMtG9NhL
-Qmgug3glnlXYg5gqlQc2YjNiWI1zdYmR7pHTxBhDYLB1hweA8X7SH690HDevqhiy
-/qT8YAHZzMZaauj/xQHpvn4uN5xpGm1eQw47tormy7I/1QaDW3pbS4YIC+Q7gdYd
-IA217tTgN5OA8+kXuPJJQKDMG0WtEGegYjMMuNh266HMtfekJVSlJTRdIFQT1j1U
-0OLjrk9WRK/ZcrcAEQEAAYkBJQQYAQIADwUCW/a+DQIbDAUJAeEzgAAKCRBgT0N8
-FoLd5RodB/4pZu8Segyb2VhGYbl0jgmZFqMZDI9iPINx7oZ+09Ck32R6UJgaiaTw
-KT0qeEssum3oj7zz3r5D0s1k5pwvd0w5TLL8CeQ7NxTy+hE+8cHZbFfoMBlnr73c
-UhedkZk+Cf7dm/GVkv1ERr/XnL46wLO3OAMamh2wwo9Od2GP58ZFgFd3jhroIuTt
-YtBVeB21JO5eaktP1ZLi7zsGWcP2mPkQAnd7BtQGwjh6x6M+Xhs/mZEmLP2/nLEf
-oX3eaWU6uE54giiHDC33rBGerBuHGeW0WT7wzatKPz9S51w7mqPXLYNmw7/Qwfvi
-Ca2w4l1R/HAaNTH5suZ1HGKy1nNcY6aA
-=fcxU
------END PGP PUBLIC KEY BLOCK-----
-
pub 62BA9C275D14234E
uid Pierre Yves Ricau <py.ricau@gmail.com>
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 82e12f6..e4547d5 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -470,6 +470,7 @@
</trusted-key>
<trusted-key id="FA7929F83AD44C4590F6CC6815C71C0A4E0B8EDD" group="net.java.dev.jna"/>
<trusted-key id="FAABC3738B1F58DA2D776FA2EB380DC13C39F675" group="com.intellij"/>
+ <trusted-key id="FB35C8D02B4724DADA23DE0AFD116C1969FCCFF3" group="org.json" name="json" />
<trusted-key id="FC411CD3CB7DCB0ABC9801058118B3BCDB1A5000" group="jakarta.xml.bind"/>
<trusted-key id="FF460ACF3266FDCE8EB8FE3BA797295E9D87BDD0" group="androidx.build.gradle.gcpbuildcache" name="gcpbuildcache"/>
<trusted-key id="FF6E2C001948C5F2F38B0CC385911F425EC61B51">
@@ -633,12 +634,12 @@
<sha256 value="6d4e2b5a118aab62e6e5e29d185a0224eed82c85c40ac3d33cf04a270c3b3744" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
- <component group="com.google.prefab" name="cli" version="2.0.0">
- <artifact name="cli-2.0.0-all.jar">
- <sha256 value="d9bd89f68446b82be038aae774771ad85922d0b375209b17625a2734b5317e29" origin="Generated by Gradle" reason="https://github.com/google/prefab/issues/157"/>
+ <component group="com.google.prefab" name="cli" version="2.1.0">
+ <artifact name="cli-2.1.0-all.jar">
+ <sha256 value="e219c8cd6bfd9ff71503a57c6af34b9ba060f03525cc3e58330dee53245a5ed6" origin="Generated by Gradle" reason="https://github.com/google/prefab/issues/157"/>
</artifact>
- <artifact name="cli-2.0.0.pom">
- <sha256 value="4856401a263b39c5394b36a16e0d99628cf05c68008a0cda9691c72bb101e1df" origin="Generated by Gradle" reason="https://github.com/google/prefab/issues/157"/>
+ <artifact name="cli-2.1.0.pom">
+ <sha256 value="110661a35386802ab731d11a0ea050444fc3c9f31c936f31a3ad58bf843be30f" origin="Generated by Gradle" reason="https://github.com/google/prefab/issues/157"/>
</artifact>
</component>
<component group="com.googlecode.json-simple" name="json-simple" version="1.1">
@@ -860,6 +861,11 @@
<sha256 value="c0c39f941138dd193676a3b1c28b8a36b7433ec760b979c69699241bdecee4cb" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
+ <component group="org.json" name="json" version="20231013">
+ <artifact name="json-20231013.pom">
+ <pgp value="FB35C8D02B4724DADA23DE0AFD116C1969FCCFF3"/>
+ </artifact>
+ </component>
<component group="org.ow2" name="ow2" version="1.5">
<artifact name="ow2-1.5.pom">
<sha256 value="0f8a1b116e760b8fe6389c51b84e4b07a70fc11082d4f936e453b583dd50b43b" origin="Generated by Gradle" reason="Artifact is not signed"/>
diff --git a/gradlew b/gradlew
index 4019b0b..28d0634 100755
--- a/gradlew
+++ b/gradlew
@@ -421,6 +421,9 @@
RETURN_VALUE=0
set -- "$@" -Dorg.gradle.projectcachedir="$OUT_DIR/gradle-project-cache"
+ KOTLIN_PROJECT_PERSISTENT_DIR="$OUT_DIR/kotlin-project-persistent-dir"
+ mkdir -p "$KOTLIN_PROJECT_PERSISTENT_DIR"
+ set -- "$@" -Pkotlin.project.persistent.dir="$KOTLIN_PROJECT_PERSISTENT_DIR"
# Disabled in Studio until these errors become shown (b/268380971) or computed more quickly (https://github.com/gradle/gradle/issues/23272)
if [[ " ${@} " =~ " --dependency-verification=" ]]; then
VERIFICATION_ARGUMENT="" # already specified by caller
diff --git a/graphics/graphics-core/api/current.ignore b/graphics/graphics-core/api/current.ignore
deleted file mode 100644
index d2d6395..0000000
--- a/graphics/graphics-core/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.graphics.CanvasBufferedRenderer#releaseBuffer(android.hardware.HardwareBuffer):
- Added method androidx.graphics.CanvasBufferedRenderer.releaseBuffer(android.hardware.HardwareBuffer)
diff --git a/graphics/graphics-core/api/restricted_current.ignore b/graphics/graphics-core/api/restricted_current.ignore
deleted file mode 100644
index d2d6395..0000000
--- a/graphics/graphics-core/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.graphics.CanvasBufferedRenderer#releaseBuffer(android.hardware.HardwareBuffer):
- Added method androidx.graphics.CanvasBufferedRenderer.releaseBuffer(android.hardware.HardwareBuffer)
diff --git a/graphics/graphics-core/build.gradle b/graphics/graphics-core/build.gradle
index 45ad794..14e500d 100644
--- a/graphics/graphics-core/build.gradle
+++ b/graphics/graphics-core/build.gradle
@@ -48,6 +48,7 @@
namespace 'androidx.graphics.core'
defaultConfig {
+ minSdkVersion 21
externalNativeBuild {
def versionScript = file("src/main/cpp/jni.lds").getAbsolutePath()
diff --git a/graphics/graphics-core/samples/build.gradle b/graphics/graphics-core/samples/build.gradle
index cdf56a1..4134f3f 100644
--- a/graphics/graphics-core/samples/build.gradle
+++ b/graphics/graphics-core/samples/build.gradle
@@ -32,6 +32,9 @@
android {
namespace "androidx.graphics.core.samples"
+ defaultConfig {
+ minSdkVersion 21
+ }
}
androidx {
diff --git a/graphics/graphics-core/src/main/java/androidx/opengl/EGLExt.kt b/graphics/graphics-core/src/main/java/androidx/opengl/EGLExt.kt
index 89acecb..4ec0b3c 100644
--- a/graphics/graphics-core/src/main/java/androidx/opengl/EGLExt.kt
+++ b/graphics/graphics-core/src/main/java/androidx/opengl/EGLExt.kt
@@ -375,7 +375,7 @@
hardwareBuffer: HardwareBuffer
): EGLImageKHR? {
val handle = EGLBindings.nCreateImageFromHardwareBuffer(
- eglDisplay.obtainNativeHandle(), hardwareBuffer
+ eglDisplay.nativeHandle, hardwareBuffer
)
return if (handle == 0L) {
null
@@ -404,7 +404,7 @@
eglDisplay: EGLDisplay,
image: EGLImageKHR
): Boolean = EGLBindings.nDestroyImageKHR(
- eglDisplay.obtainNativeHandle(),
+ eglDisplay.nativeHandle,
image.nativeHandle
)
@@ -451,7 +451,7 @@
attributes: EGLConfigAttributes?
): EGLSyncKHR? {
val handle = EGLBindings.nCreateSyncKHR(
- eglDisplay.obtainNativeHandle(), type, attributes?.attrs
+ eglDisplay.nativeHandle, type, attributes?.attrs
)
return if (handle == 0L) {
null
@@ -489,7 +489,7 @@
offset: Int
): Boolean =
EGLBindings.nGetSyncAttribKHR(
- eglDisplay.obtainNativeHandle(),
+ eglDisplay.nativeHandle,
sync.nativeHandle,
attribute,
value,
@@ -547,7 +547,7 @@
timeoutNanos: Long
): @EGLClientWaitResult Int =
EGLBindings.nClientWaitSyncKHR(
- eglDisplay.obtainNativeHandle(),
+ eglDisplay.nativeHandle,
sync.nativeHandle,
flags,
timeoutNanos
@@ -578,7 +578,7 @@
sync: EGLSyncKHR
): SyncFenceCompat {
val fd = EGLBindings.nDupNativeFenceFDANDROID(
- display.obtainNativeHandle(),
+ display.nativeHandle,
sync.nativeHandle
)
return if (fd >= 0) {
@@ -609,7 +609,7 @@
eglDisplay: EGLDisplay,
eglSync: EGLSyncKHR
): Boolean = EGLBindings.nDestroySyncKHR(
- eglDisplay.obtainNativeHandle(),
+ eglDisplay.nativeHandle,
eglSync.nativeHandle
)
@@ -620,22 +620,6 @@
@JvmStatic
fun parseExtensions(queryString: String): Set<String> =
HashSet<String>().apply { addAll(queryString.split(' ')) }
-
- /**
- * Helper method to obtain the corresponding native handle. Newer versions of Android
- * represent the native pointer as a long instead of an integer to support 64 bits.
- * For OS levels that support the wider bit format, invoke it otherwise cast the int
- * to a long.
- *
- * This is internal to avoid synthetic accessors
- */
- internal fun EGLDisplay.obtainNativeHandle(): Long =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- EGLDisplayVerificationHelper.getNativeHandle(this)
- } else {
- @Suppress("DEPRECATION")
- handle.toLong()
- }
}
}
@@ -739,16 +723,3 @@
}
}
}
-
-/**
- * Helper class to avoid class verification failures
- */
-@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-private class EGLDisplayVerificationHelper private constructor() {
-
- companion object {
- @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
- @androidx.annotation.DoNotInline
- fun getNativeHandle(eglDisplay: EGLDisplay): Long = eglDisplay.nativeHandle
- }
-}
diff --git a/graphics/graphics-path/api/current.txt b/graphics/graphics-path/api/current.txt
index 461614b..36803b6 100644
--- a/graphics/graphics-path/api/current.txt
+++ b/graphics/graphics-path/api/current.txt
@@ -18,8 +18,6 @@
}
public enum PathIterator.ConicEvaluation {
- method public static androidx.graphics.path.PathIterator.ConicEvaluation valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.graphics.path.PathIterator.ConicEvaluation[] values();
enum_constant public static final androidx.graphics.path.PathIterator.ConicEvaluation AsConic;
enum_constant public static final androidx.graphics.path.PathIterator.ConicEvaluation AsQuadratics;
}
@@ -34,8 +32,6 @@
}
public enum PathSegment.Type {
- method public static androidx.graphics.path.PathSegment.Type valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.graphics.path.PathSegment.Type[] values();
enum_constant public static final androidx.graphics.path.PathSegment.Type Close;
enum_constant public static final androidx.graphics.path.PathSegment.Type Conic;
enum_constant public static final androidx.graphics.path.PathSegment.Type Cubic;
diff --git a/graphics/graphics-path/api/restricted_current.txt b/graphics/graphics-path/api/restricted_current.txt
index 461614b..36803b6 100644
--- a/graphics/graphics-path/api/restricted_current.txt
+++ b/graphics/graphics-path/api/restricted_current.txt
@@ -18,8 +18,6 @@
}
public enum PathIterator.ConicEvaluation {
- method public static androidx.graphics.path.PathIterator.ConicEvaluation valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.graphics.path.PathIterator.ConicEvaluation[] values();
enum_constant public static final androidx.graphics.path.PathIterator.ConicEvaluation AsConic;
enum_constant public static final androidx.graphics.path.PathIterator.ConicEvaluation AsQuadratics;
}
@@ -34,8 +32,6 @@
}
public enum PathSegment.Type {
- method public static androidx.graphics.path.PathSegment.Type valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.graphics.path.PathSegment.Type[] values();
enum_constant public static final androidx.graphics.path.PathSegment.Type Close;
enum_constant public static final androidx.graphics.path.PathSegment.Type Conic;
enum_constant public static final androidx.graphics.path.PathSegment.Type Cubic;
diff --git a/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIterator.kt b/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIterator.kt
index 700e7a6..9e22e5d 100644
--- a/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIterator.kt
+++ b/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIterator.kt
@@ -29,7 +29,7 @@
* [PathIterator] objects are created implicitly through a given [Path] object; to create a
* [PathIterator], call one of the two [Path.iterator] extension functions.
*/
-@Suppress("NotCloseable", "IllegalExperimentalApiUsage")
+@Suppress("NotCloseable")
class PathIterator constructor(
val path: Path,
val conicEvaluation: ConicEvaluation = ConicEvaluation.AsQuadratics,
diff --git a/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIteratorImpl.kt b/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIteratorImpl.kt
index 6dfff55..f22f8a7 100644
--- a/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIteratorImpl.kt
+++ b/graphics/graphics-path/src/main/java/androidx/graphics/path/PathIteratorImpl.kt
@@ -28,7 +28,6 @@
* is implemented in the subclasses except for [next], which relies on shared native code
* to perform conic conversion.
*/
-@Suppress("IllegalExperimentalApiUsage")
internal abstract class PathIteratorImpl(
val path: Path,
val conicEvaluation: ConicEvaluation = ConicEvaluation.AsQuadratics,
diff --git a/graphics/graphics-shapes/api/1.0.0-beta01.txt b/graphics/graphics-shapes/api/1.0.0-beta01.txt
index a93466c..f9d62d6f 100644
--- a/graphics/graphics-shapes/api/1.0.0-beta01.txt
+++ b/graphics/graphics-shapes/api/1.0.0-beta01.txt
@@ -56,6 +56,10 @@
public final class Morph {
ctor public Morph(androidx.graphics.shapes.RoundedPolygon start, androidx.graphics.shapes.RoundedPolygon end);
method public java.util.List<androidx.graphics.shapes.Cubic> asCubics(float progress);
+ method public float[] calculateBounds();
+ method public float[] calculateBounds(optional float[] bounds);
+ method public float[] calculateBounds(optional float[] bounds, optional boolean approximate);
+ method public float[] calculateMaxBounds(optional float[] bounds);
method public inline void forEachCubic(float progress, optional androidx.graphics.shapes.MutableCubic mutableCubic, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
method public inline void forEachCubic(float progress, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
}
@@ -134,8 +138,9 @@
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding);
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing);
- method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional float centerX);
- method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional float centerX, optional float centerY);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX, optional float centerY);
method public static androidx.graphics.shapes.RoundedPolygon rectangle(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX, optional float centerY);
method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius);
method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius);
diff --git a/graphics/graphics-shapes/api/current.txt b/graphics/graphics-shapes/api/current.txt
index a93466c..f9d62d6f 100644
--- a/graphics/graphics-shapes/api/current.txt
+++ b/graphics/graphics-shapes/api/current.txt
@@ -56,6 +56,10 @@
public final class Morph {
ctor public Morph(androidx.graphics.shapes.RoundedPolygon start, androidx.graphics.shapes.RoundedPolygon end);
method public java.util.List<androidx.graphics.shapes.Cubic> asCubics(float progress);
+ method public float[] calculateBounds();
+ method public float[] calculateBounds(optional float[] bounds);
+ method public float[] calculateBounds(optional float[] bounds, optional boolean approximate);
+ method public float[] calculateMaxBounds(optional float[] bounds);
method public inline void forEachCubic(float progress, optional androidx.graphics.shapes.MutableCubic mutableCubic, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
method public inline void forEachCubic(float progress, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
}
@@ -134,8 +138,9 @@
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding);
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing);
- method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional float centerX);
- method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional float centerX, optional float centerY);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX, optional float centerY);
method public static androidx.graphics.shapes.RoundedPolygon rectangle(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX, optional float centerY);
method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius);
method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius);
diff --git a/graphics/graphics-shapes/api/restricted_1.0.0-beta01.txt b/graphics/graphics-shapes/api/restricted_1.0.0-beta01.txt
index 6f683a9..d691f29 100644
--- a/graphics/graphics-shapes/api/restricted_1.0.0-beta01.txt
+++ b/graphics/graphics-shapes/api/restricted_1.0.0-beta01.txt
@@ -56,6 +56,10 @@
public final class Morph {
ctor public Morph(androidx.graphics.shapes.RoundedPolygon start, androidx.graphics.shapes.RoundedPolygon end);
method public java.util.List<androidx.graphics.shapes.Cubic> asCubics(float progress);
+ method public float[] calculateBounds();
+ method public float[] calculateBounds(optional float[] bounds);
+ method public float[] calculateBounds(optional float[] bounds, optional boolean approximate);
+ method public float[] calculateMaxBounds(optional float[] bounds);
method public inline void forEachCubic(float progress, optional androidx.graphics.shapes.MutableCubic mutableCubic, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
method public inline void forEachCubic(float progress, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
property @kotlin.PublishedApi internal final java.util.List<kotlin.Pair<androidx.graphics.shapes.Cubic,androidx.graphics.shapes.Cubic>> morphMatch;
@@ -135,8 +139,9 @@
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding);
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing);
- method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional float centerX);
- method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional float centerX, optional float centerY);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX, optional float centerY);
method public static androidx.graphics.shapes.RoundedPolygon rectangle(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX, optional float centerY);
method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius);
method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius);
diff --git a/graphics/graphics-shapes/api/restricted_current.txt b/graphics/graphics-shapes/api/restricted_current.txt
index 6f683a9..d691f29 100644
--- a/graphics/graphics-shapes/api/restricted_current.txt
+++ b/graphics/graphics-shapes/api/restricted_current.txt
@@ -56,6 +56,10 @@
public final class Morph {
ctor public Morph(androidx.graphics.shapes.RoundedPolygon start, androidx.graphics.shapes.RoundedPolygon end);
method public java.util.List<androidx.graphics.shapes.Cubic> asCubics(float progress);
+ method public float[] calculateBounds();
+ method public float[] calculateBounds(optional float[] bounds);
+ method public float[] calculateBounds(optional float[] bounds, optional boolean approximate);
+ method public float[] calculateMaxBounds(optional float[] bounds);
method public inline void forEachCubic(float progress, optional androidx.graphics.shapes.MutableCubic mutableCubic, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
method public inline void forEachCubic(float progress, kotlin.jvm.functions.Function1<? super androidx.graphics.shapes.MutableCubic,kotlin.Unit> callback);
property @kotlin.PublishedApi internal final java.util.List<kotlin.Pair<androidx.graphics.shapes.Cubic,androidx.graphics.shapes.Cubic>> morphMatch;
@@ -135,8 +139,9 @@
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding);
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding);
method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing);
- method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional float centerX);
- method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional float centerX, optional float centerY);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX);
+ method public static androidx.graphics.shapes.RoundedPolygon pillStar(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional int numVerticesPerRadius, optional @FloatRange(from=0.0, fromInclusive=false, to=1.0, toInclusive=false) float innerRadiusRatio, optional androidx.graphics.shapes.CornerRounding rounding, optional androidx.graphics.shapes.CornerRounding? innerRounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional @FloatRange(from=0.0, to=1.0) float vertexSpacing, optional @FloatRange(from=0.0, to=1.0) float startLocation, optional float centerX, optional float centerY);
method public static androidx.graphics.shapes.RoundedPolygon rectangle(androidx.graphics.shapes.RoundedPolygon.Companion, optional float width, optional float height, optional androidx.graphics.shapes.CornerRounding rounding, optional java.util.List<androidx.graphics.shapes.CornerRounding>? perVertexRounding, optional float centerX, optional float centerY);
method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius);
method public static androidx.graphics.shapes.RoundedPolygon star(androidx.graphics.shapes.RoundedPolygon.Companion, int numVerticesPerRadius, optional float radius);
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 28143e6..e1008ad 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
@@ -151,12 +151,6 @@
var nonzeroCubics = nonzeroCubics(squareFeatures.flatMap { it.cubics })
assertCubicListsEqualish(square.cubics, nonzeroCubics)
- // Same as above but with rounded corners
- val roundedSquare = RoundedPolygon(4, rounding = CornerRounding(.1f))
- val roundedFeatures = roundedSquare.features
- nonzeroCubics = nonzeroCubics(roundedFeatures.flatMap { it.cubics })
- assertCubicListsEqualish(roundedSquare.cubics, nonzeroCubics)
-
// Same as the first polygon test, but with a copy of that polygon
val squareCopy = RoundedPolygon(square)
val squareCopyFeatures = squareCopy.features
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Morph.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Morph.kt
index f8a67cf..46e76126 100644
--- a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Morph.kt
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Morph.kt
@@ -16,6 +16,7 @@
package androidx.graphics.shapes
+import kotlin.math.max
import kotlin.math.min
/**
@@ -35,8 +36,8 @@
* curve placement within the shapes is very different.
*/
class Morph(
- start: RoundedPolygon,
- end: RoundedPolygon
+ private val start: RoundedPolygon,
+ private val end: RoundedPolygon
) {
/**
* The structure which holds the actual shape being morphed. It contains
@@ -50,6 +51,60 @@
private val _morphMatch: List<Pair<Cubic, Cubic>> = match(start, end)
/**
+ * Calculates the axis-aligned bounds of the object.
+ * @param approximate when true, uses a faster calculation to create the bounding
+ * box based on the min/max values of all anchor and control points that make up the shape.
+ * Default value is true.
+ * @param bounds a buffer to hold the results. If not supplied, a temporary buffer will be
+ * created.
+ * @return The axis-aligned bounding box for this object, where the rectangles left,
+ * top, right, and bottom values will be stored in entries 0, 1, 2, and 3, in that order.
+ */
+ @JvmOverloads
+ fun calculateBounds(
+ bounds: FloatArray = FloatArray(4),
+ approximate: Boolean = true
+ ): FloatArray {
+ start.calculateBounds(bounds, approximate)
+ val minX = bounds[0]
+ val minY = bounds[1]
+ val maxX = bounds[2]
+ val maxY = bounds[3]
+ end.calculateBounds(bounds, approximate)
+ bounds[0] = min(minX, bounds[0])
+ bounds[1] = min(minY, bounds[1])
+ bounds[2] = max(maxX, bounds[2])
+ bounds[3] = max(maxY, bounds[3])
+ return bounds
+ }
+
+ /**
+ * Like [calculateBounds], this function calculates the axis-aligned bounds of the
+ * object and returns that rectangle. But this function determines the max dimension of
+ * the shape (by calculating the distance from its center to the start and midpoint of
+ * each curve) and returns a square which can be used to hold the object in any rotation.
+ * This function can be used, for example, to calculate the max size of a UI element meant
+ * to hold this shape in any rotation.
+ * @param bounds a buffer to hold the results. If not supplied, a temporary buffer will be
+ * created.
+ * @return The axis-aligned max bounding box for this object, where the rectangles left,
+ * top, right, and bottom values will be stored in entries 0, 1, 2, and 3, in that order.
+ */
+ fun calculateMaxBounds(bounds: FloatArray = FloatArray(4)): FloatArray {
+ start.calculateMaxBounds(bounds)
+ val minX = bounds[0]
+ val minY = bounds[1]
+ val maxX = bounds[2]
+ val maxY = bounds[3]
+ end.calculateMaxBounds(bounds)
+ bounds[0] = min(minX, bounds[0])
+ bounds[1] = min(minY, bounds[1])
+ bounds[2] = max(maxX, bounds[2])
+ bounds[3] = max(maxY, bounds[3])
+ return bounds
+ }
+
+ /**
* Returns a representation of the morph object at a given [progress] value as a list of Cubics.
* Note that this function causes a new list to be created and populated, so there is some
* overhead.
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 73d708e..b8fb209 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
@@ -43,8 +43,22 @@
// by those points being slightly off, even by much less than a pixel
var firstCubic: Cubic? = null
var lastCubic: Cubic? = null
- for (i in features.indices) {
- val featureCubics = features[i].cubics
+ var firstFeatureSplitStart: List<Cubic>? = null
+ var firstFeatureSplitEnd: List<Cubic>? = null
+ if (features.size > 0 && features[0].cubics.size == 3) {
+ val centerCubic = features[0].cubics[1]
+ val (start, end) = centerCubic.split(.5f)
+ firstFeatureSplitStart = mutableListOf(features[0].cubics[0], start)
+ firstFeatureSplitEnd = mutableListOf(end, features[0].cubics[2])
+ }
+ // iterating one past the features list size allows us to insert the initial split
+ // cubic if it exists
+ for (i in 0..features.size) {
+ val featureCubics = if (i == 0 && firstFeatureSplitEnd != null) firstFeatureSplitEnd
+ else if (i == features.size) {
+ if (firstFeatureSplitStart != null) firstFeatureSplitStart
+ else break
+ } else features[i].cubics
for (j in featureCubics.indices) {
// Skip zero-length curves; they add nothing and can trigger rendering artifacts
val cubic = featureCubics[j]
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Shapes.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Shapes.kt
index a7ebd90..a4ef763 100644
--- a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Shapes.kt
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Shapes.kt
@@ -156,8 +156,8 @@
// Star polygon is just a polygon with all vertices supplied (where we generate
// those vertices to be on the inner/outer radii)
return RoundedPolygon(
- starVerticesFromNumVerts(numVerticesPerRadius, radius, innerRadius, centerX, centerY),
- rounding, pvRounding, centerX, centerY
+ starVerticesFromNumVerts(numVerticesPerRadius, radius, innerRadius,
+ centerX, centerY), rounding, pvRounding, centerX, centerY
)
}
@@ -246,6 +246,11 @@
* A value of 1 does the opposite, with the outer vertices spaced the same as the
* vertices on the straight edges. The default value is .5, which takes the average of these
* two extremes.
+ * @param startLocation A value from 0 to 1 which determines how far along the perimeter of
+ * this shape to start the underlying curves of which it is comprised. This is not usually
+ * needed or noticed by the user. But if the caller wants to manually and gradually stroke the path
+ * when drawing it, it might matter where that path outline begins and ends. The default
+ * value is 0.
* @param centerX The X coordinate of the center of the polygon, around which all vertices will
* be placed. The default center is at (0,0).
* @param centerY The Y coordinate of the center of the polygon, around which all vertices will
@@ -266,6 +271,7 @@
innerRounding: CornerRounding? = null,
perVertexRounding: List<CornerRounding>? = null,
@FloatRange(from = 0.0, to = 1.0) vertexSpacing: Float = 0.5f,
+ @FloatRange(from = 0.0, to = 1.0) startLocation: Float = 0f,
centerX: Float = 0f,
centerY: Float = 0f
): RoundedPolygon {
@@ -286,7 +292,8 @@
}
return RoundedPolygon(
pillStarVerticesFromNumVerts(
- numVerticesPerRadius, width, height, innerRadiusRatio, vertexSpacing, centerX, centerY
+ numVerticesPerRadius, width, height, innerRadiusRatio, vertexSpacing,
+ startLocation, centerX, centerY
),
rounding, pvRounding, centerX, centerY
)
@@ -298,6 +305,7 @@
height: Float,
innerRadius: Float,
vertexSpacing: Float,
+ startLocation: Float,
centerX: Float,
centerY: Float
): FloatArray {
@@ -352,9 +360,10 @@
// in which it lands
var secStart = 0f
var secEnd = sections[1]
- // t value is used to place each vertex. We start at 0, which is on the positive x axis,
- // moving into section 0 to begin with
- var t = 0f
+ // t value is used to place each vertex. 0 is on the positive x axis,
+ // moving into section 0 to begin with. startLocation, a value from 0 to 1, varies the location
+ // anywhere on the perimeter of the shape
+ var t = startLocation * perimeter
// The list of vertices to be returned
val result = FloatArray(numVerticesPerRadius * 4)
var arrayIndex = 0
@@ -365,15 +374,18 @@
// Each iteration through this loop uses the next t value as we walk around the shape
for (i in 0 until numVerticesPerRadius * 2) {
- // Find current section for t. Stop at last section to avoid overflowing past the start.
- while (currSecIndex < 9 && t >= sections[currSecIndex + 1]) {
- currSecIndex = currSecIndex + 1
+ // t could start (and end) after 0; extra boundedT logic makes sure it does the right
+ // thing when crossing the boundar past 0 again
+ val boundedT = t % perimeter
+ if (boundedT < secStart) currSecIndex = 0
+ while (boundedT >= sections[(currSecIndex + 1) % sections.size]) {
+ currSecIndex = (currSecIndex + 1) % sections.size
secStart = sections[currSecIndex]
- secEnd = sections[currSecIndex + 1]
+ secEnd = sections[(currSecIndex + 1) % sections.size]
}
// find t in section and its proportion of that section's total length
- val tInSection = t - secStart
+ val tInSection = boundedT - secStart
val tProportion = tInSection / (secEnd - secStart)
// The vertex placement in a section varies depending on whether it is on one of the
diff --git a/graphics/integration-tests/testapp-compose/src/main/AndroidManifest.xml b/graphics/integration-tests/testapp-compose/src/main/AndroidManifest.xml
index 4652d53..daf6710 100644
--- a/graphics/integration-tests/testapp-compose/src/main/AndroidManifest.xml
+++ b/graphics/integration-tests/testapp-compose/src/main/AndroidManifest.xml
@@ -17,8 +17,29 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
- <activity android:name=".MainActivity"
+ <activity
+ android:name=".SimpleMorph"
+ android:exported="true"
+ android:label="Simple Morph"
+ android:theme="@android:style/Theme.Material.Light.NoActionBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".MainActivity"
+ android:exported="true"
android:label="Graphics Shapes Test - Compose"
+ android:theme="@android:style/Theme.Material.Light.NoActionBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ProgressActivity"
+ android:label="Animating, Shaped Progress Bar"
android:exported="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar">
<intent-filter>
@@ -26,7 +47,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
-
</application>
</manifest>
diff --git a/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/Compose.kt b/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/Compose.kt
index c1ba1a0..78e51f6 100644
--- a/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/Compose.kt
+++ b/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/Compose.kt
@@ -14,17 +14,23 @@
* limitations under the License.
*/
-@file:Suppress("NOTHING_TO_INLINE")
-
package androidx.graphics.shapes.testcompose
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
import androidx.graphics.shapes.Cubic
-import androidx.graphics.shapes.MutableCubic
+import androidx.graphics.shapes.Morph
import androidx.graphics.shapes.RoundedPolygon
import androidx.graphics.shapes.TransformResult
+import kotlin.math.max
+import kotlin.math.min
/**
* Utility functions providing more idiomatic ways of transforming RoundedPolygons and
@@ -37,16 +43,6 @@
*/
/**
- * Scales a shape (given as a Sequence) in place.
- * As this works in Sequences, it doesn't create the whole list at any point, only one
- * MutableCubic is (re)used.
- */
-fun Sequence<MutableCubic>.scaled(scale: Float) = map {
- it.transform { x, y -> TransformResult(x * scale, y * scale) }
- it
-}
-
-/**
* Scales a shape (given as a List), creating a new List.
*/
fun List<Cubic>.scaled(scale: Float) = map {
@@ -54,6 +50,61 @@
}
/**
+ * Gets a [Path] representation for a [RoundedPolygon] shape, which can be used to draw the
+ * polygon.
+ *
+ * @param path an optional [Path] object which, if supplied, will avoid the function having
+ * to create a new [Path] object
+ */
+@JvmOverloads
+fun RoundedPolygon.toPath(path: Path = Path()): Path {
+ pathFromCubics(path, cubics)
+ return path
+}
+
+/**
+ * Gets a [Path] representation for a [Morph] shape. This [Path] can be used to draw the
+ * morph.
+ *
+ * @param progress a value from 0 to 1 that determines the morph's current
+ * shape, between the start and end shapes provided at construction time. A value of 0 results
+ * in the start shape, a value of 1 results in the end shape, and any value in between
+ * results in a shape which is a linear interpolation between those two shapes.
+ * The range is generally [0..1] and values outside could result in undefined shapes, but
+ * values close to (but outside) the range can be used to get an exaggerated effect
+ * (e.g., for a bounce or overshoot animation).
+ * @param path an optional [Path] object which, if supplied, will avoid the function having
+ * to create a new [Path] object
+ */
+fun Morph.toPath(progress: Float, path: Path = Path()): Path {
+ pathFromCubics(path, asCubics(progress))
+ return path
+}
+
+/**
+ * Returns the geometry of the given [cubics] in the given [path] object. This is used
+ * internally by the toPath functions, but we could consider exposing it as public API
+ * in case anyone was dealing directly with the cubics we create for our shapes.
+ */
+private fun pathFromCubics(
+ path: Path,
+ cubics: List<Cubic>
+) {
+ var first = true
+ path.rewind()
+ for (i in 0 until cubics.size) {
+ val cubic = cubics[i]
+ if (first) {
+ path.moveTo(cubic.anchor0X, cubic.anchor0Y)
+ first = false
+ }
+ path.cubicTo(cubic.control0X, cubic.control0Y, cubic.control1X, cubic.control1Y,
+ cubic.anchor1X, cubic.anchor1Y)
+ }
+ path.close()
+}
+
+/**
* Transforms a [RoundedPolygon] with the given [Matrix]
*/
fun RoundedPolygon.transformed(matrix: Matrix): RoundedPolygon =
@@ -67,14 +118,99 @@
*/
fun RoundedPolygon.getBounds() = calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) }
-internal const val DEBUG = false
+/**
+ * Calculates and returns the bounds of this [Morph] as a [Rect]
+ */
+fun Morph.getBounds() = calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) }
-internal inline fun outputToLog(message: String) {
- println(message)
-}
-
-internal inline fun debugLog(message: String) {
- if (DEBUG) {
- println(message)
+/**
+ * This class can be used to create a [Shape] object from a [RoundedPolygon]
+ *
+ * @param polygon The [RoundedPolygon] to be used for this [Shape]
+ * @param matrix An optional transformation matrix. If none is supplied, or null is passed
+ * as the value, a transformation matrix will be calculated internally, based on the bounds of
+ * [polygon]. The result will be that [polygon] will be scaled and translated to fit within
+ * the size of the [Shape].
+ */
+class RoundedPolygonShape(
+ private val polygon: RoundedPolygon,
+ private var matrix: Matrix = Matrix()
+) : Shape {
+ private val path = Path()
+ override fun createOutline(
+ size: Size,
+ layoutDirection: LayoutDirection,
+ density: Density
+ ): Outline {
+ path.rewind()
+ polygon.toPath(path)
+ fitToViewport(path, polygon.getBounds(), size, matrix)
+ return Outline.Generic(path)
}
}
+
+/**
+ * This class can be used to create a [Shape] object from a [RoundedPolygon]
+ *
+ * @param morph The [Morph] to be used for this [Shape]
+ * @param progress a value from 0 to 1 that determines the morph's current
+ * shape, between the start and end shapes provided at construction time. A value of 0 results
+ * in the start shape, a value of 1 results in the end shape, and any value in between
+ * results in a shape which is a linear interpolation between those two shapes.
+ * The range is generally [0..1] and values outside could result in undefined shapes, but
+ * values close to (but outside) the range can be used to get an exaggerated effect
+ * (e.g., for a bounce or overshoot animation).
+ * @param matrix An optional transformation matrix. If none is supplied, or null is passed
+ * as the value, a transformation matrix will be calculated internally, based on the bounds of
+ * [morph]. The result will be that [morph] will be scaled and translated to fit within
+ * the size of the [Shape].
+ */
+class MorphShape(
+ private val morph: Morph,
+ private val progress: Float,
+ private var matrix: Matrix = Matrix()
+) : Shape {
+ private val path = Path()
+ override fun createOutline(
+ size: Size,
+ layoutDirection: LayoutDirection,
+ density: Density
+ ): Outline {
+ path.rewind()
+ morph.toPath(progress, path)
+ fitToViewport(path, morph.getBounds(), size, matrix)
+ return Outline.Generic(path)
+ }
+}
+
+/**
+ * Scales and translates the given [path] to fit within the given [viewport], using the
+ * max dimension of [bounds] and min dimension of [viewport] to ensure that the path
+ * fits completely within the viewport.
+ *
+ * @param path the path to be transformed
+ * @param bounds the bounds of the shape represented by [path]
+ * @param viewport the area within which [path] will be transformed to fit
+ * @param matrix optional [Matrix] item which can be supplied to avoid creating a
+ * new matrix every time the function is called.
+ */
+fun fitToViewport(path: Path, bounds: Rect, viewport: Size, matrix: Matrix = Matrix()) {
+ matrix.reset()
+ val maxDimension = max(bounds.width, bounds.height)
+ if (maxDimension > 0f) {
+ val viewportMin = min(viewport.width, viewport.height)
+ val scaleFactor = viewportMin / maxDimension
+ val pathCenterX = bounds.left + bounds.width / 2
+ val pathCenterY = bounds.top + bounds.height / 2
+ matrix.translate(viewportMin / 2, viewportMin / 2)
+ matrix.scale(scaleFactor, scaleFactor)
+ matrix.translate(-pathCenterX, -pathCenterY)
+ path.transform(matrix)
+ }
+}
+
+fun radialToCartesian(
+ radius: Float,
+ angleRadians: Float,
+ center: Offset = Offset.Zero
+) = directionVector(angleRadians) * radius + center
diff --git a/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/MainActivity.kt b/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/MainActivity.kt
index 2eea780..20e817d 100644
--- a/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/MainActivity.kt
+++ b/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/MainActivity.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE")
+
package androidx.graphics.shapes.testcompose
import android.content.Intent
-import android.graphics.Matrix
-import android.graphics.RectF
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.animation.core.Animatable
@@ -56,18 +56,15 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.asComposePath
+import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.unit.dp
-import androidx.core.graphics.scaleMatrix
import androidx.fragment.app.FragmentActivity
import androidx.graphics.shapes.Cubic
import androidx.graphics.shapes.Morph
import androidx.graphics.shapes.RoundedPolygon
import androidx.graphics.shapes.TransformResult
-import androidx.graphics.shapes.toPath
-import kotlin.math.max
import kotlin.math.min
import kotlinx.coroutines.launch
@@ -76,8 +73,7 @@
polygon: RoundedPolygon,
modifier: Modifier = Modifier,
stroked: Boolean = false
-) =
- PolygonComposableImpl(polygon, modifier, stroked = stroked)
+) = PolygonComposableImpl(polygon, modifier, stroked = stroked)
@Composable
private fun MorphComposable(
@@ -101,38 +97,22 @@
.fillMaxSize()
.drawWithContent {
drawContent()
- val scale = min(size.width, size.height)
- val composePath = setupPath(morph, progress, scale)
+ val path = morph.toPath(progress)
+ fitToViewport(path, morph.getBounds(), size)
if (isDebug) {
- drawPath(composePath, Color.Green, style = Stroke(2f))
+ val scale = min(size.width, size.height)
+ drawPath(path, Color.Green, style = Stroke(2f))
morph.forEachCubic(progress) { cubic ->
cubic.transform { x, y -> TransformResult(x * scale, y * scale) }
debugDraw(cubic)
}
} else {
val style = if (stroked) Stroke(size.width / 10f) else Fill
- drawPath(composePath, Color.White, style = style)
+ drawPath(path, Color.White, style = style)
}
})
}
-private fun setupPath(morph: Morph, progress: Float, viewportSize: Float):
- androidx.compose.ui.graphics.Path {
- val path = morph.toPath(progress)
- val pathBounds = RectF()
- path.computeBounds(pathBounds, false)
- val pathSize = max(pathBounds.width(), pathBounds.height())
- val scaleFactor = viewportSize / pathSize
- val pathCenterX = pathBounds.left + pathBounds.width() / 2
- val pathCenterY = pathBounds.top + pathBounds.height() / 2
- val matrix = Matrix()
- matrix.setScale(scaleFactor, scaleFactor)
- matrix.preTranslate(-pathCenterX, -pathCenterY)
- matrix.postTranslate(viewportSize / 2f, viewportSize / 2f)
- path.transform(matrix)
- return path.asComposePath()
-}
-
@Composable
internal fun PolygonComposableImpl(
polygon: RoundedPolygon,
@@ -192,9 +172,11 @@
shape.forEach { cubic -> debugDraw(cubic) }
} else {
val scaledPath = polygon.toPath()
- scaledPath.transform(scaleMatrix(scale, scale))
+ val matrix = Matrix()
+ matrix.scale(scale, scale)
+ scaledPath.transform(matrix)
val style = if (stroked) Stroke(size.width / 10f) else Fill
- drawPath(scaledPath.asComposePath(), Color.White, style = style)
+ drawPath(scaledPath, Color.White, style = style)
}
})
}
@@ -437,6 +419,14 @@
progress.animateTo(1f, animationSpec = spring(0.6f, 50f))
}
+internal const val DEBUG = false
+
+internal inline fun debugLog(message: String) {
+ if (DEBUG) {
+ println(message)
+ }
+}
+
class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/ProgressActivity.kt b/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/ProgressActivity.kt
new file mode 100644
index 0000000..7fe01ff
--- /dev/null
+++ b/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/ProgressActivity.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics.shapes.testcompose
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.PathMeasure
+import androidx.compose.ui.graphics.asComposePath
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.fragment.app.FragmentActivity
+import androidx.graphics.shapes.CornerRounding
+import androidx.graphics.shapes.RoundedPolygon
+import androidx.graphics.shapes.pillStar
+import androidx.graphics.shapes.toPath
+import kotlin.math.min
+import kotlinx.coroutines.launch
+
+/**
+ * This is a simple app showing how to use the startLocation parameter to change where a
+ * pillStar shape starts from, then animating a stroked path from that starting point.
+ */
+class ProgressActivity : FragmentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent(parent = null) {
+ MaterialTheme {
+ ProgressHolder()
+ }
+ }
+ }
+}
+
+@Composable
+fun ProgressHolder() {
+ val progress = remember { Animatable(1f) }
+ val startLocation = remember { mutableFloatStateOf(0f) }
+ val pillStar = RoundedPolygon.pillStar(
+ numVerticesPerRadius = 16,
+ width = 1.8f, height = .4f, rounding = CornerRounding(1f),
+ startLocation = startLocation.floatValue
+ )
+ val scope = rememberCoroutineScope()
+ val pathMeasure = PathMeasure()
+ val pathSegment = Path()
+
+ Column {
+ Slider(value = startLocation.floatValue.coerceIn(0f, 1f), onValueChange = {
+ startLocation.floatValue = it
+ })
+ Box(
+ Modifier
+ .clickable {
+ scope.launch {
+ doAnimation(progress)
+ }
+ }
+ .fillMaxSize()
+ .drawWithContent {
+ drawContent()
+ val scale = min(size.width, size.height) / 2
+ val m = Matrix()
+ m.translate(size.width / 2, size.height / 2, 0f)
+ m.scale(scale, scale)
+
+ val pillStarPath = pillStar
+ .toPath()
+ .asComposePath()
+ pillStarPath.transform(m)
+ pathMeasure.setPath(pillStarPath, false)
+ val pathLength = pathMeasure.length
+ pathSegment.rewind()
+ pathMeasure.getSegment(0f, progress.value * pathLength, pathSegment, true)
+ drawPath(path = pathSegment, style = Stroke(20f), color = Color.Blue)
+ }
+ )
+ }
+}
+
+private suspend fun doAnimation(progress: Animatable<Float, AnimationVector1D>) {
+ progress.snapTo(0f)
+ progress.animateTo(1f, infiniteRepeatable(
+ tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse))
+}
diff --git a/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/SimpleMorph.kt b/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/SimpleMorph.kt
new file mode 100644
index 0000000..b37c3a8
--- /dev/null
+++ b/graphics/integration-tests/testapp-compose/src/main/java/androidx/graphics/shapes/testcompose/SimpleMorph.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics.shapes.testcompose
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.spring
+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.aspectRatio
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.unit.dp
+import androidx.fragment.app.FragmentActivity
+import androidx.graphics.shapes.CornerRounding
+import androidx.graphics.shapes.Morph
+import androidx.graphics.shapes.RoundedPolygon
+import androidx.graphics.shapes.pillStar
+import androidx.graphics.shapes.star
+import kotlin.math.min
+import kotlinx.coroutines.launch
+
+val shapeColor1 = Color.Blue
+val shapeColor2 = Color.Red
+
+class SimpleMorph : FragmentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent(parent = null) {
+ MaterialTheme {
+ ShapeViewer()
+ }
+ }
+ }
+}
+
+@Composable
+fun ShapeViewer() {
+ val shape1 = RoundedPolygon.star(5)
+ val shape2 = RoundedPolygon.pillStar(
+ numVerticesPerRadius = 8, width = 2f, height = .4f,
+ rounding = CornerRounding(.5f)
+ )
+ val morph = Morph(shape1, shape2)
+ val progress = remember { Animatable(0f) }
+ val scope = rememberCoroutineScope()
+
+ Column {
+ Row {
+ ShapeView(
+ shape1, modifier =
+ Modifier
+ .weight(1f)
+ .aspectRatio(1f)
+ .padding(horizontal = 5.dp)
+ .clickable {
+ scope.launch { doAnimation(progress, reverse = true) }
+ },
+ shapeColor1
+ )
+ ShapeView(
+ shape2, modifier =
+ Modifier
+ .weight(1f)
+ .aspectRatio(1f)
+ .padding(horizontal = 5.dp)
+ .clickable {
+ scope.launch { doAnimation(progress) }
+ },
+ shapeColor2
+ )
+ }
+ Row {
+ MorphView(
+ modifier =
+ Modifier
+ .weight(1f)
+ .aspectRatio(1f)
+ .padding(horizontal = 5.dp),
+ morph = morph, progress = progress.value
+ )
+ }
+ Row {
+ Box {
+ val hexagon = remember {
+ RoundedPolygon.star(
+ numVerticesPerRadius = 6,
+ rounding = CornerRounding(0.2f)
+ )
+ }
+ val clip = remember(hexagon) {
+ RoundedPolygonShape(polygon = hexagon)
+ }
+ Box(
+ modifier = Modifier
+ .clip(clip)
+ .background(MaterialTheme.colorScheme.secondary)
+ .size(200.dp)
+ ) {
+ Text(
+ "Hello Compose",
+ color = MaterialTheme.colorScheme.onSecondary,
+ modifier = Modifier.align(Alignment.Center)
+ )
+ }
+ }
+ Box {
+ val clip = MorphShape(morph = morph, progress = progress.value)
+ Box(
+ modifier = Modifier
+ .clip(clip)
+ .background(MaterialTheme.colorScheme.secondary)
+ .size(200.dp)
+ ) {
+ Text(
+ "Hello Compose",
+ color = MaterialTheme.colorScheme.onSecondary,
+ modifier = Modifier.align(Alignment.Center)
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun ShapeView(shape: RoundedPolygon, modifier: Modifier, color: Color) {
+ Box(modifier.drawWithContent {
+ drawContent()
+ drawRoundedPolygon(shape, color)
+ })
+}
+
+fun DrawScope.drawMorph(morph: Morph, progress: Float) {
+ val path = morph.toPath(progress)
+ val scale = min(size.width, size.height) / 2
+ val translator = Matrix()
+ val matrix = Matrix()
+ translator.translate(1f * scale, 1f * scale)
+ matrix.scale(scale, scale)
+ matrix *= translator
+ path.transform(matrix)
+ drawPath(path, SolidColor(lerp(shapeColor1, shapeColor2, progress)))
+}
+
+fun DrawScope.drawRoundedPolygon(shape: RoundedPolygon, color: Color) {
+ val path = shape.toPath()
+ val scale = min(size.width, size.height) / 2
+ val translator = Matrix()
+ val matrix = Matrix()
+ translator.translate(1f * scale, 1f * scale)
+ matrix.scale(scale, scale)
+ matrix *= translator
+ path.transform(matrix)
+ drawPath(path, color)
+}
+
+@Composable
+fun MorphView(
+ morph: Morph,
+ modifier: Modifier = Modifier,
+ progress: Float
+) {
+ Box(modifier.drawWithContent {
+ drawContent()
+ drawMorph(morph, progress)
+ })
+}
+
+private suspend fun doAnimation(
+ progress: Animatable<Float, AnimationVector1D>,
+ reverse: Boolean = false
+) {
+ val startValue = if (reverse) 1f else 0f
+ val endValue = if (reverse) 0f else 1f
+ progress.snapTo(startValue)
+ progress.animateTo(endValue, animationSpec = spring(0.6f, 50f))
+}
diff --git a/graphics/integration-tests/testapp/src/main/AndroidManifest.xml b/graphics/integration-tests/testapp/src/main/AndroidManifest.xml
index 12bb57d..ef535b8 100644
--- a/graphics/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/graphics/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -28,6 +28,10 @@
<activity android:name=".CustomShapeActivity"
android:label="Custom Shapes Test - Views"
android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
</activity>
</application>
diff --git a/graphics/integration-tests/testapp/src/main/java/androidx/graphics/shapes/test/MorphView.kt b/graphics/integration-tests/testapp/src/main/java/androidx/graphics/shapes/test/MorphView.kt
index b207a6c..08d58d8 100644
--- a/graphics/integration-tests/testapp/src/main/java/androidx/graphics/shapes/test/MorphView.kt
+++ b/graphics/integration-tests/testapp/src/main/java/androidx/graphics/shapes/test/MorphView.kt
@@ -25,7 +25,7 @@
import android.graphics.Path
import android.graphics.RectF
import android.view.View
-import android.view.animation.OvershootInterpolator
+import android.view.animation.AccelerateInterpolator
import androidx.graphics.shapes.Morph
import androidx.graphics.shapes.toPath
import kotlin.math.max
@@ -35,14 +35,14 @@
val paint = Paint()
val path = Path()
private var pathBounds = RectF()
- val overshooter = OvershootInterpolator()
+ val accelerateInterpolator = AccelerateInterpolator(3f)
var morph = morph
set(value) {
field = value
val animator = ObjectAnimator.ofFloat(this, "progress", 0f, 1f)
animator.duration = 500
- animator.interpolator = overshooter
+ animator.interpolator = accelerateInterpolator
animator.start()
}
diff --git a/hilt/hilt-navigation-compose/api/current.ignore b/hilt/hilt-navigation-compose/api/current.ignore
deleted file mode 100644
index 03563c5..0000000
--- a/hilt/hilt-navigation-compose/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedMethod: androidx.hilt.navigation.compose.HiltViewModelKt#hiltViewModel(androidx.lifecycle.ViewModelStoreOwner):
- Removed method androidx.hilt.navigation.compose.HiltViewModelKt.hiltViewModel(androidx.lifecycle.ViewModelStoreOwner)
diff --git a/hilt/hilt-navigation-compose/api/restricted_current.ignore b/hilt/hilt-navigation-compose/api/restricted_current.ignore
deleted file mode 100644
index 03563c5..0000000
--- a/hilt/hilt-navigation-compose/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedMethod: androidx.hilt.navigation.compose.HiltViewModelKt#hiltViewModel(androidx.lifecycle.ViewModelStoreOwner):
- Removed method androidx.hilt.navigation.compose.HiltViewModelKt.hiltViewModel(androidx.lifecycle.ViewModelStoreOwner)
diff --git a/hilt/hilt-navigation-fragment/api/current.ignore b/hilt/hilt-navigation-fragment/api/current.ignore
deleted file mode 100644
index 1d9ae1f..0000000
--- a/hilt/hilt-navigation-fragment/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.hilt.navigation.fragment.HiltNavGraphViewModelLazyKt#hiltNavGraphViewModels(androidx.fragment.app.Fragment, int):
- Method androidx.hilt.navigation.fragment.HiltNavGraphViewModelLazyKt.hiltNavGraphViewModels has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
diff --git a/hilt/hilt-navigation-fragment/api/restricted_current.ignore b/hilt/hilt-navigation-fragment/api/restricted_current.ignore
deleted file mode 100644
index 1d9ae1f..0000000
--- a/hilt/hilt-navigation-fragment/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.hilt.navigation.fragment.HiltNavGraphViewModelLazyKt#hiltNavGraphViewModels(androidx.fragment.app.Fragment, int):
- Method androidx.hilt.navigation.fragment.HiltNavGraphViewModelLazyKt.hiltNavGraphViewModels has changed return type from kotlin.Lazy<? extends VM> to kotlin.Lazy<VM>
diff --git a/inspection/inspection-gradle-plugin/lint-baseline.xml b/inspection/inspection-gradle-plugin/lint-baseline.xml
index 0be37bb..b356138 100644
--- a/inspection/inspection-gradle-plugin/lint-baseline.xml
+++ b/inspection/inspection-gradle-plugin/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha07)" variant="all" version="8.4.0-alpha07">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
<issue
id="EagerGradleConfiguration"
@@ -37,4 +37,13 @@
file="src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt"/>
</issue>
+ <issue
+ id="WithPluginClasspathUsage"
+ message="Avoid usage of GradleRunner#withPluginClasspath, which is broken. Instead use something like https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/testkit#gradle-testkit-support-plugin"
+ errorLine1=" .withPluginClasspath()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/test/kotlin/androidx/inspection/gradle/InspectionPluginTest.kt"/>
+ </issue>
+
</issues>
diff --git a/kruth/kruth/api/api_lint.ignore b/kruth/kruth/api/api_lint.ignore
index a92425e..fd66fa7 100644
--- a/kruth/kruth/api/api_lint.ignore
+++ b/kruth/kruth/api/api_lint.ignore
@@ -8,9 +8,9 @@
ArrayReturn: androidx.kruth.IterableSubject#containsNoneIn(Object[]) parameter #0:
Method parameter should be Collection<Object> (or subclass) instead of raw array; was `java.lang.Object[]`
ArrayReturn: androidx.kruth.Kruth#assertThat(T[]) parameter #0:
- Method parameter should be Collection<T> (or subclass) instead of raw array; was `T[]`
+ Method parameter should be Collection<Object> (or subclass) instead of raw array; was `T[]`
ArrayReturn: androidx.kruth.StandardSubjectBuilder#that(T[]) parameter #0:
- Method parameter should be Collection<T> (or subclass) instead of raw array; was `T[]`
+ Method parameter should be Collection<Object> (or subclass) instead of raw array; was `T[]`
AutoBoxing: androidx.kruth.IntegerSubject#IntegerSubject(androidx.kruth.FailureMetadata, Integer) parameter #1:
@@ -19,14 +19,10 @@
Must avoid boxed primitives (`java.lang.Boolean`)
AutoBoxing: androidx.kruth.Kruth#assertThat(Double) parameter #0:
Must avoid boxed primitives (`java.lang.Double`)
-AutoBoxing: androidx.kruth.Kruth#assertThat(Integer) parameter #0:
- Must avoid boxed primitives (`java.lang.Integer`)
AutoBoxing: androidx.kruth.StandardSubjectBuilder#that(Boolean) parameter #0:
Must avoid boxed primitives (`java.lang.Boolean`)
AutoBoxing: androidx.kruth.StandardSubjectBuilder#that(Double) parameter #0:
Must avoid boxed primitives (`java.lang.Double`)
-AutoBoxing: androidx.kruth.StandardSubjectBuilder#that(Integer) parameter #0:
- Must avoid boxed primitives (`java.lang.Integer`)
BuilderSetStyle: androidx.kruth.SimpleSubjectBuilder#that(T):
@@ -41,8 +37,6 @@
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(Boolean)
BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(Double):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(Double)
-BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(Integer):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(Integer)
BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(Iterable<? extends T>):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(Iterable<? extends T>)
BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(String):
@@ -61,10 +55,14 @@
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(double[])
BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(float[]):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(float[])
+BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(int):
+ Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(int)
BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(int[]):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(int[])
BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(java.util.Map<K,? extends V>):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(java.util.Map<K,? extends V>)
+BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(long):
+ Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(long)
BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(long[]):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.kruth.StandardSubjectBuilder.that(long[])
BuilderSetStyle: androidx.kruth.StandardSubjectBuilder#that(short[]):
diff --git a/kruth/kruth/api/current.ignore b/kruth/kruth/api/current.ignore
index 96f1a33..76f05fc 100644
--- a/kruth/kruth/api/current.ignore
+++ b/kruth/kruth/api/current.ignore
@@ -1,4 +1,6 @@
// Baseline format: 1.0
+ChangedType: androidx.kruth.MultimapSubject#valuesForKey(K):
+ Method androidx.kruth.MultimapSubject.valuesForKey has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<V>
ChangedType: androidx.kruth.ObjectArraySubject#asList():
Method androidx.kruth.ObjectArraySubject.asList has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<?>
ChangedType: androidx.kruth.PrimitiveBooleanArraySubject#asList():
@@ -63,14 +65,10 @@
Removed class androidx.kruth.FloatSubject
RemovedClass: androidx.kruth.IterableSubject.UsingCorrespondence:
Removed class androidx.kruth.IterableSubject.UsingCorrespondence
-RemovedClass: androidx.kruth.LongSubject:
- Removed class androidx.kruth.LongSubject
RemovedClass: androidx.kruth.MapSubject.UsingCorrespondence:
Removed class androidx.kruth.MapSubject.UsingCorrespondence
-RemovedClass: androidx.kruth.MultimapSubject:
- Removed class androidx.kruth.MultimapSubject
-RemovedClass: androidx.kruth.MultisetSubject:
- Removed class androidx.kruth.MultisetSubject
+RemovedClass: androidx.kruth.MultimapSubject.UsingCorrespondence:
+ Removed class androidx.kruth.MultimapSubject.UsingCorrespondence
RemovedClass: androidx.kruth.PrimitiveDoubleArraySubject.DoubleArrayAsIterable:
Removed class androidx.kruth.PrimitiveDoubleArraySubject.DoubleArrayAsIterable
RemovedClass: androidx.kruth.PrimitiveFloatArraySubject.FloatArrayAsIterable:
@@ -99,6 +97,16 @@
Removed method androidx.kruth.IterableSubject.comparingElementsUsing(androidx.kruth.Correspondence<? super A,? super E>)
RemovedMethod: androidx.kruth.IterableSubject#formattingDiffsUsing(androidx.kruth.Correspondence.DiffFormatter<? super T,? super T>):
Removed method androidx.kruth.IterableSubject.formattingDiffsUsing(androidx.kruth.Correspondence.DiffFormatter<? super T,? super T>)
+RemovedMethod: androidx.kruth.LongSubject#LongSubject(androidx.kruth.FailureMetadata, Long):
+ Removed constructor androidx.kruth.LongSubject(androidx.kruth.FailureMetadata,Long)
+RemovedMethod: androidx.kruth.LongSubject#isAtLeast(int):
+ Removed method androidx.kruth.LongSubject.isAtLeast(int)
+RemovedMethod: androidx.kruth.LongSubject#isAtMost(int):
+ Removed method androidx.kruth.LongSubject.isAtMost(int)
+RemovedMethod: androidx.kruth.LongSubject#isGreaterThan(int):
+ Removed method androidx.kruth.LongSubject.isGreaterThan(int)
+RemovedMethod: androidx.kruth.LongSubject#isLessThan(int):
+ Removed method androidx.kruth.LongSubject.isLessThan(int)
RemovedMethod: androidx.kruth.MapSubject#MapSubject(androidx.kruth.FailureMetadata, java.util.Map<?,?>):
Removed constructor androidx.kruth.MapSubject(androidx.kruth.FailureMetadata,java.util.Map<?,?>)
RemovedMethod: androidx.kruth.MapSubject#comparingValuesUsing(androidx.kruth.Correspondence<? super A,? super E>):
@@ -111,6 +119,14 @@
Removed method androidx.kruth.MapSubject.containsExactly(Object,Object,java.lang.Object...)
RemovedMethod: androidx.kruth.MapSubject#formattingDiffsUsing(androidx.kruth.Correspondence.DiffFormatter<? super V,? super V>):
Removed method androidx.kruth.MapSubject.formattingDiffsUsing(androidx.kruth.Correspondence.DiffFormatter<? super V,? super V>)
+RemovedMethod: androidx.kruth.MultimapSubject#MultimapSubject(androidx.kruth.FailureMetadata, com.google.common.collect.Multimap<?,?>):
+ Removed constructor androidx.kruth.MultimapSubject(androidx.kruth.FailureMetadata,com.google.common.collect.Multimap<?,?>)
+RemovedMethod: androidx.kruth.MultimapSubject#comparingValuesUsing(androidx.kruth.Correspondence<? super A,? super E>):
+ Removed method androidx.kruth.MultimapSubject.comparingValuesUsing(androidx.kruth.Correspondence<? super A,? super E>)
+RemovedMethod: androidx.kruth.MultimapSubject#containsAtLeast(Object, Object, java.lang.Object...):
+ Removed method androidx.kruth.MultimapSubject.containsAtLeast(Object,Object,java.lang.Object...)
+RemovedMethod: androidx.kruth.MultimapSubject#containsExactly(Object, Object, java.lang.Object...):
+ Removed method androidx.kruth.MultimapSubject.containsExactly(Object,Object,java.lang.Object...)
RemovedMethod: androidx.kruth.PrimitiveDoubleArraySubject#usingExactEquality():
Removed method androidx.kruth.PrimitiveDoubleArraySubject.usingExactEquality()
RemovedMethod: androidx.kruth.PrimitiveDoubleArraySubject#usingTolerance(double):
@@ -125,8 +141,6 @@
Removed method androidx.kruth.StandardSubjectBuilder.that(Class<?>)
RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(Float):
Removed method androidx.kruth.StandardSubjectBuilder.that(Float)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(Long):
- Removed method androidx.kruth.StandardSubjectBuilder.that(Long)
RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.base.Optional<?>):
Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.base.Optional<?>)
RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multimap<?,?>):
diff --git a/kruth/kruth/api/current.txt b/kruth/kruth/api/current.txt
index f559426..9f3ba2e 100644
--- a/kruth/kruth/api/current.txt
+++ b/kruth/kruth/api/current.txt
@@ -123,18 +123,21 @@
method public static androidx.kruth.PrimitiveCharArraySubject assertThat(char[]? actual);
method public static androidx.kruth.PrimitiveDoubleArraySubject assertThat(double[]? actual);
method public static androidx.kruth.PrimitiveFloatArraySubject assertThat(float[]? actual);
+ method public static androidx.kruth.IntegerSubject assertThat(int actual);
method public static androidx.kruth.PrimitiveIntArraySubject assertThat(int[]? actual);
method public static androidx.kruth.BooleanSubject assertThat(Boolean? actual);
method public static androidx.kruth.DoubleSubject assertThat(Double? actual);
- method public static androidx.kruth.IntegerSubject assertThat(Integer? actual);
method public static <T> androidx.kruth.IterableSubject<T> assertThat(Iterable<? extends T>? actual);
method public static androidx.kruth.StringSubject assertThat(String? actual);
method public static <K, V> androidx.kruth.MapSubject<K,V> assertThat(java.util.Map<K,? extends V>? actual);
+ method public static androidx.kruth.LongSubject assertThat(long actual);
method public static androidx.kruth.PrimitiveLongArraySubject assertThat(long[]? actual);
method public static androidx.kruth.PrimitiveShortArraySubject assertThat(short[]? actual);
method public static <T extends java.lang.Comparable<? super T>> androidx.kruth.ComparableSubject<T> assertThat(T? actual);
method public static <T> androidx.kruth.Subject<T> assertThat(T? actual);
method public static <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThat(T? actual);
+ method public static <T extends java.lang.Long> androidx.kruth.LongSubject assertThat(T actual);
+ method public static <T extends java.lang.Integer> androidx.kruth.IntegerSubject assertThat(T actual);
method public static <T> androidx.kruth.ObjectArraySubject<T> assertThat(T[]? actual);
method public static androidx.kruth.StandardSubjectBuilder assertWithMessage(String messageToPrepend);
method public static androidx.kruth.StandardSubjectBuilder assert_();
@@ -147,10 +150,24 @@
public final class Kruth_jvmKt {
method public static <T> androidx.kruth.GuavaOptionalSubject<T> assertThat(com.google.common.base.Optional<T>? actual);
+ method public static <K, V> androidx.kruth.MultimapSubject<K,V> assertThat(com.google.common.collect.Multimap<K,V> actual);
+ method public static <T> androidx.kruth.MultisetSubject<T> assertThat(com.google.common.collect.Multiset<T> actual);
method public static androidx.kruth.ClassSubject assertThat(Class<?> actual);
method public static androidx.kruth.BigDecimalSubject assertThat(java.math.BigDecimal actual);
}
+ public class LongSubject extends androidx.kruth.ComparableSubject<java.lang.Long> {
+ method @Deprecated public void isEquivalentAccordingToCompareTo(Long? other);
+ method public final androidx.kruth.LongSubject.TolerantLongComparison isNotWithin(long tolerance);
+ method public final androidx.kruth.LongSubject.TolerantLongComparison isWithin(long tolerance);
+ }
+
+ public abstract static class LongSubject.TolerantLongComparison {
+ method @Deprecated public boolean equals(Object? other);
+ method @Deprecated public int hashCode();
+ method public abstract void of(long expected);
+ }
+
public class MapSubject<K, V> extends androidx.kruth.Subject<java.util.Map<K,? extends V>> {
ctor protected MapSubject(androidx.kruth.FailureMetadata metadata, java.util.Map<K,? extends V>? actual);
method public final androidx.kruth.Ordered containsAtLeast(kotlin.Pair<? extends K,? extends V>... entries);
@@ -168,6 +185,26 @@
method public final void isNotEmpty();
}
+ public class MultimapSubject<K, V> extends androidx.kruth.Subject<com.google.common.collect.Multimap<K,V>> {
+ method public final androidx.kruth.Ordered containsAtLeast(kotlin.Pair<? extends K,?>... entries);
+ method public final androidx.kruth.Ordered containsAtLeastEntriesIn(com.google.common.collect.Multimap<K,?>? expectedMultimap);
+ method public final void containsEntry(K key, Object? value);
+ method public final androidx.kruth.Ordered containsExactly();
+ method public final androidx.kruth.Ordered containsExactly(kotlin.Pair<? extends K,?>... entries);
+ method public final androidx.kruth.Ordered containsExactlyEntriesIn(com.google.common.collect.Multimap<K,?>? expectedMultimap);
+ method public final void containsKey(K key);
+ method public final void doesNotContainEntry(K key, Object? value);
+ method public final void doesNotContainKey(K key);
+ method public final void hasSize(int expectedSize);
+ method public final void isEmpty();
+ method public final void isNotEmpty();
+ method public androidx.kruth.IterableSubject<V> valuesForKey(K key);
+ }
+
+ public final class MultisetSubject<T> extends androidx.kruth.IterableSubject<T> {
+ method public void hasCount(Object? element, int expectedCount);
+ }
+
public final class ObjectArraySubject<T> extends androidx.kruth.Subject<T[]> {
method public androidx.kruth.IterableSubject<?> asList();
method public void hasLength(int length);
@@ -248,18 +285,21 @@
method public final androidx.kruth.PrimitiveCharArraySubject that(char[]? actual);
method public final androidx.kruth.PrimitiveDoubleArraySubject that(double[]? actual);
method public final androidx.kruth.PrimitiveFloatArraySubject that(float[]? actual);
+ method public final androidx.kruth.IntegerSubject that(int actual);
method public final androidx.kruth.PrimitiveIntArraySubject that(int[]? actual);
method public final androidx.kruth.BooleanSubject that(Boolean? actual);
method public final androidx.kruth.DoubleSubject that(Double? actual);
- method public final androidx.kruth.IntegerSubject that(Integer? actual);
method public final <T> androidx.kruth.IterableSubject<T> that(Iterable<? extends T>? actual);
method public final androidx.kruth.StringSubject that(String? actual);
method public final <K, V> androidx.kruth.MapSubject<K,V> that(java.util.Map<K,? extends V>? actual);
+ method public final androidx.kruth.LongSubject that(long actual);
method public final androidx.kruth.PrimitiveLongArraySubject that(long[]? actual);
method public final androidx.kruth.PrimitiveShortArraySubject that(short[]? actual);
method public final <T> androidx.kruth.Subject<T> that(T? actual);
method public final <T extends java.lang.Comparable<? super T>> androidx.kruth.ComparableSubject<T> that(T? actual);
method public final <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> that(T? actual);
+ method public final <T extends java.lang.Long> androidx.kruth.LongSubject that(T actual);
+ method public final <T extends java.lang.Integer> androidx.kruth.IntegerSubject that(T actual);
method public final <T> androidx.kruth.ObjectArraySubject<T> that(T[]? actual);
method public final androidx.kruth.StandardSubjectBuilder withMessage(String messageToPrepend);
field public static final androidx.kruth.StandardSubjectBuilder.Companion Companion;
diff --git a/kruth/kruth/api/restricted_current.ignore b/kruth/kruth/api/restricted_current.ignore
index 96f1a33..76f05fc 100644
--- a/kruth/kruth/api/restricted_current.ignore
+++ b/kruth/kruth/api/restricted_current.ignore
@@ -1,4 +1,6 @@
// Baseline format: 1.0
+ChangedType: androidx.kruth.MultimapSubject#valuesForKey(K):
+ Method androidx.kruth.MultimapSubject.valuesForKey has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<V>
ChangedType: androidx.kruth.ObjectArraySubject#asList():
Method androidx.kruth.ObjectArraySubject.asList has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<?>
ChangedType: androidx.kruth.PrimitiveBooleanArraySubject#asList():
@@ -63,14 +65,10 @@
Removed class androidx.kruth.FloatSubject
RemovedClass: androidx.kruth.IterableSubject.UsingCorrespondence:
Removed class androidx.kruth.IterableSubject.UsingCorrespondence
-RemovedClass: androidx.kruth.LongSubject:
- Removed class androidx.kruth.LongSubject
RemovedClass: androidx.kruth.MapSubject.UsingCorrespondence:
Removed class androidx.kruth.MapSubject.UsingCorrespondence
-RemovedClass: androidx.kruth.MultimapSubject:
- Removed class androidx.kruth.MultimapSubject
-RemovedClass: androidx.kruth.MultisetSubject:
- Removed class androidx.kruth.MultisetSubject
+RemovedClass: androidx.kruth.MultimapSubject.UsingCorrespondence:
+ Removed class androidx.kruth.MultimapSubject.UsingCorrespondence
RemovedClass: androidx.kruth.PrimitiveDoubleArraySubject.DoubleArrayAsIterable:
Removed class androidx.kruth.PrimitiveDoubleArraySubject.DoubleArrayAsIterable
RemovedClass: androidx.kruth.PrimitiveFloatArraySubject.FloatArrayAsIterable:
@@ -99,6 +97,16 @@
Removed method androidx.kruth.IterableSubject.comparingElementsUsing(androidx.kruth.Correspondence<? super A,? super E>)
RemovedMethod: androidx.kruth.IterableSubject#formattingDiffsUsing(androidx.kruth.Correspondence.DiffFormatter<? super T,? super T>):
Removed method androidx.kruth.IterableSubject.formattingDiffsUsing(androidx.kruth.Correspondence.DiffFormatter<? super T,? super T>)
+RemovedMethod: androidx.kruth.LongSubject#LongSubject(androidx.kruth.FailureMetadata, Long):
+ Removed constructor androidx.kruth.LongSubject(androidx.kruth.FailureMetadata,Long)
+RemovedMethod: androidx.kruth.LongSubject#isAtLeast(int):
+ Removed method androidx.kruth.LongSubject.isAtLeast(int)
+RemovedMethod: androidx.kruth.LongSubject#isAtMost(int):
+ Removed method androidx.kruth.LongSubject.isAtMost(int)
+RemovedMethod: androidx.kruth.LongSubject#isGreaterThan(int):
+ Removed method androidx.kruth.LongSubject.isGreaterThan(int)
+RemovedMethod: androidx.kruth.LongSubject#isLessThan(int):
+ Removed method androidx.kruth.LongSubject.isLessThan(int)
RemovedMethod: androidx.kruth.MapSubject#MapSubject(androidx.kruth.FailureMetadata, java.util.Map<?,?>):
Removed constructor androidx.kruth.MapSubject(androidx.kruth.FailureMetadata,java.util.Map<?,?>)
RemovedMethod: androidx.kruth.MapSubject#comparingValuesUsing(androidx.kruth.Correspondence<? super A,? super E>):
@@ -111,6 +119,14 @@
Removed method androidx.kruth.MapSubject.containsExactly(Object,Object,java.lang.Object...)
RemovedMethod: androidx.kruth.MapSubject#formattingDiffsUsing(androidx.kruth.Correspondence.DiffFormatter<? super V,? super V>):
Removed method androidx.kruth.MapSubject.formattingDiffsUsing(androidx.kruth.Correspondence.DiffFormatter<? super V,? super V>)
+RemovedMethod: androidx.kruth.MultimapSubject#MultimapSubject(androidx.kruth.FailureMetadata, com.google.common.collect.Multimap<?,?>):
+ Removed constructor androidx.kruth.MultimapSubject(androidx.kruth.FailureMetadata,com.google.common.collect.Multimap<?,?>)
+RemovedMethod: androidx.kruth.MultimapSubject#comparingValuesUsing(androidx.kruth.Correspondence<? super A,? super E>):
+ Removed method androidx.kruth.MultimapSubject.comparingValuesUsing(androidx.kruth.Correspondence<? super A,? super E>)
+RemovedMethod: androidx.kruth.MultimapSubject#containsAtLeast(Object, Object, java.lang.Object...):
+ Removed method androidx.kruth.MultimapSubject.containsAtLeast(Object,Object,java.lang.Object...)
+RemovedMethod: androidx.kruth.MultimapSubject#containsExactly(Object, Object, java.lang.Object...):
+ Removed method androidx.kruth.MultimapSubject.containsExactly(Object,Object,java.lang.Object...)
RemovedMethod: androidx.kruth.PrimitiveDoubleArraySubject#usingExactEquality():
Removed method androidx.kruth.PrimitiveDoubleArraySubject.usingExactEquality()
RemovedMethod: androidx.kruth.PrimitiveDoubleArraySubject#usingTolerance(double):
@@ -125,8 +141,6 @@
Removed method androidx.kruth.StandardSubjectBuilder.that(Class<?>)
RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(Float):
Removed method androidx.kruth.StandardSubjectBuilder.that(Float)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(Long):
- Removed method androidx.kruth.StandardSubjectBuilder.that(Long)
RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.base.Optional<?>):
Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.base.Optional<?>)
RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multimap<?,?>):
diff --git a/kruth/kruth/api/restricted_current.txt b/kruth/kruth/api/restricted_current.txt
index b61c180..91ef20d 100644
--- a/kruth/kruth/api/restricted_current.txt
+++ b/kruth/kruth/api/restricted_current.txt
@@ -123,18 +123,21 @@
method public static androidx.kruth.PrimitiveCharArraySubject assertThat(char[]? actual);
method public static androidx.kruth.PrimitiveDoubleArraySubject assertThat(double[]? actual);
method public static androidx.kruth.PrimitiveFloatArraySubject assertThat(float[]? actual);
+ method public static androidx.kruth.IntegerSubject assertThat(int actual);
method public static androidx.kruth.PrimitiveIntArraySubject assertThat(int[]? actual);
method public static androidx.kruth.BooleanSubject assertThat(Boolean? actual);
method public static androidx.kruth.DoubleSubject assertThat(Double? actual);
- method public static androidx.kruth.IntegerSubject assertThat(Integer? actual);
method public static <T> androidx.kruth.IterableSubject<T> assertThat(Iterable<? extends T>? actual);
method public static androidx.kruth.StringSubject assertThat(String? actual);
method public static <K, V> androidx.kruth.MapSubject<K,V> assertThat(java.util.Map<K,? extends V>? actual);
+ method public static androidx.kruth.LongSubject assertThat(long actual);
method public static androidx.kruth.PrimitiveLongArraySubject assertThat(long[]? actual);
method public static androidx.kruth.PrimitiveShortArraySubject assertThat(short[]? actual);
method public static <T extends java.lang.Comparable<? super T>> androidx.kruth.ComparableSubject<T> assertThat(T? actual);
method public static <T> androidx.kruth.Subject<T> assertThat(T? actual);
method public static <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> assertThat(T? actual);
+ method public static <T extends java.lang.Long> androidx.kruth.LongSubject assertThat(T actual);
+ method public static <T extends java.lang.Integer> androidx.kruth.IntegerSubject assertThat(T actual);
method public static <T> androidx.kruth.ObjectArraySubject<T> assertThat(T[]? actual);
method public static androidx.kruth.StandardSubjectBuilder assertWithMessage(String messageToPrepend);
method public static androidx.kruth.StandardSubjectBuilder assert_();
@@ -147,10 +150,24 @@
public final class Kruth_jvmKt {
method public static <T> androidx.kruth.GuavaOptionalSubject<T> assertThat(com.google.common.base.Optional<T>? actual);
+ method public static <K, V> androidx.kruth.MultimapSubject<K,V> assertThat(com.google.common.collect.Multimap<K,V> actual);
+ method public static <T> androidx.kruth.MultisetSubject<T> assertThat(com.google.common.collect.Multiset<T> actual);
method public static androidx.kruth.ClassSubject assertThat(Class<?> actual);
method public static androidx.kruth.BigDecimalSubject assertThat(java.math.BigDecimal actual);
}
+ public class LongSubject extends androidx.kruth.ComparableSubject<java.lang.Long> {
+ method @Deprecated public void isEquivalentAccordingToCompareTo(Long? other);
+ method public final androidx.kruth.LongSubject.TolerantLongComparison isNotWithin(long tolerance);
+ method public final androidx.kruth.LongSubject.TolerantLongComparison isWithin(long tolerance);
+ }
+
+ public abstract static class LongSubject.TolerantLongComparison {
+ method @Deprecated public boolean equals(Object? other);
+ method @Deprecated public int hashCode();
+ method public abstract void of(long expected);
+ }
+
public class MapSubject<K, V> extends androidx.kruth.Subject<java.util.Map<K,? extends V>> {
ctor protected MapSubject(androidx.kruth.FailureMetadata metadata, java.util.Map<K,? extends V>? actual);
method public final androidx.kruth.Ordered containsAtLeast(kotlin.Pair<? extends K,? extends V>... entries);
@@ -168,6 +185,26 @@
method public final void isNotEmpty();
}
+ public class MultimapSubject<K, V> extends androidx.kruth.Subject<com.google.common.collect.Multimap<K,V>> {
+ method public final androidx.kruth.Ordered containsAtLeast(kotlin.Pair<? extends K,?>... entries);
+ method public final androidx.kruth.Ordered containsAtLeastEntriesIn(com.google.common.collect.Multimap<K,?>? expectedMultimap);
+ method public final void containsEntry(K key, Object? value);
+ method public final androidx.kruth.Ordered containsExactly();
+ method public final androidx.kruth.Ordered containsExactly(kotlin.Pair<? extends K,?>... entries);
+ method public final androidx.kruth.Ordered containsExactlyEntriesIn(com.google.common.collect.Multimap<K,?>? expectedMultimap);
+ method public final void containsKey(K key);
+ method public final void doesNotContainEntry(K key, Object? value);
+ method public final void doesNotContainKey(K key);
+ method public final void hasSize(int expectedSize);
+ method public final void isEmpty();
+ method public final void isNotEmpty();
+ method public androidx.kruth.IterableSubject<V> valuesForKey(K key);
+ }
+
+ public final class MultisetSubject<T> extends androidx.kruth.IterableSubject<T> {
+ method public void hasCount(Object? element, int expectedCount);
+ }
+
public final class ObjectArraySubject<T> extends androidx.kruth.Subject<T[]> {
method public androidx.kruth.IterableSubject<?> asList();
method public void hasLength(int length);
@@ -248,18 +285,21 @@
method public final androidx.kruth.PrimitiveCharArraySubject that(char[]? actual);
method public final androidx.kruth.PrimitiveDoubleArraySubject that(double[]? actual);
method public final androidx.kruth.PrimitiveFloatArraySubject that(float[]? actual);
+ method public final androidx.kruth.IntegerSubject that(int actual);
method public final androidx.kruth.PrimitiveIntArraySubject that(int[]? actual);
method public final androidx.kruth.BooleanSubject that(Boolean? actual);
method public final androidx.kruth.DoubleSubject that(Double? actual);
- method public final androidx.kruth.IntegerSubject that(Integer? actual);
method public final <T> androidx.kruth.IterableSubject<T> that(Iterable<? extends T>? actual);
method public final androidx.kruth.StringSubject that(String? actual);
method public final <K, V> androidx.kruth.MapSubject<K,V> that(java.util.Map<K,? extends V>? actual);
+ method public final androidx.kruth.LongSubject that(long actual);
method public final androidx.kruth.PrimitiveLongArraySubject that(long[]? actual);
method public final androidx.kruth.PrimitiveShortArraySubject that(short[]? actual);
method public final <T> androidx.kruth.Subject<T> that(T? actual);
method public final <T extends java.lang.Comparable<? super T>> androidx.kruth.ComparableSubject<T> that(T? actual);
method public final <T extends java.lang.Throwable> androidx.kruth.ThrowableSubject<T> that(T? actual);
+ method public final <T extends java.lang.Long> androidx.kruth.LongSubject that(T actual);
+ method public final <T extends java.lang.Integer> androidx.kruth.IntegerSubject that(T actual);
method public final <T> androidx.kruth.ObjectArraySubject<T> that(T[]? actual);
method public final androidx.kruth.StandardSubjectBuilder withMessage(String messageToPrepend);
field public static final androidx.kruth.StandardSubjectBuilder.Companion Companion;
diff --git a/kruth/kruth/build.gradle b/kruth/kruth/build.gradle
index b6cafba..1f1441d 100644
--- a/kruth/kruth/build.gradle
+++ b/kruth/kruth/build.gradle
@@ -22,7 +22,6 @@
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
import androidx.build.Publish
@@ -32,10 +31,6 @@
id("AndroidXPlugin")
}
-def macEnabled = KmpPlatformsKt.enableMac(project)
-def linuxEnabled = KmpPlatformsKt.enableLinux(project)
-def nativeEnabled = KmpPlatformsKt.enableNative(project)
-
androidXMultiplatform {
jvm {}
mac()
@@ -75,14 +70,12 @@
}
}
- if (macEnabled || linuxEnabled || nativeEnabled) {
- nativeMain {
- dependsOn(commonMain)
- }
+ nativeMain {
+ dependsOn(commonMain)
+ }
- nativeTest {
- dependsOn(commonTest)
- }
+ nativeTest {
+ dependsOn(commonTest)
}
targets.all { target ->
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Kruth.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Kruth.kt
index ba73410..dffa03e 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Kruth.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Kruth.kt
@@ -41,9 +41,17 @@
fun assertThat(actual: Boolean?): BooleanSubject = assert_().that(actual)
+fun assertThat(actual: Long): LongSubject = assert_().that(actual)
+
+// Workaround for https://youtrack.jetbrains.com/issue/KT-645
+fun <T : Long?> assertThat(actual: T): LongSubject = assert_().that(actual)
+
fun assertThat(actual: Double?): DoubleSubject = assert_().that(actual)
-fun assertThat(actual: Int?): IntegerSubject = assert_().that(actual)
+fun assertThat(actual: Int): IntegerSubject = assert_().that(actual)
+
+// Workaround for https://youtrack.jetbrains.com/issue/KT-645
+fun <T : Int?> assertThat(actual: T): IntegerSubject = assert_().that(actual)
fun assertThat(actual: String?): StringSubject = assert_().that(actual)
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/LongSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/LongSubject.kt
new file mode 100644
index 0000000..e06897c
--- /dev/null
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/LongSubject.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+import androidx.kruth.Fact.Companion.fact
+
+/**
+ * Propositions for [Long] subjects.
+ */
+open class LongSubject internal constructor(
+ actual: Long?,
+ metadata: FailureMetadata = FailureMetadata(),
+) : ComparableSubject<Long>(actual, metadata) {
+
+ /**
+ * Prepares for a check that the subject is a number within the given tolerance of an expected
+ * value that will be provided in the next call in the fluent chain.
+ *
+ * @param tolerance an inclusive upper bound on the difference between the subject and object
+ * allowed by the check, which must be a non-negative value.
+ */
+ fun isWithin(tolerance: Long): TolerantLongComparison {
+ return object : TolerantLongComparison() {
+ override fun of(expected: Long) {
+ requireNonNull(actual) {
+ "Actual value cannot be null, tolerance=$tolerance, expected=$expected"
+ }
+
+ checkTolerance(tolerance)
+
+ if (!equalWithinTolerance(actual, expected, tolerance)) {
+ failWithoutActual(
+ fact("expected", expected),
+ fact("but was", actual),
+ fact("outside tolerance", tolerance),
+ )
+ }
+ }
+ }
+ }
+
+ /**
+ * Prepares for a check that the subject is a number not within the given tolerance of an
+ * expected value that will be provided in the next call in the fluent chain.
+ *
+ * @param tolerance an exclusive lower bound on the difference between the subject and object
+ * allowed by the check, which must be a non-negative value.
+ */
+ fun isNotWithin(tolerance: Long): TolerantLongComparison {
+ return object : TolerantLongComparison() {
+ override fun of(expected: Long) {
+ requireNonNull(actual) {
+ "Actual value cannot be null, tolerance=$tolerance, expected=$expected"
+ }
+
+ checkTolerance(tolerance)
+
+ if (equalWithinTolerance(actual, expected, tolerance)) {
+ failWithoutActual(
+ fact("expected not to be", expected),
+ fact("but was", actual),
+ fact("within tolerance", tolerance),
+ )
+ }
+ }
+ }
+ }
+
+ @Deprecated(
+ "Use isEqualTo instead. Long comparison is consistent with equality.",
+ ReplaceWith("this.isEqualTo(other)"),
+ )
+ override fun isEquivalentAccordingToCompareTo(other: Long?) {
+ super.isEquivalentAccordingToCompareTo(other)
+ }
+
+ private fun checkTolerance(tolerance: Long) {
+ require(tolerance >= 0) { "tolerance ($tolerance) cannot be negative" }
+ }
+
+ /**
+ * A partially specified check about an approximate relationship to a `long` subject using a
+ * tolerance.
+ */
+ abstract class TolerantLongComparison internal constructor() {
+ /**
+ * Fails if the subject was expected to be within the tolerance of the given value but was not
+ * *or* if it was expected *not* to be within the tolerance but was. The subject and
+ * tolerance are specified earlier in the fluent call chain.
+ */
+ abstract fun of(expected: Long)
+
+ /**
+ * @throws UnsupportedOperationException always
+ */
+ @Deprecated(
+ "Not supported on TolerantLongComparison. " +
+ "If you meant to compare longs, use `of(Long)` instead."
+ )
+ override fun equals(other: Any?): Boolean {
+ throw UnsupportedOperationException(
+ "If you meant to compare longs, use of(Long) instead."
+ )
+ }
+
+ /**
+ * @throws UnsupportedOperationException always
+ */
+ @Deprecated("Not supported on TolerantLongComparison")
+ override fun hashCode(): Int {
+ throw UnsupportedOperationException("Subject.hashCode() is not supported.")
+ }
+ }
+}
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MathUtil.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MathUtil.kt
index bd9ffc8..f4c0d2e 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MathUtil.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/MathUtil.kt
@@ -41,6 +41,27 @@
}
/**
+ * Returns true iff [left] and [right] are values within [tolerance] of each other.
+ */
+internal fun equalWithinTolerance(left: Long, right: Long, tolerance: Long): Boolean =
+ try {
+ val absDiff = abs(left subtractExact right)
+ 0 <= absDiff && absDiff <= abs(tolerance)
+ } catch (e: ArithmeticException) {
+ // The numbers are so far apart their difference isn't even a long.
+ false
+ }
+
+private infix fun Long.subtractExact(other: Long): Long {
+ val result = this - other
+ if ((this xor other) and (this xor result) < 0) {
+ throw ArithmeticException("Overflow when subtracting $other flow $this")
+ }
+
+ return result
+}
+
+/**
* Returns true iff [left] and [right] are finite values not within [tolerance]
* of each other. Note that both this method and [equalWithinTolerance] returns false if
* either [left] or [right] is infinite or NaN.
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
index c4d8a3b..2e6e43e 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
@@ -65,10 +65,21 @@
fun that(actual: Boolean?): BooleanSubject =
BooleanSubject(actual = actual, metadata = metadata)
+ fun that(actual: Long): LongSubject =
+ LongSubject(actual = actual, metadata = metadata)
+
+ // Workaround for https://youtrack.jetbrains.com/issue/KT-645
+ fun <T : Long?> that(actual: T): LongSubject =
+ LongSubject(actual = actual, metadata = metadata)
+
fun that(actual: Double?): DoubleSubject =
DoubleSubject(actual = actual, metadata = metadata)
- fun that(actual: Int?): IntegerSubject =
+ fun that(actual: Int): IntegerSubject =
+ IntegerSubject(actual = actual, metadata = metadata)
+
+ // Workaround for https://youtrack.jetbrains.com/issue/KT-645
+ fun <T : Int?> that(actual: T): IntegerSubject =
IntegerSubject(actual = actual, metadata = metadata)
fun that(actual: String?): StringSubject =
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Utils.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Utils.kt
index fbf3b79..d5cafd9 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Utils.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Utils.kt
@@ -19,6 +19,8 @@
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
+internal const val HUMAN_UNDERSTANDABLE_EMPTY_STRING = "\"\" (empty String)"
+
/**
* Same as [requireNotNull] but throws [NullPointerException] instead of [IllegalArgumentException].
*
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/LongSubjectTest.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/LongSubjectTest.kt
new file mode 100644
index 0000000..4efa3ad
--- /dev/null
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/LongSubjectTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class LongSubjectTest {
+
+ @Test
+ fun simpleEquality() {
+ assertThat(4L).isEqualTo(4L)
+ }
+
+ @Test
+ fun simpleInequality() {
+ assertThat(4L).isNotEqualTo(5L)
+ }
+
+ @Test
+ fun equalityWithInts() {
+ assertThat(0L).isEqualTo(0)
+ assertFailsWith<AssertionError> {
+ assertThat(0L).isNotEqualTo(0)
+ }
+ }
+
+ @Test
+ fun equalityFail() {
+ assertFailsWith<AssertionError> {
+ assertThat(4L).isEqualTo(5L)
+ }
+ }
+
+ @Test
+ fun inequalityFail() {
+ assertFailsWith<AssertionError> {
+ assertThat(4L).isNotEqualTo(4L)
+ }
+ }
+
+ @Test
+ fun equalityOfNulls() {
+ assertThat(null as Long?).isEqualTo(null)
+ }
+
+ @Test
+ fun equalityOfNullsFail_nullActual() {
+ assertFailsWith<AssertionError> {
+ assertThat(null as Long?).isEqualTo(5L)
+ }
+ }
+
+ @Test
+ fun equalityOfNullsFail_nullExpected() {
+ assertFailsWith<AssertionError> {
+ assertThat(5L).isEqualTo(null)
+ }
+ }
+
+ @Test
+ fun inequalityOfNulls() {
+ assertThat(4L).isNotEqualTo(null)
+ assertThat(null as Int?).isNotEqualTo(4L)
+ }
+
+ @Test
+ fun inequalityOfNullsFail() {
+ assertFailsWith<AssertionError> {
+ assertThat(null as Long?).isNotEqualTo(null)
+ }
+ }
+
+ @Test
+ fun testNumericTypeWithSameValue_shouldBeEqual_long_long() {
+ assertFailsWith<AssertionError> {
+ assertThat(42L).isNotEqualTo(42L)
+ }
+ }
+
+ @Test
+ fun testNumericTypeWithSameValue_shouldBeEqual_long_int() {
+ assertFailsWith<AssertionError> {
+ assertThat(42L).isNotEqualTo(42)
+ }
+ }
+
+ @Test
+ fun isGreaterThan_int_strictly() {
+ assertFailsWith<AssertionError> {
+ assertThat(2L).isGreaterThan(3)
+ }
+ }
+
+ @Test
+ fun isGreaterThan_int() {
+ assertFailsWith<AssertionError> {
+ assertThat(2L).isGreaterThan(2)
+ }
+ assertThat(2L).isGreaterThan(1)
+ }
+
+ @Test
+ fun isLessThan_int_strictly() {
+ assertFailsWith<AssertionError> {
+ assertThat(2L).isLessThan(1)
+ }
+ }
+
+ @Test
+ fun isLessThan_int() {
+ assertFailsWith<AssertionError> {
+ assertThat(2L).isLessThan(2)
+ }
+ assertThat(2L).isLessThan(3)
+ }
+
+ @Test
+ fun isAtLeast_int() {
+ assertFailsWith<AssertionError> {
+ assertThat(2L).isAtLeast(3)
+ }
+ assertThat(2L).isAtLeast(2)
+ assertThat(2L).isAtLeast(1)
+ }
+
+ @Test
+ fun isAtMost_int() {
+ assertFailsWith<AssertionError> {
+ assertThat(2L).isAtMost(1)
+ }
+ assertThat(2L).isAtMost(2)
+ assertThat(2L).isAtMost(3)
+ }
+
+ @Test
+ fun isWithinOf() {
+ assertThat(20000L).isWithin(0L).of(20000L)
+ assertThat(20000L).isWithin(1L).of(20000L)
+ assertThat(20000L).isWithin(10000L).of(20000L)
+ assertThat(20000L).isWithin(10000L).of(30000L)
+ assertThat(Long.MIN_VALUE).isWithin(1L).of(Long.MIN_VALUE + 1)
+ assertThat(Long.MAX_VALUE).isWithin(1L).of(Long.MAX_VALUE - 1)
+ assertThat(Long.MAX_VALUE / 2).isWithin(Long.MAX_VALUE).of(-Long.MAX_VALUE / 2)
+ assertThat(-Long.MAX_VALUE / 2).isWithin(Long.MAX_VALUE).of(Long.MAX_VALUE / 2)
+ assertThatIsWithinFails(20000L, 9999L, 30000L)
+ assertThatIsWithinFails(20000L, 10000L, 30001L)
+ assertThatIsWithinFails(Long.MIN_VALUE, 0L, Long.MAX_VALUE)
+ assertThatIsWithinFails(Long.MAX_VALUE, 0L, Long.MIN_VALUE)
+ assertThatIsWithinFails(Long.MIN_VALUE, 1L, Long.MIN_VALUE + 2)
+ assertThatIsWithinFails(Long.MAX_VALUE, 1L, Long.MAX_VALUE - 2)
+ // Don't fall for rollover
+ assertThatIsWithinFails(Long.MIN_VALUE, 1L, Long.MAX_VALUE)
+ assertThatIsWithinFails(Long.MAX_VALUE, 1L, Long.MIN_VALUE)
+ }
+
+ private fun assertThatIsWithinFails(actual: Long, tolerance: Long, expected: Long) {
+ assertFailsWith<AssertionError> {
+ assertThat(actual).isWithin(tolerance).of(expected)
+ }
+ }
+
+ @Test
+ fun isNotWithinOf() {
+ assertThatIsNotWithinFails(20000L, 0L, 20000L)
+ assertThatIsNotWithinFails(20000L, 1L, 20000L)
+ assertThatIsNotWithinFails(20000L, 10000L, 20000L)
+ assertThatIsNotWithinFails(20000L, 10000L, 30000L)
+ assertThatIsNotWithinFails(Long.MIN_VALUE, 1L, Long.MIN_VALUE + 1)
+ assertThatIsNotWithinFails(Long.MAX_VALUE, 1L, Long.MAX_VALUE - 1)
+ assertThatIsNotWithinFails(Long.MAX_VALUE / 2, Long.MAX_VALUE, -Long.MAX_VALUE / 2)
+ assertThatIsNotWithinFails(-Long.MAX_VALUE / 2, Long.MAX_VALUE, Long.MAX_VALUE / 2)
+ assertThat(20000L).isNotWithin(9999L).of(30000L)
+ assertThat(20000L).isNotWithin(10000L).of(30001L)
+ assertThat(Long.MIN_VALUE).isNotWithin(0L).of(Long.MAX_VALUE)
+ assertThat(Long.MAX_VALUE).isNotWithin(0L).of(Long.MIN_VALUE)
+ assertThat(Long.MIN_VALUE).isNotWithin(1L).of(Long.MIN_VALUE + 2)
+ assertThat(Long.MAX_VALUE).isNotWithin(1L).of(Long.MAX_VALUE - 2)
+ // Don't fall for rollover
+ assertThat(Long.MIN_VALUE).isNotWithin(1L).of(Long.MAX_VALUE)
+ assertThat(Long.MAX_VALUE).isNotWithin(1L).of(Long.MIN_VALUE)
+ }
+
+ private fun assertThatIsNotWithinFails(actual: Long, tolerance: Long, expected: Long) {
+ assertFailsWith<AssertionError> {
+ assertThat(actual).isNotWithin(tolerance).of(expected)
+ }
+ }
+
+ @Test
+ fun isWithinIntegers() {
+ assertThat(20000L).isWithin(0).of(20000)
+ assertThat(20000L).isWithin(1).of(20000)
+ assertThat(20000L).isWithin(10000).of(20000)
+ assertThat(20000L).isWithin(10000).of(30000)
+ assertThat(20000L).isNotWithin(0).of(200000)
+ assertThat(20000L).isNotWithin(1).of(200000)
+ assertThat(20000L).isNotWithin(10000).of(200000)
+ assertThat(20000L).isNotWithin(10000).of(300000)
+ }
+
+ @Test
+ fun isWithinNegativeTolerance() {
+ isWithinNegativeToleranceThrowsIAE(0L, -10, 5)
+ isWithinNegativeToleranceThrowsIAE(0L, -10, 20)
+ isNotWithinNegativeToleranceThrowsIAE(0L, -10, 5)
+ isNotWithinNegativeToleranceThrowsIAE(0L, -10, 20)
+ }
+
+ private fun isWithinNegativeToleranceThrowsIAE(
+ actual: Long,
+ tolerance: Long,
+ expected: Long,
+ ) {
+ assertFailsWith<IllegalArgumentException>(
+ assert = { e ->
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("tolerance ($tolerance) cannot be negative")
+ }
+ ) {
+ assertThat(actual).isWithin(tolerance).of(expected)
+ }
+ }
+
+ private fun isNotWithinNegativeToleranceThrowsIAE(
+ actual: Long,
+ tolerance: Long,
+ expected: Long,
+ ) {
+ assertFailsWith<IllegalArgumentException>(
+ assert = { e ->
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("tolerance ($tolerance) cannot be negative")
+ }
+ ) {
+ assertThat(actual).isNotWithin(tolerance).of(expected)
+ }
+ }
+}
diff --git a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/TestingUtils.kt b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/TestingUtils.kt
index dffae5e..74b3d39 100644
--- a/kruth/kruth/src/commonTest/kotlin/androidx/kruth/TestingUtils.kt
+++ b/kruth/kruth/src/commonTest/kotlin/androidx/kruth/TestingUtils.kt
@@ -31,3 +31,18 @@
internal expect fun Float.nextUp(): Float
internal expect fun Float.nextDown(): Float
+
+internal inline fun <reified E : Throwable> assertFailsWith(
+ assert: (E) -> Unit,
+ block: () -> Unit,
+) {
+ try {
+ block()
+ } catch (e: Throwable) {
+ if (e::class == E::class) {
+ assert(e as E)
+ } else {
+ throw e
+ }
+ }
+}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Kruth.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Kruth.jvm.kt
index ac03133..ea1fbc7 100644
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Kruth.jvm.kt
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/Kruth.jvm.kt
@@ -17,6 +17,8 @@
package androidx.kruth
import com.google.common.base.Optional
+import com.google.common.collect.Multimap
+import com.google.common.collect.Multiset
import java.math.BigDecimal
fun assertThat(actual: Class<*>): ClassSubject =
@@ -27,3 +29,9 @@
fun assertThat(actual: BigDecimal): BigDecimalSubject =
BigDecimalSubject(actual)
+
+fun <T> assertThat(actual: Multiset<T>): MultisetSubject<T> =
+ MultisetSubject(actual = actual)
+
+fun <K, V> assertThat(actual: Multimap<K, V>): MultimapSubject<K, V> =
+ MultimapSubject(actual = actual)
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/MultimapSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/MultimapSubject.jvm.kt
new file mode 100644
index 0000000..4e0b0c2
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/MultimapSubject.jvm.kt
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+import androidx.kruth.Fact.Companion.fact
+import androidx.kruth.Fact.Companion.simpleFact
+import androidx.kruth.Subject.Factory
+import com.google.common.collect.ImmutableList
+import com.google.common.collect.LinkedHashMultiset
+import com.google.common.collect.LinkedListMultimap
+import com.google.common.collect.ListMultimap
+import com.google.common.collect.Maps.immutableEntry
+import com.google.common.collect.Multimap
+import com.google.common.collect.SetMultimap
+
+/** Propositions for [Multimap] subjects. */
+open class MultimapSubject<K, V> internal constructor(
+ actual: Multimap<K, V>?,
+ metadata: FailureMetadata = FailureMetadata(),
+) : Subject<Multimap<K, V>>(actual, metadata) {
+
+ /** Fails if the multimap is not empty. */
+ fun isEmpty() {
+ if (!requireNonNull(actual).isEmpty) {
+ failWithActual(simpleFact("Expected to be empty"))
+ }
+ }
+
+ /** Fails if the multimap is empty. */
+ fun isNotEmpty() {
+ if (requireNonNull(actual).isEmpty) {
+ failWithoutActual(simpleFact("Expected not to be empty"))
+ }
+ }
+
+ /** Fails if the multimap does not have the given size. */
+ fun hasSize(expectedSize: Int) {
+ require(expectedSize >= 0) { "expectedSize($expectedSize) must be >= 0" }
+ // TODO: Use check("size()") once the API is there
+ check().that(requireNonNull(actual).size()).isEqualTo(expectedSize)
+ }
+
+ /** Fails if the multimap does not contain the given key. */
+ fun containsKey(key: K) {
+ // TODO: Use check("keySet()") once the API is there
+ check().that(requireNonNull(actual).keySet()).contains(key)
+ }
+
+ /** Fails if the multimap contains the given key. */
+ fun doesNotContainKey(key: K) {
+ // TODO: Use check("keySet()") once the API is there
+ check().that(requireNonNull(actual).keySet()).doesNotContain(key)
+ }
+
+ /** Fails if the multimap does not contain the given entry. */
+ fun containsEntry(key: K, value: Any?) {
+ requireNonNull(actual)
+
+ if (!actual.containsEntry(key, value)) {
+ val entry = immutableEntry(key, value)
+ val entryList = ImmutableList.of(entry)
+ if (actual.entries().hasMatchingToStringPair(entryList)) {
+ failWithActual(
+ fact("expected to contain entry", entry),
+ fact("an instance of", entry.typeName()),
+ simpleFact("but did not"),
+ fact(
+ "though it did contain",
+ actual.entries()
+ .retainMatchingToString(entryList)
+ .countDuplicatesAndAddTypeInfo(),
+ ),
+ )
+ } else if (actual.containsKey(key)) {
+ failWithActual(
+ fact("expected to contain entry", entry),
+ simpleFact("but did not"),
+ fact("though it did contain values with that key", actual.asMap()[key]),
+ )
+ } else if (actual.containsValue(value)) {
+ val keys =
+ actual.entries()
+ .asSequence()
+ .filter { it.value == value }
+ .map { it.key }
+ .toList()
+
+ failWithActual(
+ fact("expected to contain entry", entry),
+ simpleFact("but did not"),
+ fact("though it did contain keys with that value", keys),
+ )
+ } else {
+ failWithActual("expected to contain entry", immutableEntry(key, value))
+ }
+ }
+ }
+
+ /** Fails if the multimap contains the given entry. */
+ fun doesNotContainEntry(key: K, value: Any?) {
+ // TODO: Use checkNoNeedToDisplayBothValues("entries()") when the API is there?
+ check()
+ .that(requireNonNull(actual).entries())
+ .doesNotContain(immutableEntry(key, value))
+ }
+
+ /**
+ * Returns a context-aware [Subject] for making assertions about the values for the given
+ * key within the [Multimap].
+ *
+ * This method performs no checks on its own and cannot cause test failures. Subsequent
+ * assertions must be chained onto this method call to test properties of the [Multimap].
+ */
+ open fun valuesForKey(key: K): IterableSubject<V> {
+ // TODO: Use check("valuesForKey($key)") once the API is the there
+ return check().that(requireNonNull(actual).get(key))
+ }
+
+ override fun isEqualTo(expected: Any?) {
+ if (actual == expected) {
+ return
+ }
+
+ // Fail but with a more descriptive message:
+ if (((actual is ListMultimap) && (expected is SetMultimap<*, *>)) ||
+ ((actual is SetMultimap) && (expected is ListMultimap<*, *>))
+ ) {
+ val actualType = if (actual is ListMultimap) "ListMultimap" else "SetMultimap"
+ val otherType = if (expected is ListMultimap<*, *>) "ListMultimap" else "SetMultimap"
+ failWithoutActual(
+ fact("expected", expected),
+ fact("an instance of", otherType),
+ fact("but was", actual),
+ fact("an instance of", actualType),
+ simpleFact("a $actualType cannot equal a $otherType if either is non-empty"),
+ )
+ } else if (actual is ListMultimap) {
+ @Suppress("UNCHECKED_CAST")
+ containsExactlyEntriesIn(requireNonNull(expected) as Multimap<K, *>).inOrder()
+ } else if (actual is SetMultimap<*, *>) {
+ @Suppress("UNCHECKED_CAST")
+ containsExactlyEntriesIn(requireNonNull(expected) as Multimap<K, *>)
+ } else {
+ super.isEqualTo(expected)
+ }
+ }
+
+ /**
+ * Fails if the [Multimap] does not contain precisely the same entries as the argument
+ * [Multimap].
+ *
+ *
+ * A subsequent call to [Ordered.inOrder] may be made if the caller wishes to verify that
+ * the two multimaps iterate fully in the same order. That is, their key sets iterate in the same
+ * order, and the value collections for each key iterate in the same order.
+ */
+ fun containsExactlyEntriesIn(expectedMultimap: Multimap<K, *>?): Ordered {
+ requireNonNull(actual)
+ requireNonNull(expectedMultimap)
+
+ val missing = difference(expectedMultimap, actual)
+ val extra = difference(actual, expectedMultimap)
+
+ // TODO(kak): Possible enhancement: Include "[1 copy]" if the element does appear in
+ // the subject but not enough times. Similarly for unexpected extra items.
+ if (!missing.isEmpty) {
+ if (!extra.isEmpty) {
+ val addTypeInfo = missing.entries().hasMatchingToStringPair(extra.entries())
+ // Note: The usage of countDuplicatesAndAddTypeInfo() below causes entries no longer to be
+ // grouped by key in the 'missing' and 'unexpected items' parts of the message (we still
+ // show the actual and expected multimaps in the standard format).
+
+ val missingDisplay =
+ if (addTypeInfo) {
+ missing.annotateEmptyStrings().entries().countDuplicatesAndAddTypeInfo()
+ } else {
+ missing.annotateEmptyStrings().countDuplicates()
+ }
+
+ val extraDisplay =
+ if (addTypeInfo) {
+ extra.annotateEmptyStrings().entries().countDuplicatesAndAddTypeInfo()
+ } else {
+ extra.annotateEmptyStrings().countDuplicates()
+ }
+
+ failWithActual(
+ fact("missing", missingDisplay),
+ fact("unexpected", extraDisplay),
+ simpleFact("---"),
+ fact("expected", expectedMultimap.annotateEmptyStrings()),
+ )
+ } else {
+ failWithActual(
+ fact("missing", missing.annotateEmptyStrings().countDuplicates()),
+ simpleFact("---"),
+ fact("expected", expectedMultimap.annotateEmptyStrings()),
+ )
+ }
+ } else if (!extra.isEmpty) {
+ failWithActual(
+ fact("unexpected", extra.annotateEmptyStrings().countDuplicates()),
+ simpleFact("---"),
+ fact("expected", expectedMultimap.annotateEmptyStrings()),
+ )
+ }
+
+ return MultimapInOrder(allowUnexpected = false, expectedMultimap = expectedMultimap)
+ }
+
+ /**
+ * Fails if the [Multimap] does not contain at least the entries in the argument [ ].
+ *
+ *
+ * A subsequent call to [Ordered.inOrder] may be made if the caller wishes to verify that
+ * the entries are present in the same order as given. That is, the keys are present in the given
+ * order in the key set, and the values for each key are present in the given order order in the
+ * value collections.
+ */
+ fun containsAtLeastEntriesIn(expectedMultimap: Multimap<K, *>?): Ordered {
+ requireNonNull(actual)
+ requireNonNull(expectedMultimap)
+
+ val missing = difference(expectedMultimap, actual)
+
+ if (!missing.isEmpty) {
+ failWithActual(
+ fact("missing", missing.annotateEmptyStrings().countDuplicates()),
+ simpleFact("---"),
+ fact("expected to contain at least", expectedMultimap.annotateEmptyStrings()),
+ )
+ }
+
+ return MultimapInOrder(allowUnexpected = true, expectedMultimap = expectedMultimap)
+ }
+
+ /** Fails if the multimap is not empty. */
+ fun containsExactly(): Ordered =
+ check()
+ .about(iterableEntries())
+ .that(checkNotNull(actual).entries())
+ .containsExactly()
+
+ /** Fails if the multimap does not contain exactly the given set of key/value pairs. */
+ fun containsExactly(vararg entries: Pair<K, *>): Ordered {
+ return containsExactlyEntriesIn(accumulateMultimap(entries))
+ }
+
+ /** Fails if the multimap does not contain at least the given key/value pairs. */
+ fun containsAtLeast(vararg entries: Pair<K, *>): Ordered {
+ return containsAtLeastEntriesIn(accumulateMultimap(entries))
+ }
+
+ private fun iterableEntries(): Factory<IterableSubject<*>, Iterable<*>> =
+ Factory<IterableSubject<*>, Iterable<*>> { metadata, actual ->
+ IterableSubject(actual, metadata)
+ }
+
+ private inner class MultimapInOrder(
+ private val allowUnexpected: Boolean,
+ private val expectedMultimap: Multimap<K, *>,
+ ) : Ordered {
+ /**
+ * Checks whether entries in expected appear in the same order in actual.
+ *
+ *
+ * We allow for actual to have more items than the expected to support both [ ][.containsExactly] and [.containsAtLeast].
+ */
+ override fun inOrder() {
+ requireNonNull(actual)
+
+ // We use the fact that Set#intersect's result has the same order as the first parameter
+ val keysInOrder =
+ actual.keySet().intersect(expectedMultimap.keySet()) == expectedMultimap.keySet()
+
+ val keysWithValuesOutOfOrder = LinkedHashSet<K>()
+ for (key in expectedMultimap.keySet()) {
+ val actualVals = actual[key].toList()
+ val expectedVals = expectedMultimap[key].toList()
+ val actualIterator = actualVals.iterator()
+ for (value in expectedVals) {
+ if (!actualIterator.advanceToFind(value)) {
+ keysWithValuesOutOfOrder.add(key)
+ break
+ }
+ }
+ }
+
+ if (!keysInOrder) {
+ if (keysWithValuesOutOfOrder.isNotEmpty()) {
+ failWithActual(
+ simpleFact("contents match, but order was wrong"),
+ simpleFact("keys are not in order"),
+ fact("keys with out-of-order values", keysWithValuesOutOfOrder),
+ simpleFact("---"),
+ fact(
+ if (allowUnexpected) "expected to contain at least" else "expected",
+ expectedMultimap,
+ ),
+ )
+ } else {
+ failWithActual(
+ simpleFact("contents match, but order was wrong"),
+ simpleFact("keys are not in order"),
+ simpleFact("---"),
+ fact(
+ if (allowUnexpected) "expected to contain at least" else "expected",
+ expectedMultimap,
+ ),
+ )
+ }
+ } else if (keysWithValuesOutOfOrder.isNotEmpty()) {
+ failWithActual(
+ simpleFact("contents match, but order was wrong"),
+ fact("keys with out-of-order values", keysWithValuesOutOfOrder),
+ simpleFact("---"),
+ fact(
+ if (allowUnexpected) "expected to contain at least" else "expected",
+ expectedMultimap,
+ ),
+ )
+ }
+ }
+ }
+}
+
+private fun <K, V> difference(
+ minuend: Multimap<K, out V>,
+ subtrahend: Multimap<in K, out V>,
+): ListMultimap<K, V> {
+ val difference = LinkedListMultimap.create<K, V>()
+ for (key: K in minuend.keySet()) {
+ val a = minuend.get(key).toMutableList()
+ val b = subtrahend.get(key).toMutableList()
+ difference.putAll(key, difference(a, b))
+ }
+
+ return difference
+}
+
+private fun <T> difference(minuend: Iterable<T>, subtrahend: Iterable<T>): List<T> {
+ val remaining = LinkedHashMultiset.create(subtrahend)
+ return minuend.filterNot(remaining::remove)
+}
+
+private fun <K> Multimap<K, *>.countDuplicates(): String =
+ keySet().joinToString(
+ prefix = "{",
+ postfix = "}",
+ transform = { key -> "$key=${get(key).countDuplicates()}" },
+ )
+
+/**
+ * Returns a multimap with all empty strings (as keys or values) replaced by a non-empty human
+ * understandable indicator for an empty string.
+ *
+ *
+ * Returns the given multimap if it contains no empty strings.
+ */
+private fun <K, V> Multimap<K, V>.annotateEmptyStrings(): Multimap<K, V> =
+ if (containsKey("") || containsValue("")) {
+ val annotatedMultimap = LinkedListMultimap.create<K, V>()
+ for ((k, v) in entries()) {
+ @Suppress("UNCHECKED_CAST")
+ val key: K = (if (k == "") HUMAN_UNDERSTANDABLE_EMPTY_STRING as K else k)
+ @Suppress("UNCHECKED_CAST")
+ val value: V = (if (v == "") HUMAN_UNDERSTANDABLE_EMPTY_STRING as V else v)
+ annotatedMultimap.put(key, value)
+ }
+ annotatedMultimap
+ } else {
+ this
+ }
+
+/**
+ * Advances the iterator until it either returns value, or has no more elements.
+ *
+ * Returns true if the value was found, false if the end was reached before finding it.
+ *
+ * This is basically the same as [com.google.common.collect.Iterables.contains], but where the
+ * contract explicitly states that the iterator isn't advanced beyond the value if the value is
+ * found.
+ */
+private fun <T> Iterator<T>.advanceToFind(value: T): Boolean {
+ while (hasNext()) {
+ if (next() == value) {
+ return true
+ }
+ }
+
+ return false
+}
+
+private fun <K, V> accumulateMultimap(entries: Array<out Pair<K, V>>): ListMultimap<K, V> =
+ LinkedListMultimap.create<K, V>().also { map ->
+ entries.forEach { (k, v) -> map.put(k, v) }
+ }
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/MultisetSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/MultisetSubject.jvm.kt
new file mode 100644
index 0000000..b18e59e
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/MultisetSubject.jvm.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+import com.google.common.collect.Multiset
+
+/** Propositions for [Multiset] subjects. */
+class MultisetSubject<T> internal constructor(
+ actual: Multiset<T>?,
+ metadata: FailureMetadata = FailureMetadata(),
+) : IterableSubject<T>(actual, metadata) {
+ private val _actual = actual
+
+ /** Fails if the element does not have the given count. */
+ fun hasCount(element: Any?, expectedCount: Int) {
+ require(expectedCount >= 0) { "expectedCount($expectedCount) must be >= 0" }
+ val actualCount = requireNonNull(_actual).count(element)
+
+ // TODO: Use check("count($element)") instead once the API is there
+ check().that(actualCount).isEqualTo(expectedCount)
+ }
+}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt
index d3033b5..891e0a2 100644
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt
@@ -17,6 +17,8 @@
package androidx.kruth
import com.google.common.base.Optional
+import com.google.common.collect.Multimap
+import com.google.common.collect.Multiset
import java.math.BigDecimal
internal actual interface PlatformStandardSubjectBuilder {
@@ -24,6 +26,8 @@
fun that(actual: Class<*>): ClassSubject
fun <T : Any> that(actual: Optional<T>): GuavaOptionalSubject<T>
fun that(actual: BigDecimal): BigDecimalSubject
+ fun <T> that(actual: Multiset<T>): MultisetSubject<T>
+ fun <K, V> that(actual: Multimap<K, V>): MultimapSubject<K, V>
}
internal actual class PlatformStandardSubjectBuilderImpl actual constructor(
@@ -38,4 +42,10 @@
override fun that(actual: BigDecimal): BigDecimalSubject =
BigDecimalSubject(actual = actual, metadata = metadata)
+
+ override fun <T> that(actual: Multiset<T>): MultisetSubject<T> =
+ MultisetSubject(actual = actual, metadata = metadata)
+
+ override fun <K, V> that(actual: Multimap<K, V>): MultimapSubject<K, V> =
+ MultimapSubject(actual = actual, metadata = metadata)
}
diff --git a/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/MultimapSubjectTest.kt b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/MultimapSubjectTest.kt
new file mode 100644
index 0000000..0130bc4
--- /dev/null
+++ b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/MultimapSubjectTest.kt
@@ -0,0 +1,741 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+import com.google.common.collect.ArrayListMultimap
+import com.google.common.collect.HashMultimap
+import com.google.common.collect.ImmutableListMultimap
+import com.google.common.collect.ImmutableMultimap
+import com.google.common.collect.ImmutableSetMultimap
+import com.google.common.collect.LinkedListMultimap
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class MultimapSubjectTest {
+
+ @Test
+ fun listMultimapIsEqualTo_passes() {
+ val multimapA =
+ ImmutableListMultimap.builder<String, String>()
+ .putAll("kurt", "kluever", "russell", "cobain")
+ .build()
+ val multimapB =
+ ImmutableListMultimap.builder<String, String>()
+ .putAll("kurt", "kluever", "russell", "cobain")
+ .build()
+ assertThat(multimapA == multimapB).isTrue()
+ assertThat(multimapA).isEqualTo(multimapB)
+ }
+
+ @Test
+ fun listMultimapIsEqualTo_fails() {
+ val multimapA =
+ ImmutableListMultimap.builder<String, String>()
+ .putAll("kurt", "kluever", "russell", "cobain")
+ .build()
+ val multimapB =
+ ImmutableListMultimap.builder<String, String>()
+ .putAll("kurt", "kluever", "cobain", "russell")
+ .build()
+
+ assertFailsWith<AssertionError> {
+ assertThat(multimapA).isEqualTo(multimapB)
+ }
+ }
+
+ @Test
+ fun setMultimapIsEqualTo_passes() {
+ val multimapA =
+ ImmutableSetMultimap.builder<String, String>()
+ .putAll("kurt", "kluever", "russell", "cobain")
+ .build()
+ val multimapB =
+ ImmutableSetMultimap.builder<String, String>()
+ .putAll("kurt", "kluever", "cobain", "russell")
+ .build()
+ assertThat(multimapA == multimapB).isTrue()
+ assertThat(multimapA).isEqualTo(multimapB)
+ }
+
+ @Test
+ fun setMultimapIsEqualTo_fails() {
+ val multimapA =
+ ImmutableSetMultimap.builder<String, String>()
+ .putAll("kurt", "kluever", "russell", "cobain")
+ .build()
+ val multimapB =
+ ImmutableSetMultimap.builder<String, String>()
+ .putAll("kurt", "kluever", "russell")
+ .build()
+ assertFailsWith<AssertionError> {
+ assertThat(multimapA).isEqualTo(multimapB)
+ }
+ }
+
+ @Test
+ fun setMultimapIsEqualToListMultimap_fails() {
+ val multimapA =
+ ImmutableSetMultimap.builder<String, String>()
+ .putAll("kurt", "kluever", "russell", "cobain")
+ .build()
+ val multimapB =
+ ImmutableListMultimap.builder<String, String>()
+ .putAll("kurt", "kluever", "russell", "cobain")
+ .build()
+ assertFailsWith<AssertionError> {
+ assertThat(multimapA).isEqualTo(multimapB)
+ }
+ }
+
+ @Test
+ fun isEqualTo_failsWithSameToString() {
+ assertFailsWith<AssertionError> {
+ assertThat(ImmutableMultimap.of(1, "a", 1, "b", 2, "c"))
+ .isEqualTo(ImmutableMultimap.of(1L, "a", 1L, "b", 2L, "c"))
+ }
+ }
+
+ @Test
+ fun multimapIsEmpty() {
+ val multimap = ImmutableMultimap.of<String, String>()
+ assertThat(multimap).isEmpty()
+ }
+
+ @Test
+ fun multimapIsEmptyWithFailure() {
+ val multimap = ImmutableMultimap.of(1, 5)
+ assertFailsWith<AssertionError> {
+ assertThat(multimap).isEmpty()
+ }
+ }
+
+ @Test
+ fun multimapIsNotEmpty() {
+ val multimap = ImmutableMultimap.of(1, 5)
+ assertThat(multimap).isNotEmpty()
+ }
+
+ @Test
+ fun multimapIsNotEmptyWithFailure() {
+ val multimap = ImmutableMultimap.of<Int, Int>()
+ assertFailsWith<AssertionError> {
+ assertThat(multimap).isNotEmpty()
+ }
+ }
+
+ @Test
+ fun hasSize() {
+ assertThat(ImmutableMultimap.of(1, 2, 3, 4)).hasSize(2)
+ }
+
+ @Test
+ fun hasSizeZero() {
+ assertThat(ImmutableMultimap.of<Any, Any>()).hasSize(0)
+ }
+
+ @Test
+ fun hasSizeNegative() {
+ assertFailsWith<IllegalArgumentException> {
+ assertThat(ImmutableMultimap.of(1, 2)).hasSize(-1)
+ }
+ }
+
+ @Test
+ fun containsKey() {
+ val multimap = ImmutableMultimap.of("kurt", "kluever")
+ assertThat(multimap).containsKey("kurt")
+ }
+
+ @Test
+ fun containsKeyFailure() {
+ val multimap = ImmutableMultimap.of("kurt", "kluever")
+ assertFailsWith<AssertionError> {
+ assertThat(multimap).containsKey("daniel")
+ }
+ }
+
+ @Test
+ fun containsKeyNull() {
+ val multimap = HashMultimap.create<String?, String>()
+ multimap.put(null, "null")
+ assertThat(multimap).containsKey(null)
+ }
+
+ @Test
+ fun containsKeyNullFailure() {
+ val multimap = ImmutableMultimap.of("kurt", "kluever")
+ assertFailsWith<AssertionError> {
+ assertThat(multimap).containsKey(null)
+ }
+ }
+
+ @Test
+ fun containsKey_failsWithSameToString() {
+ assertFailsWith<AssertionError> {
+ assertThat(
+ ImmutableMultimap.of(1L, "value1a", 1L, "value1b", 2L, "value2", "1", "value3")
+ ).containsKey(1)
+ }
+ }
+
+ @Test
+ fun doesNotContainKey() {
+ val multimap = ImmutableMultimap.of("kurt", "kluever")
+ assertThat(multimap).doesNotContainKey("daniel")
+ assertThat(multimap).doesNotContainKey(null)
+ }
+
+ @Test
+ fun doesNotContainKeyFailure() {
+ val multimap = ImmutableMultimap.of("kurt", "kluever")
+ assertFailsWith<AssertionError> {
+ assertThat(multimap).doesNotContainKey("kurt")
+ }
+ }
+
+ @Test
+ fun doesNotContainNullKeyFailure() {
+ val multimap = HashMultimap.create<String?, String>()
+ multimap.put(null, "null")
+ assertFailsWith<AssertionError> {
+ assertThat(multimap).doesNotContainKey(null)
+ }
+ }
+
+ @Test
+ fun containsEntry() {
+ val multimap = ImmutableMultimap.of("kurt", "kluever")
+ assertThat(multimap).containsEntry("kurt", "kluever")
+ }
+
+ @Test
+ fun containsEntryFailure() {
+ val multimap = ImmutableMultimap.of("kurt", "kluever")
+ assertFailsWith<AssertionError> {
+ assertThat(multimap).containsEntry("daniel", "ploch")
+ }
+ }
+
+ @Test
+ fun containsEntryWithNullValueNullExpected() {
+ val actual = ArrayListMultimap.create<String, String?>()
+ actual.put("a", null)
+ assertThat(actual).containsEntry("a", null)
+ }
+
+ @Test
+ fun failContainsEntry() {
+ val actual = ImmutableMultimap.of("a", "A")
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsEntry("b", "B")
+ }
+ }
+
+ @Test
+ fun failContainsEntryFailsWithWrongValueForKey() {
+ val actual = ImmutableMultimap.of("a", "A")
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsEntry("a", "a")
+ }
+ }
+
+ @Test
+ fun failContainsEntryWithNullValuePresentExpected() {
+ val actual = ArrayListMultimap.create<String, String?>()
+ actual.put("a", null)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsEntry("a", "A")
+ }
+ }
+
+ @Test
+ fun failContainsEntryWithPresentValueNullExpected() {
+ val actual = ImmutableMultimap.of("a", "A")
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsEntry("a", null)
+ }
+ }
+
+ @Test
+ fun failContainsEntryFailsWithWrongKeyForValue() {
+ val actual = ImmutableMultimap.of("a", "A")
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsEntry("b", "A")
+ }
+ }
+
+ @Test
+ fun containsEntry_failsWithSameToString() {
+ assertFailsWith<AssertionError> {
+ assertThat(
+ ImmutableMultimap.builder<Any, Any>()
+ .put(1, "1")
+ .put(1, 1L)
+ .put(1L, 1)
+ .put(2, 3)
+ .build()
+ ).containsEntry(1, 1)
+ }
+ }
+
+ @Test
+ fun doesNotContainEntry() {
+ val multimap = ImmutableMultimap.of("kurt", "kluever")
+ assertThat(multimap).doesNotContainEntry("daniel", "ploch")
+ }
+
+ @Test
+ fun doesNotContainEntryFailure() {
+ val multimap = ImmutableMultimap.of("kurt", "kluever")
+ assertFailsWith<AssertionError> {
+ assertThat(multimap).doesNotContainEntry("kurt", "kluever")
+ }
+ }
+
+ @Test
+ fun valuesForKey() {
+ val multimap = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertThat(multimap).valuesForKey(3).hasSize(3)
+ assertThat(multimap).valuesForKey(4).containsExactly("four", "five")
+ assertThat(multimap).valuesForKey(3).containsAtLeast("one", "six").inOrder()
+ assertThat(multimap).valuesForKey(5).isEmpty()
+ }
+
+ @Test
+ fun valuesForKeyListMultimap() {
+ val multimap = ImmutableListMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertThat(multimap).valuesForKey(4).isInStrictOrder()
+ }
+
+ @Test
+ fun containsExactlyEntriesIn() {
+ val listMultimap =
+ ImmutableListMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val setMultimap = ImmutableSetMultimap.copyOf(listMultimap)
+ assertThat(listMultimap).containsExactlyEntriesIn(setMultimap)
+ }
+
+ @Test
+ fun containsExactlyNoArg() {
+ val actual = ImmutableMultimap.of<Int, String>()
+ assertThat(actual).containsExactly()
+ assertThat(actual).containsExactly().inOrder()
+
+ assertFailsWith<AssertionError> {
+ assertThat(ImmutableMultimap.of(42, "Answer", 42, "6x7")).containsExactly()
+ }
+ }
+
+ @Test
+ fun containsExactlyEmpty() {
+ val actual = ImmutableListMultimap.of<Int, String>()
+ val expected = ImmutableSetMultimap.of<Int, String>()
+ assertThat(actual).containsExactlyEntriesIn(expected)
+ assertThat(actual).containsExactlyEntriesIn(expected).inOrder()
+ }
+
+ @Test
+ fun containsExactlyRejectsNull() {
+ val multimap = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertFailsWith<NullPointerException> {
+ assertThat(multimap).containsExactlyEntriesIn(null)
+ }
+ }
+
+ @Test
+ fun containsExactlyRespectsDuplicates() {
+ val actual = ImmutableListMultimap.of(3, "one", 3, "two", 3, "one", 4, "five", 4, "five")
+ val expected = ImmutableListMultimap.of(3, "two", 4, "five", 3, "one", 4, "five", 3, "one")
+ assertThat(actual).containsExactlyEntriesIn(expected)
+ }
+
+ @Test
+ fun containsExactlyRespectsDuplicatesFailure() {
+ val actual = ImmutableListMultimap.of(3, "one", 3, "two", 3, "one", 4, "five", 4, "five")
+ val expected = ImmutableSetMultimap.copyOf(actual)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsExactlyEntriesIn(expected)
+ }
+ }
+
+ @Test
+ fun containsExactlyFailureMissing() {
+ val expected = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val actual = LinkedListMultimap.create(expected)
+ actual.remove(3, "six")
+ actual.remove(4, "five")
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsExactlyEntriesIn(expected)
+ }
+ }
+
+ @Test
+ fun containsExactlyFailureExtra() {
+ val expected = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val actual = LinkedListMultimap.create(expected)
+ actual.put(4, "nine")
+ actual.put(5, "eight")
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsExactlyEntriesIn(expected)
+ }
+ }
+
+ @Test
+ fun containsExactlyFailureBoth() {
+ val expected = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val actual = LinkedListMultimap.create(expected)
+ actual.remove(3, "six")
+ actual.remove(4, "five")
+ actual.put(4, "nine")
+ actual.put(5, "eight")
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsExactlyEntriesIn(expected)
+ }
+ }
+
+ @Test
+ fun containsExactlyFailureWithEmptyStringMissing() {
+ assertFailsWith<AssertionError> {
+ assertThat(ImmutableMultimap.of<Any, Any>()).containsExactly("" to "a")
+ }
+ }
+
+ @Test
+ fun containsExactlyFailureWithEmptyStringExtra() {
+ assertFailsWith<AssertionError> {
+ assertThat(ImmutableMultimap.of("a", "", "", "")).containsExactly("a" to "")
+ }
+ }
+
+ @Test
+ fun containsExactlyFailureWithEmptyStringBoth() {
+ assertFailsWith<AssertionError> {
+ assertThat(ImmutableMultimap.of("a", "")).containsExactly("" to "a")
+ }
+ }
+
+ @Test
+ fun containsExactlyInOrder() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val expected = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertThat(actual).containsExactlyEntriesIn(expected).inOrder()
+ }
+
+ @Test
+ fun containsExactlyInOrderDifferentTypes() {
+ val listMultimap =
+ ImmutableListMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val setMultimap = ImmutableSetMultimap.copyOf(listMultimap)
+ assertThat(listMultimap).containsExactlyEntriesIn(setMultimap).inOrder()
+ }
+
+ @Test
+ fun containsExactlyInOrderFailure() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val expected = ImmutableMultimap.of(4, "four", 3, "six", 4, "five", 3, "two", 3, "one")
+ assertThat(actual).containsExactlyEntriesIn(expected)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsExactlyEntriesIn(expected).inOrder()
+ }
+ }
+
+ @Test
+ fun containsExactlyInOrderFailureValuesOnly() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val expected = ImmutableMultimap.of(3, "six", 3, "two", 3, "one", 4, "five", 4, "four")
+ assertThat(actual).containsExactlyEntriesIn(expected)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsExactlyEntriesIn(expected).inOrder()
+ }
+ }
+
+ @Test
+ fun containsExactlyVararg() {
+ val listMultimap = ImmutableListMultimap.of(1, "one", 3, "six", 3, "two")
+ assertThat(listMultimap).containsExactly(1 to "one", 3 to "six", 3 to "two")
+ }
+
+ @Test
+ fun containsExactlyVarargWithNull() {
+ val listMultimap =
+ LinkedListMultimap.create(ImmutableListMultimap.of(1, "one", 3, "six", 3, "two"))
+ listMultimap.put(4, null)
+ assertThat(listMultimap).containsExactly(1 to "one", 3 to "six", 3 to "two", 4 to null)
+ }
+
+ @Test
+ fun containsExactlyVarargFailureMissing() {
+ val expected = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val actual = LinkedListMultimap.create(expected)
+ actual.remove(3, "six")
+ actual.remove(4, "five")
+ assertFailsWith<AssertionError> {
+ assertThat(actual)
+ .containsExactly(3 to "one", 3 to "six", 3 to "two", 4 to "five", 4 to "four")
+ }
+ }
+
+ @Test
+ fun containsExactlyVarargFailureExtra() {
+ val expected = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val actual = LinkedListMultimap.create(expected)
+ actual.put(4, "nine")
+ actual.put(5, "eight")
+ assertFailsWith<AssertionError> {
+ assertThat(actual)
+ .containsExactly(3 to "one", 3 to "six", 3 to "two", 4 to "five", 4 to "four")
+ }
+ }
+
+ @Test
+ fun containsExactlyVarargFailureBoth() {
+ val expected = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val actual = LinkedListMultimap.create(expected)
+ actual.remove(3, "six")
+ actual.remove(4, "five")
+ actual.put(4, "nine")
+ actual.put(5, "eight")
+ assertFailsWith<AssertionError> {
+ assertThat(actual)
+ .containsExactly(3 to "one", 3 to "six", 3 to "two", 4 to "five", 4 to "four")
+ }
+ }
+
+ @Test
+ fun containsExactlyVarargRespectsDuplicates() {
+ val actual = ImmutableListMultimap.of(3, "one", 3, "two", 3, "one", 4, "five", 4, "five")
+ assertThat(actual)
+ .containsExactly(3 to "two", 4 to "five", 3 to "one", 4 to "five", 3 to "one")
+ }
+
+ @Test
+ fun containsExactlyVarargRespectsDuplicatesFailure() {
+ val actual = ImmutableListMultimap.of(3, "one", 3, "two", 3, "one", 4, "five", 4, "five")
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsExactly(3 to "one", 3 to "two", 4 to "five")
+ }
+ }
+
+ @Test
+ fun containsExactlyVarargInOrder() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertThat(actual)
+ .containsExactly(3 to "one", 3 to "six", 3 to "two", 4 to "five", 4 to "four")
+ .inOrder()
+ }
+
+ @Test
+ fun containsExactlyVarargInOrderFailure() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertThat(actual)
+ .containsExactly(4 to "four", 3 to "six", 4 to "five", 3 to "two", 3 to "one")
+ assertFailsWith<AssertionError> {
+ assertThat(actual)
+ .containsExactly(4 to "four", 3 to "six", 4 to "five", 3 to "two", 3 to "one")
+ .inOrder()
+ }
+ }
+
+ @Test
+ fun containsExactlyVarargInOrderFailureValuesOnly() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertThat(actual)
+ .containsExactly(3 to "six", 3 to "two", 3 to "one", 4 to "five", 4 to "four")
+ assertFailsWith<AssertionError> {
+ assertThat(actual)
+ .containsExactly(3 to "six", 3 to "two", 3 to "one", 4 to "five", 4 to "four")
+ .inOrder()
+ }
+ }
+
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun containsExactlyEntriesIn_homogeneousMultimap_failsWithSameToString() {
+ assertFailsWith<AssertionError> {
+ assertThat<Any, Any>(ImmutableMultimap.of(1, "a", 1, "b", 2, "c"))
+ .containsExactlyEntriesIn(ImmutableMultimap.of(1L, "a", 1L, "b", 2L, "c"))
+ }
+ }
+
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun containsExactlyEntriesIn_heterogeneousMultimap_failsWithSameToString() {
+ assertFailsWith<AssertionError> {
+ assertThat<Any, Any>(ImmutableMultimap.of(1, "a", 1, "b", 2L, "c"))
+ .containsExactlyEntriesIn(ImmutableMultimap.of(1L, "a", 1L, "b", 2, "c"))
+ }
+ }
+
+ @Test
+ fun containsAtLeastEntriesIn() {
+ val actual = ImmutableListMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val expected = ImmutableSetMultimap.of(3, "one", 3, "six", 3, "two", 4, "five")
+ assertThat(actual).containsAtLeastEntriesIn(expected)
+ }
+
+ @Test
+ fun containsAtLeastEmpty() {
+ val actual = ImmutableListMultimap.of(3, "one")
+ val expected = ImmutableSetMultimap.of<Int, String>()
+ assertThat(actual).containsAtLeastEntriesIn(expected)
+ assertThat(actual).containsAtLeastEntriesIn(expected).inOrder()
+ }
+
+ @Test
+ fun containsAtLeastRejectsNull() {
+ val multimap = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertFailsWith<NullPointerException> {
+ assertThat(multimap).containsAtLeastEntriesIn(null)
+ }
+ }
+
+ @Test
+ fun containsAtLeastRespectsDuplicates() {
+ val actual = ImmutableListMultimap.of(3, "one", 3, "two", 3, "one", 4, "five", 4, "five")
+ val expected = ImmutableListMultimap.of(3, "two", 4, "five", 3, "one", 4, "five", 3, "one")
+ assertThat(actual).containsAtLeastEntriesIn(expected)
+ }
+
+ @Test
+ fun containsAtLeastRespectsDuplicatesFailure() {
+ val expected = ImmutableListMultimap.of(3, "one", 3, "two", 3, "one", 4, "five", 4, "five")
+ val actual = ImmutableSetMultimap.copyOf(expected)
+ assertFailsWith<AssertionError> { assertThat(actual).containsAtLeastEntriesIn(expected) }
+ }
+
+ @Test
+ fun containsAtLeastFailureMissing() {
+ val expected = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val actual = LinkedListMultimap.create(expected)
+ actual.remove(3, "six")
+ actual.remove(4, "five")
+ actual.put(50, "hawaii")
+ assertFailsWith<AssertionError> { assertThat(actual).containsAtLeastEntriesIn(expected) }
+ }
+
+ @Test
+ fun containsAtLeastFailureWithEmptyStringMissing() {
+ assertFailsWith<AssertionError> {
+ assertThat(ImmutableMultimap.of("key", "value")).containsAtLeast("" to "a")
+ }
+ }
+
+ @Test
+ fun containsAtLeastInOrder() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val expected = ImmutableMultimap.of(3, "one", 3, "six", 4, "five", 4, "four")
+ assertThat(actual).containsAtLeastEntriesIn(expected).inOrder()
+ }
+
+ @Test
+ fun containsAtLeastInOrderDifferentTypes() {
+ val actual = ImmutableListMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val expected = ImmutableSetMultimap.of(3, "one", 3, "six", 4, "five", 4, "four")
+ assertThat(actual).containsAtLeastEntriesIn(expected).inOrder()
+ }
+
+ @Test
+ fun containsAtLeastInOrderFailure() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val expected = ImmutableMultimap.of(4, "four", 3, "six", 3, "two", 3, "one")
+ assertThat(actual).containsAtLeastEntriesIn(expected)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsAtLeastEntriesIn(expected).inOrder()
+ }
+ }
+
+ @Test
+ fun containsAtLeastInOrderFailureValuesOnly() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val expected = ImmutableMultimap.of(3, "six", 3, "one", 4, "five", 4, "four")
+ assertThat(actual).containsAtLeastEntriesIn(expected)
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsAtLeastEntriesIn(expected).inOrder()
+ }
+ }
+
+ @Test
+ fun containsAtLeastVararg() {
+ val listMultimap = ImmutableListMultimap.of(1, "one", 3, "six", 3, "two", 3, "one")
+ assertThat(listMultimap).containsAtLeast(1 to "one", 3 to "six", 3 to "two")
+ }
+
+ @Test
+ fun containsAtLeastVarargWithNull() {
+ val listMultimap =
+ LinkedListMultimap.create(ImmutableListMultimap.of(1, "one", 3, "six", 3, "two"))
+ listMultimap.put(4, null)
+ assertThat(listMultimap).containsAtLeast(1 to "one", 3 to "two", 4 to null)
+ }
+
+ @Test
+ fun containsAtLeastVarargFailureMissing() {
+ val expected = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ val actual = LinkedListMultimap.create(expected)
+ actual.remove(3, "six")
+ actual.remove(4, "five")
+ actual.put(3, "nine")
+ assertFailsWith<AssertionError> {
+ assertThat(actual)
+ .containsAtLeast(3 to "one", 3 to "six", 3 to "two", 4 to "five", 4 to "four")
+ }
+ }
+
+ @Test
+ fun containsAtLeastVarargRespectsDuplicates() {
+ val actual = ImmutableListMultimap.of(3, "one", 3, "two", 3, "one", 4, "five", 4, "five")
+ assertThat(actual).containsAtLeast(3 to "two", 4 to "five", 3 to "one", 3 to "one")
+ }
+
+ @Test
+ fun containsAtLeastVarargRespectsDuplicatesFailure() {
+ val actual = ImmutableListMultimap.of(3, "one", 3, "two", 4, "five", 4, "five")
+ assertFailsWith<AssertionError> {
+ assertThat(actual).containsAtLeast(3 to "one", 3 to "one", 3 to "one", 4 to "five")
+ }
+ }
+
+ @Test
+ fun containsAtLeastVarargInOrder() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertThat(actual)
+ .containsAtLeast(3 to "one", 3 to "six", 4 to "five", 4 to "four")
+ .inOrder()
+ }
+
+ @Test
+ fun containsAtLeastVarargInOrderFailure() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertThat(actual).containsAtLeast(4 to "four", 3 to "six", 3 to "two", 3 to "one")
+ assertFailsWith<AssertionError> {
+ assertThat(actual)
+ .containsAtLeast(4 to "four", 3 to "six", 3 to "two", 3 to "one")
+ .inOrder()
+ }
+ }
+
+ @Test
+ fun containsAtLeastVarargInOrderFailureValuesOnly() {
+ val actual = ImmutableMultimap.of(3, "one", 3, "six", 3, "two", 4, "five", 4, "four")
+ assertThat(actual).containsAtLeast(3 to "two", 3 to "one", 4 to "five", 4 to "four")
+ assertFailsWith<AssertionError> {
+ assertThat(actual)
+ .containsAtLeast(3 to "two", 3 to "one", 4 to "five", 4 to "four")
+ .inOrder()
+ }
+ }
+}
diff --git a/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/MultisetSubjectTest.kt b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/MultisetSubjectTest.kt
new file mode 100644
index 0000000..971acc1
--- /dev/null
+++ b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/MultisetSubjectTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.kruth
+
+import com.google.common.collect.ImmutableMultiset
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class MultisetSubjectTest {
+
+ @Test
+ fun hasCount() {
+ val multiset = ImmutableMultiset.of("kurt", "kurt", "kluever")
+ assertThat(multiset).hasCount("kurt", 2)
+ assertThat(multiset).hasCount("kluever", 1)
+ assertThat(multiset).hasCount("alfred", 0)
+ assertWithMessage("name").that(multiset).hasCount("kurt", 2)
+ }
+
+ @Test
+ fun hasCountFail() {
+ val multiset = ImmutableMultiset.of("kurt", "kurt", "kluever")
+ assertFailsWith<AssertionError> {
+ assertThat(multiset).hasCount("kurt", 3)
+ }
+ }
+}
diff --git a/leanback/leanback/api/current.ignore b/leanback/leanback/api/current.ignore
deleted file mode 100644
index bd85f68..0000000
--- a/leanback/leanback/api/current.ignore
+++ /dev/null
@@ -1,23 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.leanback.widget.BaseGridView:
- Removed class androidx.leanback.widget.BaseGridView
-RemovedClass: androidx.leanback.widget.HorizontalGridView:
- Removed class androidx.leanback.widget.HorizontalGridView
-RemovedClass: androidx.leanback.widget.ItemAlignmentFacet:
- Removed class androidx.leanback.widget.ItemAlignmentFacet
-RemovedClass: androidx.leanback.widget.OnChildViewHolderSelectedListener:
- Removed class androidx.leanback.widget.OnChildViewHolderSelectedListener
-RemovedClass: androidx.leanback.widget.VerticalGridView:
- Removed class androidx.leanback.widget.VerticalGridView
-
-
-RemovedInterface: androidx.leanback.widget.FacetProvider:
- Removed class androidx.leanback.widget.FacetProvider
-RemovedInterface: androidx.leanback.widget.FacetProviderAdapter:
- Removed class androidx.leanback.widget.FacetProviderAdapter
-RemovedInterface: androidx.leanback.widget.OnChildLaidOutListener:
- Removed class androidx.leanback.widget.OnChildLaidOutListener
-RemovedInterface: androidx.leanback.widget.OnChildSelectedListener:
- Removed deprecated class androidx.leanback.widget.OnChildSelectedListener
-RemovedInterface: androidx.leanback.widget.ViewHolderTask:
- Removed class androidx.leanback.widget.ViewHolderTask
diff --git a/leanback/leanback/api/restricted_current.ignore b/leanback/leanback/api/restricted_current.ignore
deleted file mode 100644
index 3295f54..0000000
--- a/leanback/leanback/api/restricted_current.ignore
+++ /dev/null
@@ -1,25 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.leanback.widget.BaseGridView:
- Removed class androidx.leanback.widget.BaseGridView
-RemovedClass: androidx.leanback.widget.HorizontalGridView:
- Removed class androidx.leanback.widget.HorizontalGridView
-RemovedClass: androidx.leanback.widget.ItemAlignmentFacet:
- Removed class androidx.leanback.widget.ItemAlignmentFacet
-RemovedClass: androidx.leanback.widget.OnChildViewHolderSelectedListener:
- Removed class androidx.leanback.widget.OnChildViewHolderSelectedListener
-RemovedClass: androidx.leanback.widget.VerticalGridView:
- Removed class androidx.leanback.widget.VerticalGridView
-RemovedClass: androidx.leanback.widget.Visibility:
- Removed class androidx.leanback.widget.Visibility
-
-
-RemovedInterface: androidx.leanback.widget.FacetProvider:
- Removed class androidx.leanback.widget.FacetProvider
-RemovedInterface: androidx.leanback.widget.FacetProviderAdapter:
- Removed class androidx.leanback.widget.FacetProviderAdapter
-RemovedInterface: androidx.leanback.widget.OnChildLaidOutListener:
- Removed class androidx.leanback.widget.OnChildLaidOutListener
-RemovedInterface: androidx.leanback.widget.OnChildSelectedListener:
- Removed deprecated class androidx.leanback.widget.OnChildSelectedListener
-RemovedInterface: androidx.leanback.widget.ViewHolderTask:
- Removed class androidx.leanback.widget.ViewHolderTask
diff --git a/libraryversions.toml b/libraryversions.toml
index 4bc650d..a25b93e 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -1,5 +1,5 @@
[versions]
-ACTIVITY = "1.9.0-alpha03"
+ACTIVITY = "1.9.0-beta01"
ANNOTATION = "1.8.0-alpha01"
ANNOTATION_EXPERIMENTAL = "1.4.0-rc01"
APPACTIONS_BUILTINTYPES = "1.0.0-alpha01"
@@ -14,19 +14,19 @@
BLUETOOTH = "1.0.0-alpha02"
BROWSER = "1.9.0-alpha01"
BUILDSRC_TESTS = "1.0.0-alpha01"
-CAMERA = "1.4.0-alpha04"
+CAMERA = "1.4.0-beta01"
CAMERA_PIPE = "1.0.0-alpha01"
CAMERA_TESTING = "1.0.0-alpha01"
-CAMERA_VIEWFINDER = "1.4.0-alpha04"
+CAMERA_VIEWFINDER = "1.4.0-alpha05"
CAMERA_VIEWFINDER_COMPOSE = "1.0.0-alpha01"
CARDVIEW = "1.1.0-alpha01"
CAR_APP = "1.7.0-alpha01"
COLLECTION = "1.5.0-alpha01"
-COMPOSE = "1.7.0-alpha03"
+COMPOSE = "1.7.0-alpha04"
COMPOSE_COMPILER = "1.5.10" # Update when preparing for a release
-COMPOSE_MATERIAL3 = "1.3.0-alpha01"
-COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha07"
-COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = "1.0.0-alpha04"
+COMPOSE_MATERIAL3 = "1.3.0-alpha02"
+COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha08"
+COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = "1.0.0-alpha05"
COMPOSE_MATERIAL3_COMMON = "1.0.0-alpha01"
COMPOSE_RUNTIME_TRACING = "1.0.0-beta01"
CONSTRAINTLAYOUT = "2.2.0-alpha13"
@@ -34,7 +34,7 @@
CONSTRAINTLAYOUT_CORE = "1.1.0-alpha13"
CONTENTPAGER = "1.1.0-alpha01"
COORDINATORLAYOUT = "1.3.0-alpha02"
-CORE = "1.13.0-alpha05"
+CORE = "1.13.0-beta01"
CORE_ANIMATION = "1.0.0-rc01"
CORE_ANIMATION_TESTING = "1.0.0-rc01"
CORE_APPDIGEST = "1.0.0-alpha01"
@@ -48,12 +48,12 @@
CORE_SPLASHSCREEN = "1.1.0-alpha02"
CORE_TELECOM = "1.0.0-alpha02"
CORE_UWB = "1.0.0-alpha08"
-CREDENTIALS = "1.3.0-alpha01"
-CREDENTIALS_FIDO_QUARANTINE = "1.0.0-alpha01"
+CREDENTIALS = "1.3.0-alpha02"
+CREDENTIALS_FIDO_QUARANTINE = "1.0.0-alpha02"
CURSORADAPTER = "1.1.0-alpha01"
CUSTOMVIEW = "1.2.0-alpha03"
CUSTOMVIEW_POOLINGCONTAINER = "1.1.0-alpha01"
-DATASTORE = "1.1.0-beta01"
+DATASTORE = "1.1.0-beta02"
DOCUMENTFILE = "1.1.0-alpha02"
DRAGANDDROP = "1.1.0-alpha01"
DRAWERLAYOUT = "1.3.0-alpha01"
@@ -97,9 +97,9 @@
LINT = "1.0.0-alpha01"
LOADER = "1.2.0-alpha01"
MEDIA = "1.7.0-rc01"
-MEDIAROUTER = "1.7.0-beta01"
+MEDIAROUTER = "1.7.0-rc01"
METRICS = "1.0.0-beta02"
-NAVIGATION = "2.8.0-alpha03"
+NAVIGATION = "2.8.0-alpha04"
PAGING = "3.3.0-alpha03"
PALETTE = "1.1.0-alpha01"
PDF = "1.0.0-alpha01"
@@ -109,7 +109,7 @@
PRIVACYSANDBOX_ACTIVITY = "1.0.0-alpha01"
PRIVACYSANDBOX_ADS = "1.1.0-beta04"
PRIVACYSANDBOX_PLUGINS = "1.0.0-alpha03"
-PRIVACYSANDBOX_SDKRUNTIME = "1.0.0-alpha12"
+PRIVACYSANDBOX_SDKRUNTIME = "1.0.0-alpha13"
PRIVACYSANDBOX_TOOLS = "1.0.0-alpha07"
PRIVACYSANDBOX_UI = "1.0.0-alpha07"
PROFILEINSTALLER = "1.4.0-alpha01"
@@ -154,8 +154,8 @@
VIEWPAGER = "1.1.0-alpha02"
VIEWPAGER2 = "1.1.0-beta03"
WEAR = "1.4.0-alpha01"
-WEAR_COMPOSE = "1.4.0-alpha03"
-WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha18"
+WEAR_COMPOSE = "1.4.0-alpha04"
+WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha19"
WEAR_INPUT = "1.2.0-alpha03"
WEAR_INPUT_TESTING = "1.2.0-alpha03"
WEAR_ONGOING = "1.1.0-alpha02"
diff --git a/lifecycle/lifecycle-common/api/current.ignore b/lifecycle/lifecycle-common/api/current.ignore
deleted file mode 100644
index 2cde2c0..0000000
--- a/lifecycle/lifecycle-common/api/current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.lifecycle.Lifecycle.Event#valueOf(String) parameter #0:
- Attempted to change parameter name from name to value in method androidx.lifecycle.Lifecycle.Event.valueOf
-ParameterNameChange: androidx.lifecycle.Lifecycle.State#valueOf(String) parameter #0:
- Attempted to change parameter name from name to value in method androidx.lifecycle.Lifecycle.State.valueOf
diff --git a/lifecycle/lifecycle-common/api/current.txt b/lifecycle/lifecycle-common/api/current.txt
index 6a053e4..404bc70 100644
--- a/lifecycle/lifecycle-common/api/current.txt
+++ b/lifecycle/lifecycle-common/api/current.txt
@@ -21,13 +21,11 @@
}
public enum Lifecycle.Event {
- method public static final androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State state);
- method public static final androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State state);
- method public final androidx.lifecycle.Lifecycle.State getTargetState();
- method public static final androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State state);
- method public static final androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State state);
- method public static androidx.lifecycle.Lifecycle.Event valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.lifecycle.Lifecycle.Event[] values();
+ method public static androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State state);
+ method public static androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State state);
+ method public androidx.lifecycle.Lifecycle.State getTargetState();
+ method public static androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State state);
+ method public static androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State state);
property public final androidx.lifecycle.Lifecycle.State targetState;
enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_ANY;
enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_CREATE;
@@ -47,9 +45,7 @@
}
public enum Lifecycle.State {
- method public final boolean isAtLeast(androidx.lifecycle.Lifecycle.State state);
- method public static androidx.lifecycle.Lifecycle.State valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.lifecycle.Lifecycle.State[] values();
+ method public boolean isAtLeast(androidx.lifecycle.Lifecycle.State state);
enum_constant public static final androidx.lifecycle.Lifecycle.State CREATED;
enum_constant public static final androidx.lifecycle.Lifecycle.State DESTROYED;
enum_constant public static final androidx.lifecycle.Lifecycle.State INITIALIZED;
diff --git a/lifecycle/lifecycle-common/api/restricted_current.ignore b/lifecycle/lifecycle-common/api/restricted_current.ignore
deleted file mode 100644
index 2cde2c0..0000000
--- a/lifecycle/lifecycle-common/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.lifecycle.Lifecycle.Event#valueOf(String) parameter #0:
- Attempted to change parameter name from name to value in method androidx.lifecycle.Lifecycle.Event.valueOf
-ParameterNameChange: androidx.lifecycle.Lifecycle.State#valueOf(String) parameter #0:
- Attempted to change parameter name from name to value in method androidx.lifecycle.Lifecycle.State.valueOf
diff --git a/lifecycle/lifecycle-common/api/restricted_current.txt b/lifecycle/lifecycle-common/api/restricted_current.txt
index d11c33f..313f175 100644
--- a/lifecycle/lifecycle-common/api/restricted_current.txt
+++ b/lifecycle/lifecycle-common/api/restricted_current.txt
@@ -28,13 +28,11 @@
}
public enum Lifecycle.Event {
- method public static final androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State state);
- method public static final androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State state);
- method public final androidx.lifecycle.Lifecycle.State getTargetState();
- method public static final androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State state);
- method public static final androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State state);
- method public static androidx.lifecycle.Lifecycle.Event valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.lifecycle.Lifecycle.Event[] values();
+ method public static androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State state);
+ method public static androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State state);
+ method public androidx.lifecycle.Lifecycle.State getTargetState();
+ method public static androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State state);
+ method public static androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State state);
property public final androidx.lifecycle.Lifecycle.State targetState;
enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_ANY;
enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_CREATE;
@@ -54,9 +52,7 @@
}
public enum Lifecycle.State {
- method public final boolean isAtLeast(androidx.lifecycle.Lifecycle.State state);
- method public static androidx.lifecycle.Lifecycle.State valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.lifecycle.Lifecycle.State[] values();
+ method public boolean isAtLeast(androidx.lifecycle.Lifecycle.State state);
enum_constant public static final androidx.lifecycle.Lifecycle.State CREATED;
enum_constant public static final androidx.lifecycle.Lifecycle.State DESTROYED;
enum_constant public static final androidx.lifecycle.Lifecycle.State INITIALIZED;
diff --git a/lifecycle/lifecycle-common/build.gradle b/lifecycle/lifecycle-common/build.gradle
index b145a44..81e610e 100644
--- a/lifecycle/lifecycle-common/build.gradle
+++ b/lifecycle/lifecycle-common/build.gradle
@@ -21,7 +21,6 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import androidx.build.PlatformIdentifier
import androidx.build.Publish
import org.jetbrains.kotlin.konan.target.Family
@@ -30,9 +29,6 @@
id("AndroidXPlugin")
}
-def macEnabled = KmpPlatformsKt.enableMac(project)
-def linuxEnabled = KmpPlatformsKt.enableLinux(project)
-
androidXMultiplatform {
jvm {
withJava()
@@ -63,23 +59,17 @@
}
}
- if (macEnabled || linuxEnabled) {
- nativeMain {
- dependsOn(commonMain)
- dependencies {
- implementation(libs.atomicFu)
- }
+ nativeMain {
+ dependsOn(commonMain)
+ dependencies {
+ implementation(libs.atomicFu)
}
}
- if (macEnabled) {
- darwinMain {
- dependsOn(nativeMain)
- }
+ darwinMain {
+ dependsOn(nativeMain)
}
- if (linuxEnabled) {
- linuxMain {
- dependsOn(nativeMain)
- }
+ linuxMain {
+ dependsOn(nativeMain)
}
targets.all { target ->
diff --git a/lifecycle/lifecycle-livedata-core/api/current.ignore b/lifecycle/lifecycle-livedata-core/api/current.ignore
deleted file mode 100644
index 723ad63..0000000
--- a/lifecycle/lifecycle-livedata-core/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.lifecycle.Observer#onChanged(T) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter value in androidx.lifecycle.Observer.onChanged(T value)
diff --git a/lifecycle/lifecycle-livedata-core/api/restricted_current.ignore b/lifecycle/lifecycle-livedata-core/api/restricted_current.ignore
deleted file mode 100644
index 723ad63..0000000
--- a/lifecycle/lifecycle-livedata-core/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.lifecycle.Observer#onChanged(T) parameter #0:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter value in androidx.lifecycle.Observer.onChanged(T value)
diff --git a/lifecycle/lifecycle-livedata-ktx/api/current.ignore b/lifecycle/lifecycle-livedata-ktx/api/current.ignore
deleted file mode 100644
index 01ef7e3..0000000
--- a/lifecycle/lifecycle-livedata-ktx/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedPackage: androidx.lifecycle:
- Removed package androidx.lifecycle
diff --git a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.ignore b/lifecycle/lifecycle-livedata-ktx/api/restricted_current.ignore
deleted file mode 100644
index 01ef7e3..0000000
--- a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedPackage: androidx.lifecycle:
- Removed package androidx.lifecycle
diff --git a/lifecycle/lifecycle-runtime-compose/build.gradle b/lifecycle/lifecycle-runtime-compose/build.gradle
index e6d937c..4f63f24 100644
--- a/lifecycle/lifecycle-runtime-compose/build.gradle
+++ b/lifecycle/lifecycle-runtime-compose/build.gradle
@@ -21,9 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.Publish
-import androidx.build.RunApiTasks
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -51,10 +49,9 @@
androidx {
name = "Lifecycle Runtime Compose"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2021"
description = "Compose integration with Lifecycle"
- runApiTasks = new RunApiTasks.Yes()
metalavaK2UastEnabled = true
samples(projectOrArtifact(":lifecycle:lifecycle-runtime-compose:lifecycle-runtime-compose-samples"))
}
diff --git a/lifecycle/lifecycle-runtime-ktx/build.gradle b/lifecycle/lifecycle-runtime-ktx/build.gradle
index 27103c5..78fe658 100644
--- a/lifecycle/lifecycle-runtime-ktx/build.gradle
+++ b/lifecycle/lifecycle-runtime-ktx/build.gradle
@@ -22,11 +22,8 @@
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import androidx.build.PlatformIdentifier
import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.konan.target.Family
plugins {
id("AndroidXPlugin")
diff --git a/lifecycle/lifecycle-runtime/build.gradle b/lifecycle/lifecycle-runtime/build.gradle
index 115fc84..959a1b3 100644
--- a/lifecycle/lifecycle-runtime/build.gradle
+++ b/lifecycle/lifecycle-runtime/build.gradle
@@ -6,7 +6,6 @@
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import androidx.build.PlatformIdentifier
import androidx.build.Publish
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -16,10 +15,6 @@
id("AndroidXPlugin")
id("com.android.library")
}
-
-def macEnabled = KmpPlatformsKt.enableMac(project)
-def linuxEnabled = KmpPlatformsKt.enableLinux(project)
-
androidXMultiplatform {
android()
desktop()
@@ -86,33 +81,27 @@
}
}
- if (macEnabled || linuxEnabled) {
- nativeMain {
- dependsOn(commonMain)
+ nativeMain {
+ dependsOn(commonMain)
- // Required for WeakReference usage
- languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
- }
-
- nativeTest {
- dependsOn(commonTest)
- }
+ // Required for WeakReference usage
+ languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
}
- if (macEnabled) {
- darwinMain {
- dependsOn(nativeMain)
- // Required for WeakReference usage
- languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
- }
+ nativeTest {
+ dependsOn(commonTest)
}
- if (linuxEnabled) {
- linuxMain {
- dependsOn(nativeMain)
+ darwinMain {
+ dependsOn(nativeMain)
- // Required for WeakReference usage
- languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
- }
+ // Required for WeakReference usage
+ languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
+ }
+ linuxMain {
+ dependsOn(nativeMain)
+
+ // Required for WeakReference usage
+ languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi")
}
targets.all { target ->
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore b/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore
deleted file mode 100644
index 41a6e11..0000000
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.lifecycle.SavedStateHandle#getLiveData(String, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter initialValue in androidx.lifecycle.SavedStateHandle.getLiveData(String key, T initialValue)
-InvalidNullConversion: androidx.lifecycle.SavedStateHandle#getStateFlow(String, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter initialValue in androidx.lifecycle.SavedStateHandle.getStateFlow(String key, T initialValue)
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.ignore b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.ignore
deleted file mode 100644
index 41a6e11..0000000
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.lifecycle.SavedStateHandle#getLiveData(String, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter initialValue in androidx.lifecycle.SavedStateHandle.getLiveData(String key, T initialValue)
-InvalidNullConversion: androidx.lifecycle.SavedStateHandle#getStateFlow(String, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter initialValue in androidx.lifecycle.SavedStateHandle.getStateFlow(String key, T initialValue)
diff --git a/lifecycle/lifecycle-viewmodel/api/api_lint.ignore b/lifecycle/lifecycle-viewmodel/api/api_lint.ignore
index ee654ad..14bafae 100644
--- a/lifecycle/lifecycle-viewmodel/api/api_lint.ignore
+++ b/lifecycle/lifecycle-viewmodel/api/api_lint.ignore
@@ -1,15 +1,7 @@
// Baseline format: 1.0
-MissingGetterMatchingBuilder: androidx.lifecycle.viewmodel.InitializerViewModelFactoryBuilder#addInitializer(kotlin.reflect.KClass<T>, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends T>):
- androidx.lifecycle.ViewModelProvider.Factory does not declare a `getInitializers()` method matching method androidx.lifecycle.viewmodel.InitializerViewModelFactoryBuilder.addInitializer(kotlin.reflect.KClass<T>,kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends T>)
-
-
MissingJvmstatic: androidx.lifecycle.ViewModelProvider.NewInstanceFactory#instance:
Companion object constants like instance should be using @JvmField, not @JvmStatic; see https://developer.android.com/kotlin/interop#companion_constants
-SetterReturnsThis: androidx.lifecycle.viewmodel.InitializerViewModelFactoryBuilder#addInitializer(kotlin.reflect.KClass<T>, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends T>):
- Methods must return the builder object (return type androidx.lifecycle.viewmodel.InitializerViewModelFactoryBuilder instead of void): method androidx.lifecycle.viewmodel.InitializerViewModelFactoryBuilder.addInitializer(kotlin.reflect.KClass<T>,kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends T>)
-
-
TopLevelBuilder: androidx.lifecycle.viewmodel.InitializerViewModelFactoryBuilder:
Builder should be defined as inner class: androidx.lifecycle.viewmodel.InitializerViewModelFactoryBuilder
diff --git a/lifecycle/lifecycle-viewmodel/api/current.ignore b/lifecycle/lifecycle-viewmodel/api/current.ignore
deleted file mode 100644
index 68d0b3a..0000000
--- a/lifecycle/lifecycle-viewmodel/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.lifecycle.viewmodel.MutableCreationExtras#set(androidx.lifecycle.viewmodel.CreationExtras.Key<T>, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter t in androidx.lifecycle.viewmodel.MutableCreationExtras.set(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key, T t)
diff --git a/lifecycle/lifecycle-viewmodel/api/current.txt b/lifecycle/lifecycle-viewmodel/api/current.txt
index 642c6facc..440b09e 100644
--- a/lifecycle/lifecycle-viewmodel/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/current.txt
@@ -41,8 +41,13 @@
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras defaultCreationExtras);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner owner);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory);
+ method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
+ method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
method @MainThread public operator <T extends androidx.lifecycle.ViewModel> T get(Class<T> modelClass);
method @MainThread public operator <T extends androidx.lifecycle.ViewModel> T get(String key, Class<T> modelClass);
+ method @MainThread public final operator <T extends androidx.lifecycle.ViewModel> T get(String key, kotlin.reflect.KClass<T> modelClass);
+ method @MainThread public final operator <T extends androidx.lifecycle.ViewModel> T get(kotlin.reflect.KClass<T> modelClass);
+ field public static final androidx.lifecycle.ViewModelProvider.Companion Companion;
}
public static class ViewModelProvider.AndroidViewModelFactory extends androidx.lifecycle.ViewModelProvider.NewInstanceFactory {
@@ -57,9 +62,15 @@
method public androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory getInstance(android.app.Application application);
}
+ public static final class ViewModelProvider.Companion {
+ method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
+ method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
+ }
+
public static interface ViewModelProvider.Factory {
method public default <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
method public default <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass, androidx.lifecycle.viewmodel.CreationExtras extras);
+ method public default <T extends androidx.lifecycle.ViewModel> T create(kotlin.reflect.KClass<T> modelClass, androidx.lifecycle.viewmodel.CreationExtras extras);
method public static androidx.lifecycle.ViewModelProvider.Factory from(androidx.lifecycle.viewmodel.ViewModelInitializer<?>... initializers);
field public static final androidx.lifecycle.ViewModelProvider.Factory.Companion Companion;
}
@@ -138,6 +149,7 @@
public final class ViewModelInitializer<T extends androidx.lifecycle.ViewModel> {
ctor public ViewModelInitializer(Class<T> clazz, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends T> initializer);
+ ctor public ViewModelInitializer(kotlin.reflect.KClass<T> clazz, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends T> initializer);
}
}
diff --git a/lifecycle/lifecycle-viewmodel/api/restricted_current.ignore b/lifecycle/lifecycle-viewmodel/api/restricted_current.ignore
deleted file mode 100644
index 68d0b3a..0000000
--- a/lifecycle/lifecycle-viewmodel/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.lifecycle.viewmodel.MutableCreationExtras#set(androidx.lifecycle.viewmodel.CreationExtras.Key<T>, T) parameter #1:
- Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter t in androidx.lifecycle.viewmodel.MutableCreationExtras.set(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key, T t)
diff --git a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
index 642c6facc..440b09e 100644
--- a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
@@ -41,8 +41,13 @@
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras defaultCreationExtras);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner owner);
ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory);
+ method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
+ method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
method @MainThread public operator <T extends androidx.lifecycle.ViewModel> T get(Class<T> modelClass);
method @MainThread public operator <T extends androidx.lifecycle.ViewModel> T get(String key, Class<T> modelClass);
+ method @MainThread public final operator <T extends androidx.lifecycle.ViewModel> T get(String key, kotlin.reflect.KClass<T> modelClass);
+ method @MainThread public final operator <T extends androidx.lifecycle.ViewModel> T get(kotlin.reflect.KClass<T> modelClass);
+ field public static final androidx.lifecycle.ViewModelProvider.Companion Companion;
}
public static class ViewModelProvider.AndroidViewModelFactory extends androidx.lifecycle.ViewModelProvider.NewInstanceFactory {
@@ -57,9 +62,15 @@
method public androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory getInstance(android.app.Application application);
}
+ public static final class ViewModelProvider.Companion {
+ method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
+ method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
+ }
+
public static interface ViewModelProvider.Factory {
method public default <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
method public default <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass, androidx.lifecycle.viewmodel.CreationExtras extras);
+ method public default <T extends androidx.lifecycle.ViewModel> T create(kotlin.reflect.KClass<T> modelClass, androidx.lifecycle.viewmodel.CreationExtras extras);
method public static androidx.lifecycle.ViewModelProvider.Factory from(androidx.lifecycle.viewmodel.ViewModelInitializer<?>... initializers);
field public static final androidx.lifecycle.ViewModelProvider.Factory.Companion Companion;
}
@@ -138,6 +149,7 @@
public final class ViewModelInitializer<T extends androidx.lifecycle.ViewModel> {
ctor public ViewModelInitializer(Class<T> clazz, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends T> initializer);
+ ctor public ViewModelInitializer(kotlin.reflect.KClass<T> clazz, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends T> initializer);
}
}
diff --git a/lifecycle/lifecycle-viewmodel/build.gradle b/lifecycle/lifecycle-viewmodel/build.gradle
index 13d4549..6e073f9 100644
--- a/lifecycle/lifecycle-viewmodel/build.gradle
+++ b/lifecycle/lifecycle-viewmodel/build.gradle
@@ -21,6 +21,8 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
+import androidx.build.KmpPlatformsKt
import androidx.build.PlatformIdentifier
import androidx.build.Publish
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
@@ -30,8 +32,16 @@
id("com.android.library")
}
+def macEnabled = KmpPlatformsKt.enableMac(project)
+def linuxEnabled = KmpPlatformsKt.enableLinux(project)
+
androidXMultiplatform {
android()
+ desktop()
+ mac()
+ linux()
+ ios()
+
kotlin {
explicitApi = ExplicitApiMode.Strict
}
@@ -41,32 +51,43 @@
sourceSets {
commonMain {
dependencies {
- // TODO(b/214568825): migrate from `androidMain` to here.
+ api(project(":annotation:annotation"))
+ api(libs.kotlinStdlib)
+ api(libs.kotlinCoroutinesCore)
+ implementation(libs.atomicFu)
}
}
commonTest {
dependencies {
- // TODO(b/214568825): migrate from `androidUnitTest` to here.
+ implementation(project(":kruth:kruth"))
+ implementation(libs.kotlinTest)
+ implementation(libs.kotlinCoroutinesTest)
+ }
+ }
+
+ jvmMain {
+ dependsOn(commonMain)
+ }
+
+ jvmTest {
+ dependsOn(commonTest)
+ dependencies {
+ implementation(libs.junit)
}
}
androidMain {
- dependsOn(commonMain)
+ dependsOn(jvmMain)
dependencies {
- api(project(":annotation:annotation"))
- api(libs.kotlinStdlib)
api(libs.kotlinCoroutinesAndroid)
}
}
androidUnitTest {
- dependsOn(commonTest)
+ dependsOn(jvmTest)
dependencies {
- implementation(libs.junit)
implementation(libs.mockitoCore4)
- implementation(libs.kotlinTest)
- implementation(libs.truth)
}
}
@@ -74,14 +95,53 @@
dependsOn(commonTest)
dependencies {
implementation("androidx.core:core-ktx:1.2.0")
- implementation(libs.truth)
- implementation(libs.kotlinStdlib)
- implementation(libs.junit)
implementation(libs.testExtJunit)
implementation(libs.testCore)
implementation(libs.testRunner)
}
}
+
+ desktopMain {
+ dependsOn(jvmMain)
+ }
+
+ if (macEnabled || linuxEnabled) {
+ nativeMain {
+ dependsOn(commonMain)
+ }
+
+ nativeTest {
+ dependsOn(commonTest)
+ }
+ }
+ if (macEnabled) {
+ darwinMain {
+ dependsOn(nativeMain)
+ }
+ }
+ if (linuxEnabled) {
+ linuxMain {
+ dependsOn(nativeMain)
+ }
+ }
+
+ targets.all { target ->
+ if (target.platformType == org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.native) {
+ target.compilations["main"].defaultSourceSet {
+ def konanTargetFamily = target.konanTarget.family
+ if (konanTargetFamily == org.jetbrains.kotlin.konan.target.Family.OSX || konanTargetFamily == org.jetbrains.kotlin.konan.target.Family.IOS) {
+ dependsOn(darwinMain)
+ } else if (konanTargetFamily == org.jetbrains.kotlin.konan.target.Family.LINUX) {
+ dependsOn(linuxMain)
+ } else {
+ throw new GradleException("unknown native target ${target}")
+ }
+ }
+ target.compilations["test"].defaultSourceSet {
+ dependsOn(nativeTest)
+ }
+ }
+ }
}
}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/AndroidViewModelFactoryTest.kt b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/AndroidViewModelFactoryTest.kt
index 543d342..1ee6227 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/AndroidViewModelFactoryTest.kt
+++ b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/AndroidViewModelFactoryTest.kt
@@ -17,13 +17,13 @@
package androidx.lifecycle
import android.app.Application
+import androidx.kruth.assertThat
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/CreationExtrasTest.kt b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/CreationExtrasTest.kt
index f480a9a..87855fe 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/CreationExtrasTest.kt
+++ b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/CreationExtrasTest.kt
@@ -18,11 +18,11 @@
import android.os.Bundle
import androidx.core.os.bundleOf
+import androidx.kruth.assertThat
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelTest.kt b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelScopeTest.kt
similarity index 77%
rename from lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelTest.kt
rename to lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelScopeTest.kt
index 3bbfbf6..be3a1c2 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelTest.kt
+++ b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewModelScopeTest.kt
@@ -16,9 +16,9 @@
package androidx.lifecycle
+import androidx.kruth.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth
import kotlinx.coroutines.async
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
@@ -29,35 +29,39 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
-class ViewModelTest {
+class ViewModelScopeTest {
- @Test fun testVmScope() {
+ @Test
+ fun testVmScope() {
val vm = object : ViewModel() {}
val job1 = vm.viewModelScope.launch { delay(1000) }
val job2 = vm.viewModelScope.launch { delay(1000) }
vm.clear()
- Truth.assertThat(job1.isCancelled).isTrue()
- Truth.assertThat(job2.isCancelled).isTrue()
+ assertThat(job1.isCancelled).isTrue()
+ assertThat(job2.isCancelled).isTrue()
}
- @Test fun testStartJobInClearedVM() {
+ @Test
+ fun testStartJobInClearedVM() {
val vm = object : ViewModel() {}
vm.clear()
val job1 = vm.viewModelScope.launch { delay(1000) }
- Truth.assertThat(job1.isCancelled).isTrue()
+ assertThat(job1.isCancelled).isTrue()
}
- @Test fun testSameScope() {
+ @Test
+ fun testSameScope() {
val vm = object : ViewModel() {}
val scope1 = vm.viewModelScope
val scope2 = vm.viewModelScope
- Truth.assertThat(scope1).isSameInstanceAs(scope2)
+ assertThat(scope1).isSameInstanceAs(scope2)
vm.clear()
val scope3 = vm.viewModelScope
- Truth.assertThat(scope3).isSameInstanceAs(scope2)
+ assertThat(scope3).isSameInstanceAs(scope2)
}
- @Test fun testJobIsSuperVisor() {
+ @Test
+ fun testJobIsSuperVisor() {
val vm = object : ViewModel() {}
val scope = vm.viewModelScope
val delayingDeferred = scope.async { delay(Long.MAX_VALUE) }
@@ -68,7 +72,7 @@
failingDeferred.await()
} catch (e: Error) {
}
- Truth.assertThat(delayingDeferred.isActive).isTrue()
+ assertThat(delayingDeferred.isActive).isTrue()
delayingDeferred.cancelAndJoin()
}
}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwnerTest.kt b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwnerTest.kt
index 6424d49..695d591 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwnerTest.kt
+++ b/lifecycle/lifecycle-viewmodel/src/androidInstrumentedTest/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwnerTest.kt
@@ -18,10 +18,10 @@
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
+import androidx.kruth.assertWithMessage
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/AndroidViewModel.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/AndroidViewModel.android.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/AndroidViewModel.kt
rename to lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/AndroidViewModel.android.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModel.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModel.kt
deleted file mode 100644
index 5698608..0000000
--- a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModel.kt
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.lifecycle
-
-import androidx.annotation.MainThread
-import java.io.Closeable
-import java.io.IOException
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-
-/**
- * ViewModel is a class that is responsible for preparing and managing the data for
- * an [Activity][android.app.Activity] or a [Fragment][androidx.fragment.app.Fragment].
- * It also handles the communication of the Activity / Fragment with the rest of the application
- * (e.g. calling the business logic classes).
- *
- * A ViewModel is always created in association with a scope (a fragment or an activity) and will
- * be retained as long as the scope is alive. E.g. if it is an Activity, until it is finished.
- *
- * In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a
- * configuration change (e.g. rotation). The new owner instance just re-connects to the existing
- * model.
- *
- * The purpose of the ViewModel is to acquire and keep the information that is necessary for an
- * Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the
- * ViewModel. ViewModels usually expose this information via [Lifecycle][androidx.lifecycle.LiveData] or
- * Android Data Binding. You can also use any observability construct from your favorite framework.
- *
- * ViewModel's only responsibility is to manage the data for the UI. It **should never** access
- * your view hierarchy or hold a reference back to the Activity or the Fragment.
- *
- * Typical usage from an Activity standpoint would be:
- *
- * ```
- * class UserActivity : ComponentActivity {
- * private val viewModel by viewModels<UserViewModel>()
- *
- * override fun onCreate(savedInstanceState: Bundle) {
- * super.onCreate(savedInstanceState)
- * setContentView(R.layout.user_activity_layout)
- * viewModel.user.observe(this) { user: User ->
- * // update ui.
- * }
- * requireViewById(R.id.button).setOnClickListener {
- * viewModel.doAction()
- * }
- * }
- * }
- * ```
- *
- * ViewModel would be:
- *
- * ```
- * class UserViewModel : ViewModel {
- * private val userLiveData = MutableLiveData<User>()
- * val user: LiveData<User> get() = userLiveData
- *
- * init {
- * // trigger user load.
- * }
- *
- * fun doAction() {
- * // depending on the action, do necessary business logic calls and update the
- * // userLiveData.
- * }
- * }
- * ```
- *
- * ViewModels can also be used as a communication layer between different Fragments of an Activity.
- * Each Fragment can acquire the ViewModel using the same key via their Activity. This allows
- * communication between Fragments in a de-coupled fashion such that they never need to talk to
- * the other Fragment directly.
- *
- * ```
- * class MyFragment : Fragment {
- * val viewModel by activityViewModels<UserViewModel>()
- * }
- *```
- */
-public abstract class ViewModel {
-
- /**
- * Holds a mapping between [String] keys and [AutoCloseable] resources that have been associated
- * with this [ViewModel].
- *
- * The associated resources will be [AutoCloseable.close] right before the [ViewModel.onCleared]
- * is called. This provides automatic resource cleanup upon [ViewModel] release.
- *
- * The clearing order is:
- * 1. [keyToCloseables][AutoCloseable.close]
- * 2. [closeables][Closeable.close]
- * 3. [ViewModel.onCleared]
- *
- * Compatibility considerations:
- * - **Why manual synchronization?** [java.util.concurrent.ConcurrentHashMap] can cause value
- * loss on the Android API 21 and 22. Using a regular map and controlling access with
- * [synchronized] guarantees reliable cleanup even on older devices. See b/37042460.
- * - **Why nullable collections?** Since [clear] is final, this method is still called on mock
- * objects. In those cases, [bagOfTags] is `null`. It'll always be empty though because
- * [addCloseable] and [getCloseable] are open so we can skip clearing it.
- */
- @Suppress("RedundantNullableReturnType")
- private val bagOfTags: MutableMap<String, AutoCloseable>? = mutableMapOf()
-
- /**
- * @see [bagOfTags]
- */
- @Suppress("RedundantNullableReturnType")
- private val closeables: MutableSet<AutoCloseable>? = mutableSetOf()
-
- @Volatile
- private var isCleared = false
-
- /**
- * Construct a new ViewModel instance.
- *
- * You should **never** manually construct a ViewModel outside of a
- * [ViewModelProvider.Factory].
- */
- public constructor()
-
- /**
- * Construct a new ViewModel instance. Any [AutoCloseable] objects provided here
- * will be closed directly before [ViewModel.onCleared] is called.
- *
- * You should **never** manually construct a ViewModel outside of a
- * [ViewModelProvider.Factory].
- */
- @Deprecated(message = "Replaced by `AutoCloseable` overload.", level = DeprecationLevel.HIDDEN)
- public constructor(vararg closeables: Closeable) {
- if (this.closeables != null) {
- this.closeables += closeables
- }
- }
-
- /**
- * Construct a new ViewModel instance. Any [AutoCloseable] objects provided here
- * will be closed directly before [ViewModel.onCleared] is called.
- *
- * You should **never** manually construct a ViewModel outside of a
- * [ViewModelProvider.Factory].
- */
- public constructor(vararg closeables: AutoCloseable) {
- if (this.closeables != null) {
- this.closeables += closeables
- }
- }
-
- /**
- * This method will be called when this ViewModel is no longer used and will be destroyed.
- *
- * It is useful when ViewModel observes some data and you need to clear this subscription to
- * prevent a leak of this ViewModel.
- */
- protected open fun onCleared() {}
-
- @MainThread
- internal fun clear() {
- isCleared = true
- if (bagOfTags != null) {
- synchronized(bagOfTags) {
- for (value in bagOfTags.values) {
- // see comment for the similar call in `setTagIfAbsent`
- closeWithRuntimeException(value)
- }
- }
- }
- if (closeables != null) {
- synchronized(closeables) {
- for (closeable in closeables) {
- closeWithRuntimeException(closeable)
- }
- }
- closeables.clear()
- }
- onCleared()
- }
-
- /**
- * Add a new [AutoCloseable] object that will be closed directly before
- * [ViewModel.onCleared] is called.
- *
- * If `onCleared()` has already been called, the closeable will not be added,
- * and will instead be closed immediately.
- *
- * @param key A key that allows you to retrieve the closeable passed in by using the same
- * key with [ViewModel.getCloseable]
- * @param closeable The object that should be [AutoCloseable.close] directly before
- * [ViewModel.onCleared] is called.
- */
- public fun addCloseable(key: String, closeable: AutoCloseable) {
- // Although no logic should be done after user calls onCleared(), we will
- // ensure that if it has already been called, the closeable attempting to
- // be added will be closed immediately to ensure there will be no leaks.
- if (isCleared) {
- closeWithRuntimeException(closeable)
- return
- }
-
- if (bagOfTags != null) {
- synchronized(bagOfTags) { bagOfTags.put(key, closeable) }
- }
- }
-
- /**
- * Add a new [AutoCloseable] object that will be closed directly before
- * [ViewModel.onCleared] is called.
- *
- * If `onCleared()` has already been called, the closeable will not be added,
- * and will instead be closed immediately.
- *
- * @param closeable The object that should be [closed][AutoCloseable.close] directly before
- * [ViewModel.onCleared] is called.
- */
- public open fun addCloseable(closeable: AutoCloseable) {
- // Although no logic should be done after user calls onCleared(), we will
- // ensure that if it has already been called, the closeable attempting to
- // be added will be closed immediately to ensure there will be no leaks.
- if (isCleared) {
- closeWithRuntimeException(closeable)
- return
- }
-
- if (this.closeables != null) {
- synchronized(this.closeables) {
- this.closeables += closeable
- }
- }
- }
-
- /**
- * Add a new [Closeable] object that will be closed directly before
- * [ViewModel.onCleared] is called.
- *
- * If `onCleared()` has already been called, the closeable will not be added,
- * and will instead be closed immediately.
- *
- * @param closeable The object that should be [closed][Closeable.close] directly before
- * [ViewModel.onCleared] is called.
- */
- @Deprecated(message = "Replaced by `AutoCloseable` overload.", level = DeprecationLevel.HIDDEN)
- public open fun addCloseable(closeable: Closeable) {
- // Upcast to `AutoCloseable` to avoid recursive call.
- addCloseable(closeable as AutoCloseable)
- }
-
- /**
- * Returns the closeable previously added with [ViewModel.addCloseable] with the given [key].
- *
- * @param key The key that was used to add the Closeable.
- */
- public fun <T : AutoCloseable> getCloseable(key: String): T? {
- if (bagOfTags == null) {
- return null
- }
- synchronized(bagOfTags) {
- @Suppress("UNCHECKED_CAST")
- return bagOfTags[key] as T?
- }
- }
-
- private fun closeWithRuntimeException(instance: Any) {
- if (instance is Closeable) {
- try {
- instance.close()
- } catch (e: IOException) {
- throw RuntimeException(e)
- }
- }
- }
-}
-
-private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
-
-/**
- * [CoroutineScope] tied to this [ViewModel].
- * This scope will be canceled when ViewModel will be cleared, i.e. [ViewModel.onCleared] is called
- *
- * This scope is bound to
- * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
- */
-public val ViewModel.viewModelScope: CoroutineScope
- get() {
- return getCloseable<CloseableCoroutineScope>(JOB_KEY) ?: CloseableCoroutineScope(
- SupervisorJob() + Dispatchers.Main.immediate
- ).also { newClosableScope ->
- addCloseable(JOB_KEY, newClosableScope)
- }
- }
-
-private class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
- override val coroutineContext: CoroutineContext = context
-
- override fun close() {
- coroutineContext.cancel()
- }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.android.kt
similarity index 60%
rename from lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.kt
rename to lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.android.kt
index 4c4761b..8b0e24b 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.kt
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.android.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,97 +13,47 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:JvmName("ViewModelProviderGetKt")
+@file:JvmName("ViewModelProvider")
package androidx.lifecycle
import android.app.Application
import androidx.annotation.MainThread
import androidx.annotation.RestrictTo
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.DEFAULT_KEY
-import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.defaultFactory
-import androidx.lifecycle.ViewModelProvider.NewInstanceFactory.Companion.VIEW_MODEL_KEY
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.CreationExtras.Key
import androidx.lifecycle.viewmodel.InitializerViewModelFactory
-import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.lifecycle.viewmodel.ViewModelInitializer
-import java.lang.IllegalArgumentException
-import java.lang.RuntimeException
+import androidx.lifecycle.viewmodel.ViewModelProviderImpl
+import androidx.lifecycle.viewmodel.internal.JvmViewModelProviders
+import androidx.lifecycle.viewmodel.internal.ViewModelProviders
import java.lang.reflect.InvocationTargetException
-import kotlin.UnsupportedOperationException
+import kotlin.reflect.KClass
-/**
- * A utility class that provides `ViewModels` for a scope.
- *
- * Default `ViewModelProvider` for an `Activity` or a `Fragment` can be obtained
- * by passing it to the constructor: `ViewModelProvider(myFragment)`
- */
-public open class ViewModelProvider
-/**
- * Creates a ViewModelProvider
- *
- * @param store `ViewModelStore` where ViewModels will be stored.
- * @param factory factory a `Factory` which will be used to instantiate new `ViewModels`
- * @param defaultCreationExtras extras to pass to a factory
- */
-@JvmOverloads
-constructor(
- private val store: ViewModelStore,
- private val factory: Factory,
- private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
+public actual open class ViewModelProvider private constructor(
+ private val impl: ViewModelProviderImpl,
) {
- /**
- * Implementations of `Factory` interface are responsible to instantiate ViewModels.
- */
- public interface Factory {
- /**
- * Creates a new instance of the given `Class`.
- *
- * Default implementation throws [UnsupportedOperationException].
- *
- * @param modelClass a `Class` whose instance is requested
- * @return a newly created ViewModel
- */
- public fun <T : ViewModel> create(modelClass: Class<T>): T {
- throw UnsupportedOperationException(
- "Factory.create(String) is unsupported. This Factory requires " +
- "`CreationExtras` to be passed into `create` method."
- )
- }
-
- /**
- * Creates a new instance of the given `Class`.
- *
- * @param modelClass a `Class` whose instance is requested
- * @param extras an additional information for this creation request
- * @return a newly created ViewModel
- */
- public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =
- create(modelClass)
-
- public companion object {
- /**
- * Creates an [InitializerViewModelFactory] using the given initializers.
- *
- * @param initializers the class initializer pairs used for the factory to create
- * simple view models
- */
- @JvmStatic
- public fun from(vararg initializers: ViewModelInitializer<*>): Factory =
- InitializerViewModelFactory(*initializers)
- }
- }
-
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public open class OnRequeryFactory {
- public open fun onRequery(viewModel: ViewModel) {}
- }
/**
- * Creates `ViewModelProvider`. This will create `ViewModels`
- * and retain them in a store of the given `ViewModelStoreOwner`.
+ * Creates a [ViewModelProvider]. This provider generates [ViewModel] instances using the
+ * specified [Factory] and stores them within the [ViewModelStore] of the provided
+ * [ViewModelStoreOwner].
*
+ * @param store `ViewModelStore` where ViewModels will be stored.
+ * @param factory The [Factory] responsible for creating new [ViewModel] instances.
+ * @param defaultCreationExtras Additional data to be passed to the [Factory] during
+ * [ViewModel] creation.
+ */
+ @JvmOverloads
+ public constructor(
+ store: ViewModelStore,
+ factory: Factory,
+ defaultCreationExtras: CreationExtras = CreationExtras.Empty,
+ ) : this(ViewModelProviderImpl(store, factory, defaultCreationExtras))
+
+ /**
+ * Creates [ViewModelProvider]. This will create [ViewModel] instances and retain them in the
+ * [ViewModelStore] of the given [ViewModelStoreOwner].
*
* This method will use the
* [default factory][HasDefaultViewModelProviderFactory.defaultViewModelProviderFactory]
@@ -111,24 +61,35 @@
* [NewInstanceFactory] will be used.
*/
public constructor(
- owner: ViewModelStoreOwner
- ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
+ owner: ViewModelStoreOwner,
+ ) : this(
+ store = owner.viewModelStore,
+ factory = ViewModelProviders.getDefaultFactory(owner),
+ defaultCreationExtras = ViewModelProviders.getDefaultCreationExtras(owner)
+ )
/**
- * Creates `ViewModelProvider`, which will create `ViewModels` via the given
- * `Factory` and retain them in a store of the given `ViewModelStoreOwner`.
+ * Creates a [ViewModelProvider]. This provider generates [ViewModel] instances using the
+ * specified [Factory] and stores them within the [ViewModelStore] of the provided
+ * [ViewModelStoreOwner].
*
- * @param owner a `ViewModelStoreOwner` whose [ViewModelStore] will be used to
- * retain `ViewModels`
- * @param factory a `Factory` which will be used to instantiate
- * new `ViewModels`
+ * @param owner The [ViewModelStoreOwner] that will manage the lifecycle of the created
+ * [ViewModel] instances.
+ * @param factory The [Factory] responsible for creating new [ViewModel] instances.
*/
- public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
- owner.viewModelStore,
- factory,
- defaultCreationExtras(owner)
+ public constructor(
+ owner: ViewModelStoreOwner,
+ factory: Factory,
+ ) : this(
+ store = owner.viewModelStore,
+ factory = factory,
+ defaultCreationExtras = ViewModelProviders.getDefaultCreationExtras(owner)
)
+ @MainThread
+ public actual operator fun <T : ViewModel> get(modelClass: KClass<T>): T =
+ impl.getViewModel(modelClass)
+
/**
* Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
* an activity), associated with this `ViewModelProvider`.
@@ -144,11 +105,12 @@
* @throws IllegalArgumentException if the given [modelClass] is local or anonymous class.
*/
@MainThread
- public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
- val canonicalName = modelClass.canonicalName
- ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
- return get("$DEFAULT_KEY:$canonicalName", modelClass)
- }
+ public open operator fun <T : ViewModel> get(modelClass: Class<T>): T =
+ get(modelClass.kotlin)
+
+ @MainThread
+ public actual operator fun <T : ViewModel> get(key: String, modelClass: KClass<T>): T =
+ impl.getViewModel(modelClass, key)
/**
* Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
@@ -163,51 +125,85 @@
* present.
* @return A ViewModel that is an instance of the given type `T`.
*/
- @Suppress("UNCHECKED_CAST")
@MainThread
- public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
- val viewModel = store[key]
- if (modelClass.isInstance(viewModel)) {
- (factory as? OnRequeryFactory)?.onRequery(viewModel!!)
- return viewModel as T
- } else {
- @Suppress("ControlFlowWithEmptyBody")
- if (viewModel != null) {
- // TODO: log a warning.
- }
+ public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T =
+ impl.getViewModel(modelClass.kotlin, key)
+
+ public actual interface Factory {
+
+ /**
+ * Creates a new instance of the given `Class`.
+ *
+ * Default implementation throws [UnsupportedOperationException].
+ * ˆ
+ * @param modelClass a `Class` whose instance is requested
+ * @return a newly created ViewModel
+ */
+ public fun <T : ViewModel> create(modelClass: Class<T>): T =
+ ViewModelProviders.unsupportedCreateViewModel()
+
+ /**
+ * Creates a new instance of the given `Class`.
+ *
+ * @param modelClass a `Class` whose instance is requested
+ * @param extras an additional information for this creation request
+ * @return a newly created ViewModel
+ */
+ public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =
+ create(modelClass)
+
+ public actual fun <T : ViewModel> create(
+ modelClass: KClass<T>,
+ extras: CreationExtras,
+ ): T = create(modelClass.java, extras)
+
+ public companion object {
+ /**
+ * Creates an [InitializerViewModelFactory] using the given initializers.
+ *
+ * @param initializers the class initializer pairs used for the factory to create
+ * simple view models
+ *
+ * @see [InitializerViewModelFactory]
+ */
+ @JvmStatic
+ public fun from(vararg initializers: ViewModelInitializer<*>): Factory =
+ ViewModelProviders.createInitializerFactory(*initializers)
}
- val extras = MutableCreationExtras(defaultCreationExtras)
- extras[VIEW_MODEL_KEY] = key
- // AGP has some desugaring issues associated with compileOnly dependencies so we need to
- // fall back to the other create method to keep from crashing.
- return try {
- factory.create(modelClass, extras)
- } catch (e: AbstractMethodError) {
- factory.create(modelClass)
- }.also { store.put(key, it) }
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public actual open class OnRequeryFactory {
+ public actual open fun onRequery(viewModel: ViewModel) {}
}
/**
* Simple factory, which calls empty constructor on the give class.
*/
- // actually there is getInstance()
+ public open class NewInstanceFactory
+ /**
+ * Construct a new [NewInstanceFactory] instance.
+ *
+ * Use [NewInstanceFactory.instance] to get a default instance of [NewInstanceFactory].
+ */
@Suppress("SingletonConstructor")
- public open class NewInstanceFactory : Factory {
- @Suppress("DocumentExceptions")
- override fun <T : ViewModel> create(modelClass: Class<T>): T {
- return try {
- modelClass.getDeclaredConstructor().newInstance()
- } catch (e: NoSuchMethodException) {
- throw RuntimeException("Cannot create an instance of $modelClass", e)
- } catch (e: InstantiationException) {
- throw RuntimeException("Cannot create an instance of $modelClass", e)
- } catch (e: IllegalAccessException) {
- throw RuntimeException("Cannot create an instance of $modelClass", e)
- }
- }
+ constructor() : Factory {
+
+ public override fun <T : ViewModel> create(modelClass: Class<T>): T =
+ JvmViewModelProviders.createViewModel(modelClass)
+
+ public override fun <T : ViewModel> create(
+ modelClass: Class<T>,
+ extras: CreationExtras,
+ ): T = create(modelClass)
+
+ public override fun <T : ViewModel> create(
+ modelClass: KClass<T>,
+ extras: CreationExtras,
+ ): T = create(modelClass.java, extras)
public companion object {
- private var sInstance: NewInstanceFactory? = null
+ private var _instance: NewInstanceFactory? = null
/**
* Retrieve a singleton instance of NewInstanceFactory.
@@ -218,13 +214,12 @@
public val instance: NewInstanceFactory
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
get() {
- if (sInstance == null) {
- sInstance = NewInstanceFactory()
+ if (_instance == null) {
+ _instance = NewInstanceFactory()
}
- return sInstance!!
+ return _instance!!
}
- private object ViewModelKeyImpl : Key<String>
/**
* A [CreationExtras.Key] to get a key associated with a requested
* `ViewModel` from [CreationExtras]
@@ -235,7 +230,7 @@
* are passed to [ViewModelProvider.Factory].
*/
@JvmField
- public val VIEW_MODEL_KEY: Key<String> = ViewModelKeyImpl
+ public val VIEW_MODEL_KEY: Key<String> = ViewModelProviders.ViewModelKey
}
}
@@ -260,7 +255,7 @@
* [IllegalArgumentException] will be thrown from [create] method.
*/
@Suppress("SingletonConstructor")
- public constructor() : this(null, 0)
+ public constructor() : this(application = null, unused = 0)
/**
* Constructs this factory.
@@ -268,7 +263,7 @@
* @param application an application to pass in [AndroidViewModel]
*/
@Suppress("SingletonConstructor")
- public constructor(application: Application) : this(application, 0)
+ public constructor(application: Application) : this(application, unused = 0)
@Suppress("DocumentExceptions")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
@@ -321,13 +316,7 @@
}
public companion object {
- internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
- if (owner is HasDefaultViewModelProviderFactory)
- owner.defaultViewModelProviderFactory else instance
-
- internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
-
- private var sInstance: AndroidViewModelFactory? = null
+ private var _instance: AndroidViewModelFactory? = null
/**
* Retrieve a singleton instance of AndroidViewModelFactory.
@@ -337,34 +326,33 @@
*/
@JvmStatic
public fun getInstance(application: Application): AndroidViewModelFactory {
- if (sInstance == null) {
- sInstance = AndroidViewModelFactory(application)
+ if (_instance == null) {
+ _instance = AndroidViewModelFactory(application)
}
- return sInstance!!
+ return _instance!!
}
- private object ApplicationKeyImpl : Key<Application>
-
/**
* A [CreationExtras.Key] to query an application in which ViewModel is being created.
*/
@JvmField
- public val APPLICATION_KEY: Key<Application> = ApplicationKeyImpl
+ public val APPLICATION_KEY: Key<Application> = object : Key<Application> {}
}
}
-}
-internal fun defaultCreationExtras(owner: ViewModelStoreOwner): CreationExtras {
- return if (owner is HasDefaultViewModelProviderFactory) {
- owner.defaultViewModelCreationExtras
- } else CreationExtras.Empty
-}
+ public actual companion object {
+ @JvmStatic
+ public actual fun create(
+ owner: ViewModelStoreOwner,
+ factory: Factory,
+ extras: CreationExtras,
+ ): ViewModelProvider = ViewModelProvider(owner.viewModelStore, factory, extras)
-/**
- * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
- * an activity), associated with this `ViewModelProvider`.
- *
- * @see ViewModelProvider.get(Class)
- */
-@MainThread
-public inline fun <reified VM : ViewModel> ViewModelProvider.get(): VM = get(VM::class.java)
+ @JvmStatic
+ public actual fun create(
+ store: ViewModelStore,
+ factory: Factory,
+ extras: CreationExtras
+ ): ViewModelProvider = ViewModelProvider(store, factory, extras)
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModel.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModel.android.kt
similarity index 96%
rename from lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModel.kt
rename to lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModel.android.kt
index 9d49d055..b2bdab7 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModel.kt
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModel.android.kt
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:JvmName("ViewTreeViewModelKt")
package androidx.lifecycle
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwner.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwner.android.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwner.kt
rename to lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewTreeViewModelStoreOwner.android.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.android.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.android.kt
new file mode 100644
index 0000000..2ab7540
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.android.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.lifecycle.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.internal.ViewModelProviders
+import kotlin.reflect.KClass
+
+public actual class ViewModelInitializer<T : ViewModel>
+actual constructor(
+ internal actual val clazz: KClass<T>,
+ internal actual val initializer: CreationExtras.() -> T,
+) {
+
+ /**
+ * Construct a new [ViewModelInitializer] instance.
+ *
+ * @param clazz [ViewModel] class with which the specified [initializer] is to be associated.
+ * @param initializer factory lambda to be associated with the specified [ViewModel] class.
+ */
+ public constructor(
+ clazz: Class<T>,
+ initializer: CreationExtras.() -> T
+ ) : this(clazz.kotlin, initializer)
+}
+
+internal actual class InitializerViewModelFactory
+actual constructor(
+ private vararg val initializers: ViewModelInitializer<*>
+) : ViewModelProvider.Factory {
+
+ override fun <VM : ViewModel> create(modelClass: Class<VM>, extras: CreationExtras): VM =
+ ViewModelProviders.createViewModelFromInitializers(modelClass.kotlin, extras, *initializers)
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt
deleted file mode 100644
index 38ec91c..0000000
--- a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt
+++ /dev/null
@@ -1,117 +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.lifecycle.viewmodel
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import kotlin.reflect.KClass
-
-@DslMarker
-public annotation class ViewModelFactoryDsl
-
-/**
- * Creates an [InitializerViewModelFactory] with the initializers provided in the builder.
- */
-public inline fun viewModelFactory(
- builder: InitializerViewModelFactoryBuilder.() -> Unit
-): ViewModelProvider.Factory = InitializerViewModelFactoryBuilder().apply(builder).build()
-
-/**
- * DSL for constructing a new [ViewModelProvider.Factory]
- */
-@ViewModelFactoryDsl
-public class InitializerViewModelFactoryBuilder {
- private val initializers = mutableListOf<ViewModelInitializer<*>>()
-
- /**
- * Add the initializer for the given ViewModel class.
- *
- * @param clazz the class the initializer is associated with.
- * @param initializer lambda used to create an instance of the ViewModel class
- */
- public fun <T : ViewModel> addInitializer(
- clazz: KClass<T>,
- initializer: CreationExtras.() -> T,
- ) {
- initializers.add(ViewModelInitializer(clazz.java, initializer))
- }
-
- /**
- * Build the InitializerViewModelFactory.
- */
- public fun build(): ViewModelProvider.Factory =
- InitializerViewModelFactory(*initializers.toTypedArray())
-}
-
-/**
- * Add an initializer to the [InitializerViewModelFactoryBuilder]
- */
-public inline fun <reified VM : ViewModel> InitializerViewModelFactoryBuilder.initializer(
- noinline initializer: CreationExtras.() -> VM
-) {
- addInitializer(VM::class, initializer)
-}
-
-/**
- * Holds a [ViewModel] class and initializer for that class
- */
-public class ViewModelInitializer<T : ViewModel>(
- internal val clazz: Class<T>,
- internal val initializer: CreationExtras.() -> T,
-)
-
-/**
- * A [ViewModelProvider.Factory] that allows you to add lambda initializers for handling
- * particular ViewModel classes using [CreationExtras], while using the default behavior for any
- * other classes.
- *
- * ```
- * val factory = viewModelFactory {
- * initializer { TestViewModel(this[key]) }
- * }
- * val viewModel: TestViewModel = factory.create(TestViewModel::class.java, extras)
- * ```
- */
-internal class InitializerViewModelFactory(
- private vararg val initializers: ViewModelInitializer<*>
-) : ViewModelProvider.Factory {
-
- /**
- * Creates a new instance of the given `Class`.
- *
- * This will use the initializer if one has been set for the class, otherwise it throw an
- * [IllegalArgumentException].
- *
- * @param modelClass a `Class` whose instance is requested
- * @param extras an additional information for this creation request
- * @return a newly created ViewModel
- *
- * @throws IllegalArgumentException if no initializer has been set for the given class.
- */
- override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
- var viewModel: T? = null
- @Suppress("UNCHECKED_CAST")
- initializers.forEach {
- if (it.clazz == modelClass) {
- viewModel = it.initializer.invoke(extras) as? T
- }
- }
- return viewModel ?: throw IllegalArgumentException(
- "No initializer set for given class ${modelClass.name}"
- )
- }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/MockViewModelStoreTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/MockViewModelStoreTest.kt
new file mode 100644
index 0000000..cd0446e
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/MockViewModelStoreTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.lifecycle
+
+import androidx.kruth.assertThat
+import kotlin.test.Test
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+class MockViewModelStoreTest {
+
+ private open class TestViewModel : ViewModel() {
+ var cleared = false
+ public override fun onCleared() {
+ cleared = true
+ }
+ }
+
+ @Test
+ fun testClear() {
+ val store = ViewModelStore()
+ val mockedViewModel = mock(TestViewModel::class.java)
+ store.put(key = "mock", mockedViewModel)
+ assertThat(mockedViewModel.cleared).isFalse()
+ store.clear()
+ verify(mockedViewModel).onCleared()
+ verifyNoMoreInteractions(mockedViewModel)
+ assertThat(store["a"]).isNull()
+ assertThat(store["b"]).isNull()
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/MockViewModelTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/MockViewModelTest.kt
new file mode 100644
index 0000000..579bd99
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/MockViewModelTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.lifecycle
+
+import androidx.kruth.assertThat
+import java.io.Closeable
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito
+
+@RunWith(JUnit4::class)
+class MockViewModelTest {
+
+ private class CloseableImpl : Closeable {
+ var wasClosed = false
+ override fun close() {
+ wasClosed = true
+ }
+ }
+
+ private open class TestViewModel : ViewModel()
+
+ @Test
+ fun addCloseable_withMock_doesNotThrow() {
+ val vm = Mockito.mock(TestViewModel::class.java)
+ val impl = CloseableImpl()
+ // This shouldn't crash, even on a mocked object
+ vm.addCloseable(impl)
+ }
+
+ @Test
+ fun getCloseable_withMock_doesNotThrow_returnsNull() {
+ val key = "key"
+ val vm = Mockito.mock(TestViewModel::class.java)
+ val impl = CloseableImpl()
+ // This shouldn't crash, even on a mocked object
+ vm.addCloseable(key, impl)
+ val actualCloseable = vm.getCloseable<CloseableImpl>(key)
+ assertThat(actualCloseable).isNull()
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/NewInstanceFactoryTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/NewInstanceFactoryTest.kt
new file mode 100644
index 0000000..120fea4
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/NewInstanceFactoryTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.kruth.assertThat
+import androidx.lifecycle.viewmodel.CreationExtras
+import kotlin.test.fail
+import org.junit.Test
+
+class NewInstanceFactoryTest {
+
+ @Test
+ fun create_withConstructorWithZeroArguments_returnsViewModel() {
+ val modelClass = TestViewModel1::class
+ val factory = ViewModelProvider.NewInstanceFactory()
+
+ val viewModel = factory.create(modelClass, CreationExtras.Empty)
+
+ assertThat(viewModel).isNotNull()
+ }
+
+ @Test
+ fun create_withConstructorWithOneArgument_throwsNoSuchMethodException() {
+ val modelClass = TestViewModel2::class
+ val factory = ViewModelProvider.NewInstanceFactory()
+ try {
+ factory.create(modelClass, CreationExtras.Empty)
+ fail("Expected `IllegalArgumentException` but no exception has been throw.")
+ } catch (e: RuntimeException) {
+ assertThat(e).hasCauseThat().isInstanceOf<NoSuchMethodException>()
+ assertThat(e).hasMessageThat()
+ .contains("Cannot create an instance of class ${TestViewModel2::class.java.name}")
+ }
+ }
+
+ @Test
+ fun create_withPrivateConstructor_throwsIllegalAccessException() {
+ val modelClass = TestViewModel3::class
+ val factory = ViewModelProvider.NewInstanceFactory()
+ try {
+ factory.create(modelClass, CreationExtras.Empty)
+ fail("Expected `IllegalArgumentException` but no exception has been throw.")
+ } catch (e: RuntimeException) {
+ assertThat(e).hasCauseThat().isInstanceOf<IllegalAccessException>()
+ assertThat(e).hasMessageThat()
+ .contains("Cannot create an instance of class ${TestViewModel3::class.java.name}")
+ }
+ }
+
+ @Test
+ fun create_withAbstractConstructor_throwsInstantiationException() {
+ val modelClass = ViewModel::class
+ val factory = ViewModelProvider.NewInstanceFactory()
+ try {
+ factory.create(modelClass, CreationExtras.Empty)
+ fail("Expected `IllegalArgumentException` but no exception has been throw.")
+ } catch (e: RuntimeException) {
+ assertThat(e).hasCauseThat().isInstanceOf<InstantiationException>()
+ assertThat(e).hasMessageThat()
+ .contains("Cannot create an instance of class ${ViewModel::class.java.name}")
+ }
+ }
+
+ class TestViewModel1 : ViewModel()
+ class TestViewModel2(val unused: Int) : ViewModel()
+ private class TestViewModel3 : ViewModel()
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelLazyTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelLazyTest.kt
deleted file mode 100644
index 3607742..0000000
--- a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelLazyTest.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.lifecycle
-
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class ViewModelLazyTest {
-
- @Test
- fun test() {
- val factoryProducer = { TestFactory() }
- val store = ViewModelStore()
- val vm by ViewModelLazy(TestVM::class, { store }, factoryProducer)
- assertThat(vm.prop).isEqualTo("spb")
- assertThat(store.keys()).isNotEmpty()
- }
-
- class TestVM(val prop: String) : ViewModel()
-
- @Suppress("UNCHECKED_CAST")
- class TestFactory : ViewModelProvider.Factory {
-
- override fun <T : ViewModel> create(modelClass: Class<T>): T = TestVM("spb") as T
- }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelProviderReifiedTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelProviderReifiedTest.kt
deleted file mode 100644
index 2862a73..0000000
--- a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelProviderReifiedTest.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.lifecycle
-
-import org.junit.Assert.assertNotNull
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class ViewModelProviderReifiedTest {
- class TestViewModel : ViewModel()
-
- @Test
- fun providerReifiedGet() {
- val factory = object : ViewModelProvider.Factory {
- override fun <T : ViewModel> create(modelClass: Class<T>) = modelClass
- .getDeclaredConstructor().newInstance()
- }
- val provider = ViewModelProvider(ViewModelStore(), factory)
-
- val viewModel = provider.get<TestViewModel>()
- assertNotNull(viewModel)
- }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelProviderTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelProviderTest.kt
deleted file mode 100644
index 934878d..0000000
--- a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelProviderTest.kt
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.lifecycle
-
-import androidx.lifecycle.viewmodel.CreationExtras
-import androidx.lifecycle.viewmodel.MutableCreationExtras
-import com.google.common.truth.Truth.assertThat
-import org.junit.Assert.fail
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class ViewModelProviderTest {
- private lateinit var viewModelProvider: ViewModelProvider
- @Before
- fun setup() {
- viewModelProvider =
- ViewModelProvider(ViewModelStore(), ViewModelProvider.NewInstanceFactory())
- }
-
- @Test
- fun twoViewModelsWithSameKey() {
- val key = "the_key"
- val vm1 = viewModelProvider[key, ViewModel1::class.java]
- assertThat(vm1.cleared).isFalse()
- val vw2 = viewModelProvider[key, ViewModel2::class.java]
- assertThat(vw2).isNotNull()
- assertThat(vm1.cleared).isTrue()
- }
-
- @Test
- fun localViewModel() {
- class VM : ViewModel1()
- try {
- viewModelProvider[VM::class.java]
- fail("Local viewModel should be created from the ViewModelProvider")
- } catch (ignored: IllegalArgumentException) { }
- }
-
- @Test
- fun twoViewModels() {
- val model1 = viewModelProvider[ViewModel1::class.java]
- val model2 = viewModelProvider[ViewModel2::class.java]
- assertThat(viewModelProvider[ViewModel1::class.java]).isSameInstanceAs(model1)
- assertThat(viewModelProvider[ViewModel2::class.java]).isSameInstanceAs(model2)
- }
-
- @Test
- fun testOwnedBy() {
- val owner = FakeViewModelStoreOwner()
- val provider = ViewModelProvider(owner, ViewModelProvider.NewInstanceFactory())
- val viewModel = provider[ViewModel1::class.java]
- assertThat(viewModel).isSameInstanceAs(provider[ViewModel1::class.java])
- }
-
- @Test
- fun testCustomDefaultFactory() {
- val store = ViewModelStore()
- val factory = CountingFactory()
- val owner = ViewModelStoreOwnerWithFactory(store, factory)
- val provider = ViewModelProvider(owner)
- val viewModel = provider[ViewModel1::class.java]
- assertThat(viewModel).isSameInstanceAs(provider[ViewModel1::class.java])
- assertThat(factory.called).isEqualTo(1)
- }
-
- @Test
- fun testKeyedFactory() {
- val owner = FakeViewModelStoreOwner()
- val explicitlyKeyed: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
- override fun <T : ViewModel> create(
- modelClass: Class<T>,
- extras: CreationExtras
- ): T {
- val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
- assertThat(key).isEqualTo("customKey")
- @Suppress("UNCHECKED_CAST")
- return ViewModel1() as T
- }
- }
- val provider = ViewModelProvider(owner, explicitlyKeyed)
- provider["customKey", ViewModel1::class.java]
- val implicitlyKeyed: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
- override fun <T : ViewModel> create(
- modelClass: Class<T>,
- extras: CreationExtras
- ): T {
- val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
- assertThat(key).isNotNull()
- @Suppress("UNCHECKED_CAST")
- return ViewModel1() as T
- }
- }
- ViewModelProvider(owner, implicitlyKeyed)["customKey", ViewModel1::class.java]
- }
-
- @Test
- fun testDefaultCreationExtrasWithMutableExtras() {
- val owner = ViewModelStoreOwnerWithCreationExtras()
- val wasCalled = BooleanArray(1)
- val testFactory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
- override fun <T : ViewModel> create(
- modelClass: Class<T>,
- extras: CreationExtras
- ): T {
- val mutableKey = object : CreationExtras.Key<String> {}
- val mutableValue = "value"
- val mutableExtras = MutableCreationExtras(extras)
- mutableExtras[mutableKey] = mutableValue
- val key = mutableExtras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
- assertThat(key).isEqualTo("customKey")
- assertThat(mutableExtras[TEST_KEY]).isEqualTo(TEST_VALUE)
- assertThat(mutableExtras[mutableKey]).isEqualTo(mutableValue)
- wasCalled[0] = true
- @Suppress("UNCHECKED_CAST")
- return ViewModel1() as T
- }
- }
- ViewModelProvider(owner, testFactory)["customKey", ViewModel1::class.java]
- assertThat(wasCalled[0]).isTrue()
- wasCalled[0] = false
- ViewModelProvider(object : ViewModelStoreOwnerWithCreationExtras() {
- override val defaultViewModelProviderFactory = testFactory
- })["customKey", ViewModel1::class.java]
- assertThat(wasCalled[0]).isTrue()
- }
-
- @Test
- fun testDefaultCreationExtras() {
- val owner = ViewModelStoreOwnerWithCreationExtras()
- val wasCalled = BooleanArray(1)
- val testFactory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
- override fun <T : ViewModel> create(
- modelClass: Class<T>,
- extras: CreationExtras
- ): T {
- val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
- assertThat(key).isEqualTo("customKey")
- assertThat(extras[TEST_KEY]).isEqualTo(TEST_VALUE)
- wasCalled[0] = true
- @Suppress("UNCHECKED_CAST")
- return ViewModel1() as T
- }
- }
- ViewModelProvider(owner, testFactory)["customKey", ViewModel1::class.java]
- assertThat(wasCalled[0]).isTrue()
- wasCalled[0] = false
- ViewModelProvider(object : ViewModelStoreOwnerWithCreationExtras() {
- override val defaultViewModelProviderFactory = testFactory
- })["customKey", ViewModel1::class.java]
- assertThat(wasCalled[0]).isTrue()
- }
-
- class ViewModelStoreOwnerWithFactory internal constructor(
- private val mStore: ViewModelStore,
- private val mFactory: ViewModelProvider.Factory
- ) : ViewModelStoreOwner, HasDefaultViewModelProviderFactory {
- override val viewModelStore: ViewModelStore = mStore
- override val defaultViewModelProviderFactory = mFactory
- }
-
- class FakeViewModelStoreOwner internal constructor(
- store: ViewModelStore = ViewModelStore()
- ) : ViewModelStoreOwner {
- override val viewModelStore: ViewModelStore = store
- }
-
- open class ViewModel1 : ViewModel() {
- var cleared = false
- override fun onCleared() {
- cleared = true
- }
- }
-
- class ViewModel2 : ViewModel()
- class CountingFactory : ViewModelProvider.NewInstanceFactory() {
- var called = 0
- override fun <T : ViewModel> create(modelClass: Class<T>): T {
- called++
- return super.create(modelClass)
- }
- }
-
- internal open class ViewModelStoreOwnerWithCreationExtras : ViewModelStoreOwner,
- HasDefaultViewModelProviderFactory {
- private val _viewModelStore = ViewModelStore()
- override val defaultViewModelProviderFactory: ViewModelProvider.Factory
- get() = throw UnsupportedOperationException()
-
- override val defaultViewModelCreationExtras: CreationExtras
- get() {
- val extras = MutableCreationExtras()
- extras[TEST_KEY] = TEST_VALUE
- return extras
- }
-
- override val viewModelStore: ViewModelStore = _viewModelStore
- }
-}
-
-private val TEST_KEY = object : CreationExtras.Key<String> {}
-private const val TEST_VALUE = "test_value"
diff --git a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelStoreTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelStoreTest.kt
deleted file mode 100644
index 6e048f6..0000000
--- a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelStoreTest.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.lifecycle
-
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.nullValue
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-
-@RunWith(JUnit4::class)
-class ViewModelStoreTest {
- @Test
- fun testClear() {
- val store = ViewModelStore()
- val viewModel1 = TestViewModel()
- val viewModel2 = TestViewModel()
- val mockViewModel = mock(TestViewModel::class.java)
- store.put("a", viewModel1)
- store.put("b", viewModel2)
- store.put("mock", mockViewModel)
- assertThat(viewModel1.cleared, `is`(false))
- assertThat(viewModel2.cleared, `is`(false))
- store.clear()
- assertThat(viewModel1.cleared, `is`(true))
- assertThat(viewModel2.cleared, `is`(true))
- verify(mockViewModel).onCleared()
- verifyNoMoreInteractions(mockViewModel)
- assertThat(store["a"], nullValue())
- assertThat(store["b"], nullValue())
- }
-
- internal open class TestViewModel : ViewModel() {
- var cleared = false
- public override fun onCleared() {
- cleared = true
- }
- }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelTest.kt b/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelTest.kt
deleted file mode 100644
index 68697fa..0000000
--- a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/ViewModelTest.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.lifecycle
-
-import java.io.Closeable
-import org.hamcrest.CoreMatchers.nullValue
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito
-
-@RunWith(JUnit4::class)
-class ViewModelTest {
-
- internal class CloseableImpl : Closeable {
- var wasClosed = false
- override fun close() {
- wasClosed = true
- }
- }
-
- internal open class ViewModel : androidx.lifecycle.ViewModel()
- internal class ConstructorArgViewModel(closeable: Closeable) :
- androidx.lifecycle.ViewModel(closeable)
-
- @Test
- fun testCloseableWithKey() {
- val vm = ViewModel()
- val impl = CloseableImpl()
- vm.addCloseable("totally_not_coroutine_context", impl)
- vm.clear()
- assertTrue(impl.wasClosed)
- }
-
- @Test
- fun testCloseableWithKeyAlreadyClearedVM() {
- val vm = ViewModel()
- vm.clear()
- val impl = CloseableImpl()
- vm.addCloseable("key", impl)
- assertTrue(impl.wasClosed)
- }
-
- @Test
- fun testMockedGetCloseable() {
- val vm = Mockito.mock(ViewModel::class.java)
- assertThat(vm.getCloseable("Careless mocks =|"), nullValue())
- }
-
- @Test
- fun testAddCloseable() {
- val vm = ViewModel()
- val impl = CloseableImpl()
- vm.addCloseable(impl)
- vm.clear()
- assertTrue(impl.wasClosed)
- }
-
- @Test
- fun testAddCloseableAlreadyClearedVM() {
- val vm = ViewModel()
- vm.clear()
- val impl = CloseableImpl()
- // This shouldn't crash, even though vm already cleared
- vm.addCloseable(impl)
- assertTrue(impl.wasClosed)
- }
-
- @Test
- fun testConstructorCloseable() {
- val impl = CloseableImpl()
- val vm = ConstructorArgViewModel(impl)
- vm.clear()
- assertTrue(impl.wasClosed)
- }
-
- @Test
- fun testMockedAddCloseable() {
- val vm = Mockito.mock(ViewModel::class.java)
- val impl = CloseableImpl()
- // This shouldn't crash, even on a mocked object
- vm.addCloseable(impl)
- }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/HasDefaultViewModelProviderFactory.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/HasDefaultViewModelProviderFactory.android.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/HasDefaultViewModelProviderFactory.kt
rename to lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/HasDefaultViewModelProviderFactory.android.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModel.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModel.kt
new file mode 100644
index 0000000..62ee01b
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModel.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:OptIn(ExperimentalStdlibApi::class)
+
+package androidx.lifecycle
+
+import androidx.annotation.MainThread
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+
+/**
+ * ViewModel is a class that is responsible for preparing and managing the data for
+ * an [Activity][androidx.activity.ComponentActivity] or a [Fragment][androidx.fragment.app.Fragment].
+ * It also handles the communication of the Activity / Fragment with the rest of the application
+ * (e.g. calling the business logic classes).
+ *
+ * A ViewModel is always created in association with a scope (a fragment or an activity) and will
+ * be retained as long as the scope is alive. E.g. if it is an Activity, until it is finished.
+ *
+ * In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a
+ * configuration change (e.g. rotation). The new owner instance just re-connects to the existing
+ * model.
+ *
+ * The purpose of the ViewModel is to acquire and keep the information that is necessary for an
+ * Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the
+ * ViewModel. ViewModels usually expose this information via [Lifecycle][androidx.lifecycle.LiveData] or
+ * Android Data Binding. You can also use any observability construct from your favorite framework.
+ *
+ * ViewModel's only responsibility is to manage the data for the UI. It **should never** access
+ * your view hierarchy or hold a reference back to the Activity or the Fragment.
+ *
+ * Typical usage from an Activity standpoint would be:
+ *
+ * ```
+ * class UserActivity : ComponentActivity {
+ * private val viewModel by viewModels<UserViewModel>()
+ *
+ * override fun onCreate(savedInstanceState: Bundle) {
+ * super.onCreate(savedInstanceState)
+ * setContentView(R.layout.user_activity_layout)
+ * viewModel.user.observe(this) { user: User ->
+ * // update ui.
+ * }
+ * requireViewById(R.id.button).setOnClickListener {
+ * viewModel.doAction()
+ * }
+ * }
+ * }
+ * ```
+ *
+ * ViewModel would be:
+ *
+ * ```
+ * class UserViewModel : ViewModel {
+ * private val userLiveData = MutableLiveData<User>()
+ * val user: LiveData<User> get() = userLiveData
+ *
+ * init {
+ * // trigger user load.
+ * }
+ *
+ * fun doAction() {
+ * // depending on the action, do necessary business logic calls and update the
+ * // userLiveData.
+ * }
+ * }
+ * ```
+ *
+ * ViewModels can also be used as a communication layer between different Fragments of an Activity.
+ * Each Fragment can acquire the ViewModel using the same key via their Activity. This allows
+ * communication between Fragments in a de-coupled fashion such that they never need to talk to
+ * the other Fragment directly.
+ *
+ * ```
+ * class MyFragment : Fragment {
+ * val viewModel by activityViewModels<UserViewModel>()
+ * }
+ *```
+ */
+public expect abstract class ViewModel {
+
+ /**
+ * Construct a new ViewModel instance.
+ *
+ * You should **never** manually construct a ViewModel outside of a
+ * [ViewModelProvider.Factory].
+ */
+ public constructor()
+
+ /**
+ * Construct a new ViewModel instance. Any [AutoCloseable] objects provided here
+ * will be closed directly before [ViewModel.onCleared] is called.
+ *
+ * You should **never** manually construct a ViewModel outside of a
+ * [ViewModelProvider.Factory].
+ */
+ public constructor(vararg closeables: AutoCloseable)
+
+ /**
+ * This method will be called when this ViewModel is no longer used and will be destroyed.
+ *
+ * It is useful when ViewModel observes some data and you need to clear this subscription to
+ * prevent a leak of this ViewModel.
+ */
+ protected open fun onCleared()
+
+ @MainThread
+ internal fun clear()
+
+ /**
+ * Add a new [AutoCloseable] object that will be closed directly before
+ * [ViewModel.onCleared] is called.
+ *
+ * If `onCleared()` has already been called, the closeable will not be added,
+ * and will instead be closed immediately.
+ *
+ * @param key A key that allows you to retrieve the closeable passed in by using the same
+ * key with [ViewModel.getCloseable]
+ * @param closeable The object that should be [AutoCloseable.close] directly before
+ * [ViewModel.onCleared] is called.
+ */
+ public fun addCloseable(key: String, closeable: AutoCloseable)
+
+ /**
+ * Add a new [AutoCloseable] object that will be closed directly before
+ * [ViewModel.onCleared] is called.
+ *
+ * If `onCleared()` has already been called, the closeable will not be added,
+ * and will instead be closed immediately.
+ *
+ * @param closeable The object that should be [closed][AutoCloseable.close] directly before
+ * [ViewModel.onCleared] is called.
+ */
+ public open fun addCloseable(closeable: AutoCloseable)
+
+ /**
+ * Returns the closeable previously added with [ViewModel.addCloseable] with the given [key].
+ *
+ * @param key The key that was used to add the Closeable.
+ */
+ public fun <T : AutoCloseable> getCloseable(key: String): T?
+}
+
+private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
+
+/**
+ * [CoroutineScope] tied to this [ViewModel].
+ * This scope will be canceled when ViewModel will be cleared, i.e. [ViewModel.onCleared] is called
+ *
+ * This scope is bound to
+ * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
+ */
+public val ViewModel.viewModelScope: CoroutineScope
+ get() {
+ return getCloseable<CloseableCoroutineScope>(JOB_KEY) ?: CloseableCoroutineScope(
+ SupervisorJob() + Dispatchers.Main.immediate
+ ).also { newClosableScope ->
+ addCloseable(JOB_KEY, newClosableScope)
+ }
+ }
+
+private class CloseableCoroutineScope(context: CoroutineContext) : AutoCloseable, CoroutineScope {
+ override val coroutineContext: CoroutineContext = context
+
+ override fun close() {
+ coroutineContext.cancel()
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelLazy.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelLazy.kt
similarity index 84%
rename from lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelLazy.kt
rename to lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelLazy.kt
index 770b0b1..ec366cc 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelLazy.kt
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelLazy.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package androidx.lifecycle
import androidx.lifecycle.viewmodel.CreationExtras
+import kotlin.jvm.JvmOverloads
import kotlin.reflect.KClass
/**
@@ -29,7 +29,7 @@
* [factoryProducer] is a lambda that will be called during initialization,
* returned [ViewModelProvider.Factory] will be used for creation of [VM]
*
- * [providerOwner] is a lambda that will be called during initialization,
+ * [extrasProducer] is a lambda that will be called during initialization,
* returned [HasDefaultViewModelProviderFactory] will get [CreationExtras] used for creation of [VM]
*/
public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
@@ -44,15 +44,12 @@
get() {
val viewModel = cached
return if (viewModel == null) {
- val factory = factoryProducer()
val store = storeProducer()
- ViewModelProvider(
- store,
- factory,
- extrasProducer()
- ).get(viewModelClass.java).also {
- cached = it
- }
+ val factory = factoryProducer()
+ val extras = extrasProducer()
+ ViewModelProvider.create(store, factory, extras)
+ .get(viewModelClass)
+ .also { cached = it }
} else {
viewModel
}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelProvider.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelProvider.kt
new file mode 100644
index 0000000..3894dbc
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelProvider.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.annotation.MainThread
+import androidx.annotation.RestrictTo
+import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.internal.DefaultViewModelProviderFactory
+import androidx.lifecycle.viewmodel.internal.ViewModelProviders
+import kotlin.reflect.KClass
+
+/**
+ * A utility class that provides `ViewModels` for a scope.
+ *
+ * Default `ViewModelProvider` for an `Activity` or a `Fragment` can be obtained
+ * by passing it to the constructor: `ViewModelProvider(myFragment)`
+ */
+public expect class ViewModelProvider {
+
+ /**
+ * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
+ * an activity), associated with this `ViewModelProvider`.
+ *
+ *
+ * The created ViewModel is associated with the given scope and will be retained
+ * as long as the scope is alive (e.g. if it is an activity, until it is
+ * finished or process is killed).
+ *
+ * @param modelClass The class of the ViewModel to create an instance of it if it is not
+ * present.
+ * @return A ViewModel that is an instance of the given type `T`.
+ * @throws IllegalArgumentException if the given [modelClass] is local or anonymous class.
+ */
+ @MainThread
+ public operator fun <T : ViewModel> get(modelClass: KClass<T>): T
+
+ /**
+ * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
+ * an activity), associated with this `ViewModelProvider`.
+ *
+ * The created ViewModel is associated with the given scope and will be retained
+ * as long as the scope is alive (e.g. if it is an activity, until it is
+ * finished or process is killed).
+ *
+ * @param key The key to use to identify the ViewModel.
+ * @param modelClass The class of the ViewModel to create an instance of it if it is not
+ * present.
+ * @return A ViewModel that is an instance of the given type `T`.
+ */
+ @MainThread
+ public operator fun <T : ViewModel> get(key: String, modelClass: KClass<T>): T
+
+ /**
+ * Implementations of `Factory` interface are responsible to instantiate ViewModels.
+ */
+ public interface Factory {
+
+ /**
+ * Creates a new instance of the given `Class`.
+ *
+ * @param modelClass a [KClass] whose instance is requested
+ * @param extras an additional information for this creation request
+ * @return a newly created [ViewModel]
+ */
+ public open fun <T : ViewModel> create(
+ modelClass: KClass<T>,
+ extras: CreationExtras,
+ ): T
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public open class OnRequeryFactory {
+ public open fun onRequery(viewModel: ViewModel)
+ }
+
+ public companion object {
+ /**
+ * Creates a [ViewModelProvider]. This provider generates [ViewModel] instances using the
+ * specified [Factory] and stores them within the [ViewModelStore] of the provided
+ * [ViewModelStoreOwner].
+ *
+ * @param owner The [ViewModelStoreOwner] that will manage the lifecycle of the created
+ * [ViewModel] instances.
+ * @param factory The [Factory] responsible for creating new [ViewModel] instances.
+ * @param extras Additional data to be passed to the [Factory] during
+ * [ViewModel] creation.
+ */
+ public fun create(
+ owner: ViewModelStoreOwner,
+ factory: Factory = ViewModelProviders.getDefaultFactory(owner),
+ extras: CreationExtras = ViewModelProviders.getDefaultCreationExtras(owner),
+ ): ViewModelProvider
+
+ /**
+ * Creates a [ViewModelProvider]. This provider generates [ViewModel] instances using the
+ * specified [Factory] and stores them within the [ViewModelStore] of the provided
+ * [ViewModelStoreOwner].
+ *
+ * @param store `ViewModelStore` where ViewModels will be stored.
+ * @param factory factory a `Factory` which will be used to instantiate new `ViewModels`
+ * @param extras Additional data to be passed to the [Factory] during
+ * [ViewModel] creation.
+ * */
+ public fun create(
+ store: ViewModelStore,
+ factory: Factory = DefaultViewModelProviderFactory,
+ extras: CreationExtras = CreationExtras.Empty,
+ ): ViewModelProvider
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelProviderGet.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelProviderGet.kt
new file mode 100644
index 0000000..090e661
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelProviderGet.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("ViewModelProviderGetKt")
+
+package androidx.lifecycle
+
+import androidx.annotation.MainThread
+import kotlin.jvm.JvmName
+
+/**
+ * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
+ * an activity), associated with this `ViewModelProvider`.
+ *
+ * @see ViewModelProvider.get(Class)
+ */
+@MainThread
+public inline fun <reified VM : ViewModel> ViewModelProvider.get(): VM = get(VM::class)
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelStore.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelStore.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelStore.kt
rename to lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelStore.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelStoreOwner.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelStoreOwner.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelStoreOwner.kt
rename to lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelStoreOwner.kt
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/CreationExtras.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/CreationExtras.kt
similarity index 99%
rename from lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/CreationExtras.kt
rename to lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/CreationExtras.kt
index 3d5d587..f6cf9c6 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/viewmodel/CreationExtras.kt
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/CreationExtras.kt
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package androidx.lifecycle.viewmodel
/**
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.kt
new file mode 100644
index 0000000..df164d3
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.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.
+ */
+@file:JvmName("InitializerViewModelFactoryKt")
+
+package androidx.lifecycle.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.internal.ViewModelProviders
+import kotlin.jvm.JvmName
+import kotlin.reflect.KClass
+
+@DslMarker
+public annotation class ViewModelFactoryDsl
+
+/**
+ * Creates an [InitializerViewModelFactory] with the initializers provided in the builder.
+ */
+public inline fun viewModelFactory(
+ builder: InitializerViewModelFactoryBuilder.() -> Unit
+): ViewModelProvider.Factory = InitializerViewModelFactoryBuilder().apply(builder).build()
+
+/**
+ * DSL for constructing a new [ViewModelProvider.Factory]
+ */
+@ViewModelFactoryDsl
+public class InitializerViewModelFactoryBuilder
+public constructor() {
+
+ private val initializers = mutableListOf<ViewModelInitializer<*>>()
+
+ /**
+ * Associates the specified [initializer] with the given [ViewModel] class.
+ *
+ * @param clazz [ViewModel] class with which the specified [initializer] is to be associated.
+ * @param initializer factory lambda to be associated with the specified [ViewModel] class.
+ */
+ @Suppress("SetterReturnsThis", "MissingGetterMatchingBuilder")
+ public fun <T : ViewModel> addInitializer(
+ clazz: KClass<T>,
+ initializer: CreationExtras.() -> T,
+ ) {
+ initializers += ViewModelInitializer(clazz, initializer)
+ }
+
+ /**
+ * Returns an instance of [ViewModelProvider.Factory] created from the initializers set on this
+ * builder.
+ */
+ public fun build(): ViewModelProvider.Factory =
+ ViewModelProviders.createInitializerFactory(initializers)
+}
+
+/**
+ * Add an initializer to the [InitializerViewModelFactoryBuilder]
+ */
+public inline fun <reified VM : ViewModel> InitializerViewModelFactoryBuilder.initializer(
+ noinline initializer: CreationExtras.() -> VM
+) {
+ addInitializer(VM::class, initializer)
+}
+
+/**
+ * Holds a [ViewModel] class and initializer for that class
+ */
+public expect class ViewModelInitializer<T : ViewModel>
+/**
+ * Construct a new [ViewModelInitializer] instance.
+ *
+ * @param clazz [ViewModel] class with which the specified [initializer] is to be associated.
+ * @param initializer factory lambda to be associated with the specified [ViewModel] class.
+ */
+public constructor(
+ clazz: KClass<T>,
+ initializer: CreationExtras.() -> T,
+) {
+ internal val clazz: KClass<T>
+ internal val initializer: CreationExtras.() -> T
+}
+
+/**
+ * A [ViewModelProvider.Factory] that allows you to add lambda initializers for handling
+ * particular ViewModel classes using [CreationExtras], while using the default behavior for any
+ * other classes.
+ *
+ * ```
+ * val factory = viewModelFactory {
+ * initializer { TestViewModel(this[key]) }
+ * }
+ * val viewModel: TestViewModel = factory.create(TestViewModel::class.java, extras)
+ * ```
+ */
+internal expect class InitializerViewModelFactory(
+ vararg initializers: ViewModelInitializer<*>,
+) : ViewModelProvider.Factory
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactory.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactory.kt
new file mode 100644
index 0000000..bebfc7a
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactory.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel.internal
+
+import androidx.lifecycle.ViewModelProvider
+
+internal expect object DefaultViewModelProviderFactory : ViewModelProvider.Factory
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelImpl.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelImpl.kt
new file mode 100644
index 0000000..3b66252
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelImpl.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalStdlibApi::class)
+
+package androidx.lifecycle.viewmodel.internal
+
+import androidx.annotation.MainThread
+import androidx.lifecycle.ViewModel
+import kotlin.jvm.Volatile
+import kotlinx.atomicfu.locks.SynchronizedObject
+import kotlinx.atomicfu.locks.synchronized
+
+/**
+ * Internal implementation of the multiplatform [ViewModel].
+ *
+ * Kotlin Multiplatform does not support expect class with default implementation yet, so we
+ * extracted the common logic used by all platforms to this internal class.
+ *
+ * @see <a href="https://youtrack.jetbrains.com/issue/KT-20427">KT-20427</a>
+ */
+internal class ViewModelImpl {
+
+ private val lock = SynchronizedObject()
+
+ /**
+ * Holds a mapping between [String] keys and [AutoCloseable] resources that have been associated
+ * with this [ViewModel].
+ *
+ * The associated resources will be [AutoCloseable.close] right before the [ViewModel.onCleared]
+ * is called. This provides automatic resource cleanup upon [ViewModel] release.
+ *
+ * The clearing order is:
+ * 1. [keyToCloseables][AutoCloseable.close]
+ * 2. [closeables][AutoCloseable.close]
+ * 3. [ViewModel.onCleared]
+ *
+ * **Note:** Manually [synchronized] is necessary to prevent issues on Android API 21 and 22.
+ * This avoids potential problems found in older versions of `ConcurrentHashMap`.
+ *
+ * @see <a href="https://issuetracker.google.com/37042460">b/37042460</a>
+ */
+ private val bagOfTags = mutableMapOf<String, AutoCloseable>()
+
+ /**
+ * @see [bagOfTags]
+ */
+ private val closeables = mutableSetOf<AutoCloseable>()
+
+ @Volatile
+ private var isCleared = false
+
+ /**
+ * Construct a new [ViewModel] instance.
+ *
+ * You should **never** manually construct a [ViewModel] outside of a
+ * [androidx.lifecycle.ViewModelProvider.Factory].
+ */
+ constructor()
+
+ /**
+ * Construct a new [ViewModel] instance. Any [AutoCloseable] objects provided here
+ * will be closed directly before [ViewModel.onCleared] is called.
+ *
+ * You should **never** manually construct a [ViewModel] outside of a
+ * [androidx.lifecycle.ViewModelProvider.Factory].
+ */
+ constructor(vararg closeables: AutoCloseable) {
+ this.closeables += closeables
+ }
+
+ @MainThread
+ fun clear() {
+ isCleared = true
+ synchronized(lock) {
+ for (value in bagOfTags.values) {
+ // see comment for the similar call in `setTagIfAbsent`
+ closeWithRuntimeException(value)
+ }
+ for (closeable in closeables) {
+ closeWithRuntimeException(closeable)
+ }
+ }
+ closeables.clear()
+ }
+
+ /**
+ * Add a new [AutoCloseable] object that will be closed directly before
+ * [ViewModel.onCleared] is called.
+ *
+ * If `onCleared()` has already been called, the closeable will not be added,
+ * and will instead be closed immediately.
+ *
+ * @param key A key that allows you to retrieve the closeable passed in by using the same
+ * key with [ViewModel.getCloseable]
+ * @param closeable The object that should be [AutoCloseable.close] directly before
+ * [ViewModel.onCleared] is called.
+ */
+ fun addCloseable(key: String, closeable: AutoCloseable) {
+ // Although no logic should be done after user calls onCleared(), we will
+ // ensure that if it has already been called, the closeable attempting to
+ // be added will be closed immediately to ensure there will be no leaks.
+ if (isCleared) {
+ closeWithRuntimeException(closeable)
+ return
+ }
+
+ synchronized(lock) { bagOfTags.put(key, closeable) }
+ }
+
+ /**
+ * Add a new [AutoCloseable] object that will be closed directly before
+ * [ViewModel.onCleared] is called.
+ *
+ * If `onCleared()` has already been called, the closeable will not be added,
+ * and will instead be closed immediately.
+ *
+ * @param closeable The object that should be [closed][AutoCloseable.close] directly before
+ * [ViewModel.onCleared] is called.
+ */
+ fun addCloseable(closeable: AutoCloseable) {
+ // Although no logic should be done after user calls onCleared(), we will
+ // ensure that if it has already been called, the closeable attempting to
+ // be added will be closed immediately to ensure there will be no leaks.
+ if (isCleared) {
+ closeWithRuntimeException(closeable)
+ return
+ }
+
+ synchronized(lock) {
+ this.closeables += closeable
+ }
+ }
+
+ /**
+ * Returns the closeable previously added with [ViewModel.addCloseable] with the given [key].
+ *
+ * @param key The key that was used to add the Closeable.
+ */
+ fun <T : AutoCloseable> getCloseable(key: String): T? {
+ synchronized(lock) {
+ @Suppress("UNCHECKED_CAST")
+ return bagOfTags[key] as T?
+ }
+ }
+
+ private fun closeWithRuntimeException(instance: Any) {
+ if (instance is AutoCloseable) {
+ try {
+ instance.close()
+ } catch (e: Exception) {
+ throw RuntimeException(e)
+ }
+ }
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelProviderImpl.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelProviderImpl.kt
new file mode 100644
index 0000000..08ff658
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelProviderImpl.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStore
+import androidx.lifecycle.ViewModelStoreOwner
+import androidx.lifecycle.viewmodel.internal.ViewModelProviders
+import kotlin.reflect.KClass
+
+/**
+ * Internal implementation of the multiplatform [ViewModelProvider].
+ *
+ * Kotlin Multiplatform does not support expect class with default implementation yet, so we
+ * extracted the common logic used by all platforms to this internal class.
+ *
+ * @see <a href="https://youtrack.jetbrains.com/issue/KT-20427">KT-20427</a>
+ */
+internal class ViewModelProviderImpl(
+ private val store: ViewModelStore,
+ private val factory: ViewModelProvider.Factory,
+ private val extras: CreationExtras,
+) {
+
+ constructor(
+ owner: ViewModelStoreOwner,
+ factory: ViewModelProvider.Factory,
+ extras: CreationExtras,
+ ) : this(owner.viewModelStore, factory, extras)
+
+ @Suppress("UNCHECKED_CAST")
+ internal fun <T : ViewModel> getViewModel(
+ modelClass: KClass<T>,
+ key: String = ViewModelProviders.getDefaultKey(modelClass),
+ ): T {
+ val viewModel = store[key]
+ if (modelClass.isInstance(viewModel)) {
+ if (factory is ViewModelProvider.OnRequeryFactory) {
+ factory.onRequery(viewModel!!)
+ }
+ return viewModel as T
+ } else {
+ @Suppress("ControlFlowWithEmptyBody") if (viewModel != null) {
+ // TODO: log a warning.
+ }
+ }
+ val extras = MutableCreationExtras(extras)
+ extras[ViewModelProviders.ViewModelKey] = key
+ // AGP has some desugaring issues associated with compileOnly dependencies so we need to
+ // fall back to the other create method to keep from crashing.
+ return try {
+ factory.create(modelClass, extras)
+ } catch (e: Error) { // There is no `AbstractMethodError` on KMP, using common ancestor.
+ factory.create(modelClass, CreationExtras.Empty)
+ }.also { newViewModel -> store.put(key, newViewModel) }
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelProviders.kt b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelProviders.kt
new file mode 100644
index 0000000..c419e3f
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelProviders.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.lifecycle.viewmodel.internal
+
+import androidx.lifecycle.HasDefaultViewModelProviderFactory
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStoreOwner
+import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.InitializerViewModelFactory
+import androidx.lifecycle.viewmodel.ViewModelInitializer
+import kotlin.reflect.KClass
+
+/**
+ * [ViewModelProviders] provides common helper functionalities.
+ *
+ * Kotlin Multiplatform does not support expect class with default implementation yet, so we
+ * extracted the common logic used by all platforms to this internal class.
+ *
+ * @see <a href="https://youtrack.jetbrains.com/issue/KT-20427">KT-20427</a>
+ */
+internal object ViewModelProviders {
+
+ private const val VIEW_MODEL_PROVIDER_DEFAULT_KEY: String =
+ "androidx.lifecycle.ViewModelProvider.DefaultKey"
+
+ internal fun <T : ViewModel> getDefaultKey(modelClass: KClass<T>): String {
+ val canonicalName = requireNotNull(modelClass.qualifiedName) {
+ "Local and anonymous classes can not be ViewModels"
+ }
+ return "$VIEW_MODEL_PROVIDER_DEFAULT_KEY:$canonicalName"
+ }
+
+ internal object ViewModelKey : CreationExtras.Key<String>
+
+ internal fun <VM : ViewModel> unsupportedCreateViewModel(): VM =
+ throw UnsupportedOperationException(
+ "`Factory.create(String, CreationExtras)` is not implemented. You may need to " +
+ "override the method and provide a custom implementation. Note that using " +
+ "`Factory.create(String)` is not supported and considered an error."
+ )
+
+ internal fun createInitializerFactory(
+ initializers: List<ViewModelInitializer<*>>,
+ ): ViewModelProvider.Factory = InitializerViewModelFactory(*initializers.toTypedArray())
+
+ internal fun createInitializerFactory(
+ vararg initializers: ViewModelInitializer<*>,
+ ): ViewModelProvider.Factory = InitializerViewModelFactory(*initializers)
+
+ internal fun getDefaultFactory(owner: ViewModelStoreOwner): ViewModelProvider.Factory =
+ if (owner is HasDefaultViewModelProviderFactory) {
+ owner.defaultViewModelProviderFactory
+ } else {
+ DefaultViewModelProviderFactory
+ }
+
+ internal fun getDefaultCreationExtras(owner: ViewModelStoreOwner): CreationExtras =
+ if (owner is HasDefaultViewModelProviderFactory) {
+ owner.defaultViewModelCreationExtras
+ } else {
+ CreationExtras.Empty
+ }
+
+ internal fun <VM : ViewModel> createViewModelFromInitializers(
+ modelClass: KClass<VM>,
+ extras: CreationExtras,
+ vararg initializers: ViewModelInitializer<*>,
+ ): VM {
+ @Suppress("UNCHECKED_CAST")
+ val viewModel = initializers.firstOrNull { it.clazz == modelClass }
+ ?.initializer
+ ?.invoke(extras) as VM?
+ return requireNotNull(viewModel) {
+ "No initializer set for given class ${modelClass.qualifiedName}"
+ }
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelLazyTest.kt b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelLazyTest.kt
new file mode 100644
index 0000000..60a764a
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelLazyTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.kruth.assertThat
+import androidx.lifecycle.viewmodel.CreationExtras
+import kotlin.reflect.KClass
+import kotlin.test.Test
+
+class ViewModelLazyTest {
+
+ @Test
+ fun test() {
+ val store = ViewModelStore()
+ val viewModel by ViewModelLazy(
+ viewModelClass = TestViewModel::class,
+ storeProducer = { store },
+ factoryProducer = { TestFactory() },
+ )
+ assertThat(viewModel.prop).isEqualTo(expected = "spb")
+ assertThat(store.keys()).isNotEmpty()
+ }
+
+ private class TestViewModel(val prop: String) : ViewModel()
+
+ private class TestFactory : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T =
+ TestViewModel(prop = "spb") as T
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelProviderGetTest.kt b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelProviderGetTest.kt
new file mode 100644
index 0000000..38ab320
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelProviderGetTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.kruth.assertThat
+import androidx.lifecycle.viewmodel.CreationExtras
+import kotlin.reflect.KClass
+import kotlin.test.Test
+
+class ViewModelProviderGetTest {
+ @Test
+ fun get_withReifiedType() {
+ val factory = object : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T =
+ TestViewModel() as T
+ }
+ val provider = ViewModelProvider.create(ViewModelStore(), factory)
+
+ val viewModel = provider.get<TestViewModel>()
+ assertThat(viewModel).isNotNull()
+ }
+
+ private class TestViewModel : ViewModel()
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelProviderTest.kt b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelProviderTest.kt
new file mode 100644
index 0000000..e694ad0
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelProviderTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.kruth.assertThat
+import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.MutableCreationExtras
+import androidx.lifecycle.viewmodel.internal.ViewModelProviders
+import kotlin.reflect.KClass
+import kotlin.test.Test
+import kotlin.test.fail
+
+class ViewModelProviderTest {
+
+ private val viewModelProvider = ViewModelProvider.create(
+ store = ViewModelStore(),
+ factory = TestViewModelFactory(),
+ )
+
+ @Test
+ fun twoViewModelsWithSameKey() {
+ val key = "the_key"
+ val vm1 = viewModelProvider[key, TestViewModel1::class]
+ assertThat(vm1.cleared).isFalse()
+ val vw2 = viewModelProvider[key, TestViewModel2::class]
+ assertThat(vw2).isNotNull()
+ assertThat(vm1.cleared).isTrue()
+ }
+
+ @Test
+ fun localViewModel() {
+ class LocalViewModel : ViewModel()
+ try {
+ viewModelProvider[LocalViewModel::class]
+ fail("Expected `IllegalArgumentException` but no exception has been throw.")
+ } catch (e: IllegalArgumentException) {
+ assertThat(e).hasCauseThat().isNull()
+ assertThat(e).hasMessageThat()
+ .contains("Local and anonymous classes can not be ViewModels")
+ }
+ }
+
+ @Test
+ fun twoViewModels() {
+ val model1 = viewModelProvider[TestViewModel1::class]
+ val model2 = viewModelProvider[TestViewModel2::class]
+ assertThat(viewModelProvider[TestViewModel1::class]).isSameInstanceAs(model1)
+ assertThat(viewModelProvider[TestViewModel2::class]).isSameInstanceAs(model2)
+ }
+
+ @Test
+ fun testOwnedBy() {
+ val owner = FakeViewModelStoreOwner()
+ val provider =
+ ViewModelProvider.create(owner, TestViewModelFactory())
+ val viewModel = provider[TestViewModel1::class]
+ assertThat(viewModel).isSameInstanceAs(provider[TestViewModel1::class])
+ }
+
+ @Test
+ fun testCustomDefaultFactory() {
+ val store = ViewModelStore()
+ val factory = TestViewModelFactory()
+ val owner = ViewModelStoreOwnerWithFactory(store, factory)
+ val provider = ViewModelProvider.create(owner)
+ val viewModel = provider[TestViewModel1::class]
+ assertThat(viewModel).isSameInstanceAs(provider[TestViewModel1::class])
+ assertThat(factory.called).isEqualTo(1)
+ }
+
+ @Test
+ fun testKeyedFactory() {
+ val owner = FakeViewModelStoreOwner()
+ val explicitlyKeyed: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(
+ modelClass: KClass<T>,
+ extras: CreationExtras
+ ): T {
+ val key = extras[ViewModelProviders.ViewModelKey]
+ assertThat(key).isEqualTo("customKey")
+ @Suppress("UNCHECKED_CAST")
+ return TestViewModel1() as T
+ }
+ }
+ val provider = ViewModelProvider.create(owner, explicitlyKeyed)
+ provider["customKey", TestViewModel1::class]
+ val implicitlyKeyed: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(
+ modelClass: KClass<T>,
+ extras: CreationExtras
+ ): T {
+ val key = extras[ViewModelProviders.ViewModelKey]
+ assertThat(key).isNotNull()
+ @Suppress("UNCHECKED_CAST")
+ return TestViewModel1() as T
+ }
+ }
+ ViewModelProvider.create(
+ owner,
+ implicitlyKeyed
+ )["customKey", TestViewModel1::class]
+ }
+
+ @Test
+ fun testDefaultCreationExtrasWithMutableExtras() {
+ val owner = ViewModelStoreOwnerWithCreationExtras()
+ val wasCalled = BooleanArray(1)
+ val testFactory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(
+ modelClass: KClass<T>,
+ extras: CreationExtras
+ ): T {
+ val mutableKey = object : CreationExtras.Key<String> {}
+ val mutableValue = "value"
+ val mutableExtras = MutableCreationExtras(extras)
+ mutableExtras[mutableKey] = mutableValue
+ val key =
+ mutableExtras[ViewModelProviders.ViewModelKey]
+ assertThat(key).isEqualTo("customKey")
+ assertThat(mutableExtras[TEST_KEY]).isEqualTo(TEST_VALUE)
+ assertThat(mutableExtras[mutableKey]).isEqualTo(mutableValue)
+ wasCalled[0] = true
+ @Suppress("UNCHECKED_CAST")
+ return TestViewModel1() as T
+ }
+ }
+ ViewModelProvider.create(
+ owner,
+ testFactory
+ )["customKey", TestViewModel1::class]
+ assertThat(wasCalled[0]).isTrue()
+ wasCalled[0] = false
+ ViewModelProvider.create(object : ViewModelStoreOwnerWithCreationExtras() {
+ override val defaultViewModelProviderFactory = testFactory
+ })["customKey", TestViewModel1::class]
+ assertThat(wasCalled[0]).isTrue()
+ }
+
+ @Test
+ fun testDefaultCreationExtras() {
+ val owner = ViewModelStoreOwnerWithCreationExtras()
+ val wasCalled = BooleanArray(1)
+ val testFactory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(
+ modelClass: KClass<T>,
+ extras: CreationExtras
+ ): T {
+ val key = extras[ViewModelProviders.ViewModelKey]
+ assertThat(key).isEqualTo("customKey")
+ assertThat(extras[TEST_KEY]).isEqualTo(TEST_VALUE)
+ wasCalled[0] = true
+ @Suppress("UNCHECKED_CAST")
+ return TestViewModel1() as T
+ }
+ }
+ ViewModelProvider.create(
+ owner,
+ testFactory
+ )["customKey", TestViewModel1::class]
+ assertThat(wasCalled[0]).isTrue()
+ wasCalled[0] = false
+ ViewModelProvider.create(object : ViewModelStoreOwnerWithCreationExtras() {
+ override val defaultViewModelProviderFactory = testFactory
+ })["customKey", TestViewModel1::class]
+ assertThat(wasCalled[0]).isTrue()
+ }
+
+ class ViewModelStoreOwnerWithFactory internal constructor(
+ private val mStore: ViewModelStore,
+ private val mFactory: ViewModelProvider.Factory
+ ) : ViewModelStoreOwner, HasDefaultViewModelProviderFactory {
+ override val viewModelStore: ViewModelStore = mStore
+ override val defaultViewModelProviderFactory = mFactory
+ }
+
+ class FakeViewModelStoreOwner internal constructor(
+ store: ViewModelStore = ViewModelStore()
+ ) : ViewModelStoreOwner {
+ override val viewModelStore: ViewModelStore = store
+ }
+
+ private class TestViewModelFactory : ViewModelProvider.Factory {
+ var called = 0
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T {
+ called++
+ return when (modelClass) {
+ TestViewModel1::class -> TestViewModel1()
+ TestViewModel2::class -> TestViewModel2()
+ else -> error("View model class not supported: $modelClass")
+ } as T
+ }
+ }
+
+ private abstract class ClearableViewModel : ViewModel() {
+ var cleared = false
+ private set
+
+ final override fun onCleared() {
+ cleared = true
+ }
+ }
+
+ private class TestViewModel1 : ClearableViewModel()
+
+ private class TestViewModel2 : ClearableViewModel()
+
+ private open class ViewModelStoreOwnerWithCreationExtras : ViewModelStoreOwner,
+ HasDefaultViewModelProviderFactory {
+ private val _viewModelStore = ViewModelStore()
+ override val defaultViewModelProviderFactory: ViewModelProvider.Factory
+ get() = throw UnsupportedOperationException()
+
+ override val defaultViewModelCreationExtras: CreationExtras
+ get() {
+ val extras = MutableCreationExtras()
+ extras[TEST_KEY] = TEST_VALUE
+ return extras
+ }
+
+ override val viewModelStore: ViewModelStore = _viewModelStore
+ }
+}
+
+private val TEST_KEY = object : CreationExtras.Key<String> {}
+private const val TEST_VALUE = "test_value"
diff --git a/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelStoreTest.kt b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelStoreTest.kt
new file mode 100644
index 0000000..8a7000e
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelStoreTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.lifecycle
+
+import androidx.kruth.assertThat
+import kotlin.test.Test
+
+class ViewModelStoreTest {
+
+ @Test
+ fun testClear() {
+ val store = ViewModelStore()
+ val viewModel1 = TestViewModel()
+ val viewModel2 = TestViewModel()
+ store.put(key = "a", viewModel1)
+ store.put(key = "b", viewModel2)
+ assertThat(viewModel1.cleared).isFalse()
+ assertThat(viewModel2.cleared).isFalse()
+ store.clear()
+ assertThat(viewModel1.cleared).isTrue()
+ assertThat(viewModel2.cleared).isTrue()
+ assertThat(store["a"]).isNull()
+ assertThat(store["b"]).isNull()
+ }
+
+ private open class TestViewModel : ViewModel() {
+ var cleared = false
+ public override fun onCleared() {
+ cleared = true
+ }
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelTest.kt b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelTest.kt
new file mode 100644
index 0000000..af37623
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/ViewModelTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:OptIn(ExperimentalStdlibApi::class)
+
+package androidx.lifecycle
+
+import androidx.kruth.assertThat
+import kotlin.test.Test
+
+class ViewModelTest {
+
+ private class CloseableImpl : AutoCloseable {
+ var wasClosed = false
+ override fun close() {
+ wasClosed = true
+ }
+ }
+
+ private class TestViewModel : ViewModel()
+ private class CloseableTestViewModel(closeable: AutoCloseable) : ViewModel(closeable)
+
+ @Test
+ fun testCloseableWithKey() {
+ val vm = TestViewModel()
+ val impl = CloseableImpl()
+ vm.addCloseable("totally_not_coroutine_context", impl)
+ vm.clear()
+ assertThat(impl.wasClosed).isTrue()
+ }
+
+ @Test
+ fun testCloseableWithKeyAlreadyClearedVM() {
+ val vm = TestViewModel()
+ vm.clear()
+ val impl = CloseableImpl()
+ vm.addCloseable("key", impl)
+ assertThat(impl.wasClosed).isTrue()
+ }
+
+ @Test
+ fun testAddCloseable() {
+ val vm = TestViewModel()
+ val impl = CloseableImpl()
+ vm.addCloseable(impl)
+ vm.clear()
+ assertThat(impl.wasClosed).isTrue()
+ }
+
+ @Test
+ fun testAddCloseableAlreadyClearedVM() {
+ val vm = TestViewModel()
+ vm.clear()
+ val impl = CloseableImpl()
+ // This shouldn't crash, even though vm already cleared
+ vm.addCloseable(impl)
+ assertThat(impl.wasClosed).isTrue()
+ }
+
+ @Test
+ fun testConstructorCloseable() {
+ val impl = CloseableImpl()
+ val vm = CloseableTestViewModel(impl)
+ vm.clear()
+ assertThat(impl.wasClosed).isTrue()
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/viewmodel/ViewModelInitializerTest.kt b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/viewmodel/ViewModelInitializerTest.kt
similarity index 84%
rename from lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/viewmodel/ViewModelInitializerTest.kt
rename to lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/viewmodel/ViewModelInitializerTest.kt
index d9d3d1f..7214a33 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidUnitTest/kotlin/androidx/lifecycle/viewmodel/ViewModelInitializerTest.kt
+++ b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/viewmodel/ViewModelInitializerTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,13 +16,10 @@
package androidx.lifecycle.viewmodel
+import androidx.kruth.assertThat
import androidx.lifecycle.ViewModel
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
+import kotlin.test.Test
-@RunWith(JUnit4::class)
class ViewModelInitializerTest {
@Test
fun testInitializerFactory() {
@@ -35,8 +32,8 @@
initializer { TestViewModel1(extras1[key]) }
initializer { TestViewModel2(extras2[key]) }
}
- val viewModel1: TestViewModel1 = factory.create(TestViewModel1::class.java, extras1)
- val viewModel2: TestViewModel2 = factory.create(TestViewModel2::class.java, extras2)
+ val viewModel1: TestViewModel1 = factory.create(TestViewModel1::class, extras1)
+ val viewModel2: TestViewModel2 = factory.create(TestViewModel2::class, extras2)
assertThat(viewModel1.value).isEqualTo(value1)
assertThat(viewModel2.value).isEqualTo(value2)
}
@@ -48,10 +45,10 @@
val extras = MutableCreationExtras().apply { set(key, value) }
val factory = viewModelFactory { }
try {
- factory.create(TestViewModel1::class.java, extras)
+ factory.create(TestViewModel1::class, extras)
} catch (e: IllegalArgumentException) {
assertThat(e).hasMessageThat().isEqualTo(
- "No initializer set for given class ${TestViewModel1::class.java.name}"
+ "No initializer set for given class ${TestViewModel1::class.qualifiedName}"
)
}
}
diff --git a/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelProvidersTest.kt b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelProvidersTest.kt
new file mode 100644
index 0000000..5f53e09
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/commonTest/kotlin/androidx/lifecycle/viewmodel/internal/ViewModelProvidersTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel.internal
+
+import androidx.kruth.assertThat
+import androidx.lifecycle.HasDefaultViewModelProviderFactory
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStore
+import androidx.lifecycle.ViewModelStoreOwner
+import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.MutableCreationExtras
+import kotlin.test.Test
+
+class ViewModelProvidersTest {
+
+ @Test
+ fun getDefaultFactory_ownerWithNoFactory_returnsDefault() {
+ val owner = TestViewModelStoreOwner()
+ val factory = ViewModelProviders.getDefaultFactory(owner)
+ assertThat(factory).isEqualTo(DefaultViewModelProviderFactory)
+ }
+
+ @Test
+ fun getDefaultFactory_ownerWithFactory_returnsExtras() {
+ val customFactory = object : ViewModelProvider.Factory {}
+ val owner = TestViewModelStoreOwnerWithDefaults(
+ defaultViewModelProviderFactory = customFactory,
+ )
+
+ val factory = ViewModelProviders.getDefaultFactory(owner)
+
+ assertThat(factory).isEqualTo(customFactory)
+ }
+
+ @Test
+ fun getDefaultCreationExtras_ownerWithNoExtras_returnsDefault() {
+ val owner = TestViewModelStoreOwner()
+ val extras = ViewModelProviders.getDefaultCreationExtras(owner)
+ assertThat(extras).isEqualTo(CreationExtras.Empty)
+ }
+
+ @Test
+ fun getDefaultCreationExtras_ownerWithExtras_returnsExtras() {
+ val customExtras = MutableCreationExtras()
+ val owner = TestViewModelStoreOwnerWithDefaults(
+ defaultViewModelCreationExtras = customExtras,
+ )
+
+ val extras = ViewModelProviders.getDefaultCreationExtras(owner)
+
+ assertThat(extras).isEqualTo(customExtras)
+ }
+
+ private class TestViewModelStoreOwner(
+ override val viewModelStore: ViewModelStore = ViewModelStore(),
+ ) : ViewModelStoreOwner
+
+ private class TestViewModelStoreOwnerWithDefaults(
+ override val viewModelStore: ViewModelStore = ViewModelStore(),
+ override val defaultViewModelProviderFactory: ViewModelProvider.Factory =
+ DefaultViewModelProviderFactory,
+ override val defaultViewModelCreationExtras: CreationExtras = CreationExtras.Empty,
+ ) : ViewModelStoreOwner, HasDefaultViewModelProviderFactory
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/desktopMain/kotlin/androidx/lifecycle/ViewModelProvider.desktop.kt b/lifecycle/lifecycle-viewmodel/src/desktopMain/kotlin/androidx/lifecycle/ViewModelProvider.desktop.kt
new file mode 100644
index 0000000..06e7b56
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/desktopMain/kotlin/androidx/lifecycle/ViewModelProvider.desktop.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.annotation.MainThread
+import androidx.annotation.RestrictTo
+import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.ViewModelProviderImpl
+import androidx.lifecycle.viewmodel.internal.ViewModelProviders
+import kotlin.reflect.KClass
+
+public actual class ViewModelProvider private constructor(
+ private val impl: ViewModelProviderImpl,
+) {
+
+ @MainThread
+ public actual operator fun <T : ViewModel> get(modelClass: KClass<T>): T =
+ impl.getViewModel(modelClass)
+
+ @MainThread
+ public actual operator fun <T : ViewModel> get(
+ key: String,
+ modelClass: KClass<T>,
+ ): T = impl.getViewModel(modelClass, key)
+
+ public actual interface Factory {
+ public actual fun <T : ViewModel> create(
+ modelClass: KClass<T>,
+ extras: CreationExtras,
+ ): T = ViewModelProviders.unsupportedCreateViewModel()
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public actual open class OnRequeryFactory {
+ public actual open fun onRequery(viewModel: ViewModel) {}
+ }
+
+ public actual companion object {
+ @JvmStatic
+ public actual fun create(
+ owner: ViewModelStoreOwner,
+ factory: Factory,
+ extras: CreationExtras,
+ ): ViewModelProvider =
+ ViewModelProvider(ViewModelProviderImpl(owner.viewModelStore, factory, extras))
+
+ @JvmStatic
+ public actual fun create(
+ store: ViewModelStore,
+ factory: Factory,
+ extras: CreationExtras
+ ): ViewModelProvider = ViewModelProvider(ViewModelProviderImpl(store, factory, extras))
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/desktopMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.desktop.kt b/lifecycle/lifecycle-viewmodel/src/desktopMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.desktop.kt
new file mode 100644
index 0000000..5c73169
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/desktopMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.desktop.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.internal.ViewModelProviders
+import kotlin.reflect.KClass
+
+public actual class ViewModelInitializer<T : ViewModel>
+actual constructor(
+ internal actual val clazz: KClass<T>,
+ internal actual val initializer: CreationExtras.() -> T,
+)
+
+internal actual class InitializerViewModelFactory
+actual constructor(
+ private vararg val initializers: ViewModelInitializer<*>
+) : ViewModelProvider.Factory {
+
+ override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T =
+ ViewModelProviders.createViewModelFromInitializers(modelClass, extras, *initializers)
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/ViewModel.jvm.kt b/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/ViewModel.jvm.kt
new file mode 100644
index 0000000..d26032b
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/ViewModel.jvm.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("ViewModel")
+
+package androidx.lifecycle
+
+import androidx.annotation.MainThread
+import androidx.lifecycle.viewmodel.internal.ViewModelImpl
+import java.io.Closeable
+
+public actual abstract class ViewModel {
+
+ /**
+ * Internal implementation of the multiplatform [ViewModel].
+ *
+ * **Why is it nullable?** Since [clear] is final, this method is still called on mock
+ * objects. In those cases, [impl] is `null`. It'll always be empty though because
+ * [addCloseable] and [getCloseable] are open so we can skip clearing it.
+ */
+ private val impl: ViewModelImpl?
+
+ public actual constructor() {
+ impl = ViewModelImpl()
+ }
+
+ public actual constructor(vararg closeables: AutoCloseable) {
+ impl = ViewModelImpl(*closeables)
+ }
+
+ /**
+ * Construct a new ViewModel instance. Any [AutoCloseable] objects provided here
+ * will be closed directly before [ViewModel.onCleared] is called.
+ *
+ * You should **never** manually construct a ViewModel outside of a
+ * [ViewModelProvider.Factory].
+ */
+ @Deprecated(message = "Replaced by `AutoCloseable` overload.", level = DeprecationLevel.HIDDEN)
+ public constructor(vararg closeables: Closeable) {
+ impl = ViewModelImpl(*closeables)
+ }
+
+ protected actual open fun onCleared() {}
+
+ @MainThread
+ internal actual fun clear() {
+ impl?.clear()
+ onCleared()
+ }
+
+ public actual fun addCloseable(key: String, closeable: AutoCloseable) {
+ impl?.addCloseable(key, closeable)
+ }
+
+ public actual open fun addCloseable(closeable: AutoCloseable) {
+ impl?.addCloseable(closeable)
+ }
+
+ /**
+ * Add a new [Closeable] object that will be closed directly before
+ * [ViewModel.onCleared] is called.
+ *
+ * If `onCleared()` has already been called, the closeable will not be added,
+ * and will instead be closed immediately.
+ *
+ * @param closeable The object that should be [closed][Closeable.close] directly before
+ * [ViewModel.onCleared] is called.
+ */
+ @Deprecated(message = "Replaced by `AutoCloseable` overload.", level = DeprecationLevel.HIDDEN)
+ public open fun addCloseable(closeable: Closeable) {
+ impl?.addCloseable(closeable)
+ }
+
+ public actual fun <T : AutoCloseable> getCloseable(key: String): T? = impl?.getCloseable(key)
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactory.jvm.kt b/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactory.jvm.kt
new file mode 100644
index 0000000..fa4d3f2
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactory.jvm.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel.internal
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.CreationExtras
+import kotlin.reflect.KClass
+
+internal actual object DefaultViewModelProviderFactory : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T =
+ JvmViewModelProviders.createViewModel(modelClass.java)
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/viewmodel/internal/JvmViewModelProviders.kt b/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/viewmodel/internal/JvmViewModelProviders.kt
new file mode 100644
index 0000000..6b22a49
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/jvmMain/kotlin/androidx/lifecycle/viewmodel/internal/JvmViewModelProviders.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel.internal
+
+import androidx.lifecycle.ViewModel
+
+/**
+ * [JvmViewModelProviders] provides common helper functionalities.
+ *
+ * Kotlin Multiplatform does not support expect class with default implementation yet, so we
+ * extracted the common logic used by all platforms to this internal class.
+ *
+ * @see <a href="https://youtrack.jetbrains.com/issue/KT-20427">KT-20427</a>
+ */
+internal object JvmViewModelProviders {
+
+ /**
+ * Creates a new [ViewModel] instance using the no-args constructor if available, otherwise
+ * throws a [RuntimeException].
+ */
+ @Suppress("DocumentExceptions")
+ fun <T : ViewModel> createViewModel(modelClass: Class<T>): T =
+ try {
+ modelClass.getDeclaredConstructor().newInstance()
+ } catch (e: NoSuchMethodException) {
+ throw RuntimeException("Cannot create an instance of $modelClass", e)
+ } catch (e: InstantiationException) {
+ throw RuntimeException("Cannot create an instance of $modelClass", e)
+ } catch (e: IllegalAccessException) {
+ throw RuntimeException("Cannot create an instance of $modelClass", e)
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/jvmTest/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactoryTest.kt b/lifecycle/lifecycle-viewmodel/src/jvmTest/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactoryTest.kt
new file mode 100644
index 0000000..dab5b2c
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/jvmTest/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactoryTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel.internal
+
+import androidx.kruth.assertThat
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewmodel.CreationExtras
+import kotlin.test.Test
+import kotlin.test.fail
+
+class DefaultViewModelProviderFactoryTest {
+
+ @Test
+ fun create_withConstructorWithZeroArguments_returnsViewModel() {
+ val modelClass = TestViewModel1::class
+ val factory = DefaultViewModelProviderFactory
+
+ val viewModel = factory.create(modelClass, CreationExtras.Empty)
+
+ assertThat(viewModel).isNotNull()
+ }
+
+ @Test
+ fun create_withConstructorWithOneArgument_throwsNoSuchMethodException() {
+ val modelClass = TestViewModel2::class
+ val factory = DefaultViewModelProviderFactory
+ try {
+ factory.create(modelClass, CreationExtras.Empty)
+ fail("Expected `IllegalArgumentException` but no exception has been throw.")
+ } catch (e: RuntimeException) {
+ assertThat(e).hasCauseThat().isInstanceOf<NoSuchMethodException>()
+ assertThat(e).hasMessageThat()
+ .contains("Cannot create an instance of class ${TestViewModel2::class.java.name}")
+ }
+ }
+
+ @org.junit.Test
+ fun create_withPrivateConstructor_throwsIllegalAccessException() {
+ val modelClass = TestViewModel3::class
+ val factory = DefaultViewModelProviderFactory
+ try {
+ factory.create(modelClass, CreationExtras.Empty)
+ fail("Expected `IllegalArgumentException` but no exception has been throw.")
+ } catch (e: RuntimeException) {
+ assertThat(e).hasCauseThat().isInstanceOf<IllegalAccessException>()
+ assertThat(e).hasMessageThat()
+ .contains("Cannot create an instance of class ${TestViewModel3::class.java.name}")
+ }
+ }
+
+ @Test
+ fun create_withAbstractConstructor_throwsInstantiationException() {
+ val modelClass = ViewModel::class
+ val factory = DefaultViewModelProviderFactory
+ try {
+ factory.create(modelClass, CreationExtras.Empty)
+ fail("Expected `IllegalArgumentException` but no exception has been throw.")
+ } catch (e: RuntimeException) {
+ assertThat(e).hasCauseThat().isInstanceOf<InstantiationException>()
+ assertThat(e).hasMessageThat()
+ .contains("Cannot create an instance of class ${ViewModel::class.java.name}")
+ }
+ }
+
+ class TestViewModel1 : ViewModel()
+ class TestViewModel2(@Suppress("unused") val unused: Int) : ViewModel()
+ private class TestViewModel3 private constructor() : ViewModel()
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/ViewModel.native.kt b/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/ViewModel.native.kt
new file mode 100644
index 0000000..6106d62
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/ViewModel.native.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:OptIn(ExperimentalStdlibApi::class)
+
+package androidx.lifecycle
+
+import androidx.annotation.MainThread
+import androidx.lifecycle.viewmodel.internal.ViewModelImpl
+
+public actual abstract class ViewModel {
+
+ private val impl: ViewModelImpl
+
+ public actual constructor() {
+ impl = ViewModelImpl()
+ }
+
+ public actual constructor(vararg closeables: AutoCloseable) {
+ impl = ViewModelImpl(*closeables)
+ }
+
+ protected actual open fun onCleared() {}
+
+ @MainThread
+ internal actual fun clear() {
+ impl.clear()
+ onCleared()
+ }
+
+ public actual fun addCloseable(key: String, closeable: AutoCloseable) {
+ impl.addCloseable(key, closeable)
+ }
+
+ public actual open fun addCloseable(closeable: AutoCloseable) {
+ impl.addCloseable(closeable)
+ }
+
+ public actual fun <T : AutoCloseable> getCloseable(key: String): T? = impl.getCloseable(key)
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/ViewModelProvider.native.kt b/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/ViewModelProvider.native.kt
new file mode 100644
index 0000000..e1d0c62
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/ViewModelProvider.native.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.annotation.MainThread
+import androidx.annotation.RestrictTo
+import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.ViewModelProviderImpl
+import androidx.lifecycle.viewmodel.internal.ViewModelProviders
+import kotlin.reflect.KClass
+
+public actual class ViewModelProvider private constructor(
+ private val impl: ViewModelProviderImpl,
+) {
+
+ @MainThread
+ public actual operator fun <T : ViewModel> get(modelClass: KClass<T>): T =
+ impl.getViewModel(modelClass)
+
+ @MainThread
+ public actual operator fun <T : ViewModel> get(
+ key: String,
+ modelClass: KClass<T>,
+ ): T = impl.getViewModel(modelClass, key)
+
+ public actual interface Factory {
+ public actual fun <T : ViewModel> create(
+ modelClass: KClass<T>,
+ extras: CreationExtras,
+ ): T = ViewModelProviders.unsupportedCreateViewModel()
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public actual open class OnRequeryFactory {
+ public actual open fun onRequery(viewModel: ViewModel) {}
+ }
+
+ public actual companion object {
+ public actual fun create(
+ owner: ViewModelStoreOwner,
+ factory: Factory,
+ extras: CreationExtras,
+ ): ViewModelProvider =
+ ViewModelProvider(ViewModelProviderImpl(owner.viewModelStore, factory, extras))
+
+ public actual fun create(
+ store: ViewModelStore,
+ factory: Factory,
+ extras: CreationExtras
+ ): ViewModelProvider = ViewModelProvider(ViewModelProviderImpl(store, factory, extras))
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.native.kt b/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.native.kt
new file mode 100644
index 0000000..5c73169
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/viewmodel/InitializerViewModelFactory.native.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.internal.ViewModelProviders
+import kotlin.reflect.KClass
+
+public actual class ViewModelInitializer<T : ViewModel>
+actual constructor(
+ internal actual val clazz: KClass<T>,
+ internal actual val initializer: CreationExtras.() -> T,
+)
+
+internal actual class InitializerViewModelFactory
+actual constructor(
+ private vararg val initializers: ViewModelInitializer<*>
+) : ViewModelProvider.Factory {
+
+ override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T =
+ ViewModelProviders.createViewModelFromInitializers(modelClass, extras, *initializers)
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactory.native.kt b/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactory.native.kt
new file mode 100644
index 0000000..cef88bf
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/nativeMain/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactory.native.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel.internal
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.CreationExtras
+import kotlin.reflect.KClass
+
+internal actual object DefaultViewModelProviderFactory : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T =
+ ViewModelProviders.unsupportedCreateViewModel()
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/nativeTest/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactoryTest.kt b/lifecycle/lifecycle-viewmodel/src/nativeTest/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactoryTest.kt
new file mode 100644
index 0000000..cb8b852
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/nativeTest/kotlin/androidx/lifecycle/viewmodel/internal/DefaultViewModelProviderFactoryTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel.internal
+
+import androidx.kruth.assertThat
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewmodel.CreationExtras
+import kotlin.test.Test
+import kotlin.test.fail
+
+class DefaultViewModelProviderFactoryTest {
+
+ @Test
+ fun create_throwsUnsupportedOperationException() {
+ val modelClass = ViewModel::class
+ try {
+ DefaultViewModelProviderFactory.create(modelClass, CreationExtras.Empty)
+ fail("Expected `UnsupportedOperationException` but no exception has been throw.")
+ } catch (e: UnsupportedOperationException) {
+ assertThat(e).hasCauseThat().isNull()
+ assertThat(e).hasMessageThat().contains(
+ "`Factory.create(String, CreationExtras)` is not implemented. You may need to " +
+ "override the method and provide a custom implementation. Note that using " +
+ "`Factory.create(String)` is not supported and considered an error."
+ )
+ }
+ }
+}
diff --git a/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt b/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt
index 33b8853..092417f 100644
--- a/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt
@@ -219,6 +219,7 @@
val annotationGroupId = annotationCoordinates.groupId
+ val isUsedInAlpha = usageCoordinates.version.contains("-alpha")
val isUsedInSameGroup = usageCoordinates.groupId == annotationCoordinates.groupId
val isUsedInSameArtifact = usageCoordinates.artifactId == annotationCoordinates.artifactId
val isAtomic = atomicGroupList.contains(usageGroupId)
@@ -226,11 +227,13 @@
/**
* Usage of experimental APIs is allowed in either of the following conditions:
*
+ * - The usage is in an alpha library
* - Both the group ID and artifact ID in `usageCoordinates` and
* `annotationCoordinates` match
* - The group IDs match, and that group ID is atomic
*/
- if ((isUsedInSameGroup && isUsedInSameArtifact) ||
+ if (isUsedInAlpha ||
+ (isUsedInSameGroup && isUsedInSameArtifact) ||
(isUsedInSameGroup && isAtomic)) return
// Log inappropriate experimental usage
diff --git a/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt b/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
index 2d6ad74..ab50329 100644
--- a/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
@@ -48,6 +48,7 @@
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtModifierListOwner
+import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
import org.jetbrains.uast.UDeclaration
import org.jetbrains.uast.UMethod
@@ -249,8 +250,14 @@
analyze(source) {
val member = (source as? KtDeclaration)?.getSymbol() ?: return
val expect = member.getExpectsForActual().singleOrNull() ?: return
- (expect.psi as? KtDeclaration)?.docComment?.let {
- handleSampleLink(it)
+ val declaration = expect.psi ?: return
+ // Recursively handle everything inside the expect declaration, for example if it
+ // is a class with members that have documentation that we should look at - this
+ // will visit the declaration itself as well
+ declaration.forEachDescendantOfType<KtDeclaration> {
+ it.docComment?.let { comment ->
+ handleSampleLink(comment)
+ }
}
}
}
diff --git a/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt b/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt
index 422201a..425267a 100644
--- a/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt
@@ -205,4 +205,55 @@
check(provider, consumer).expect(expected)
}
+
+ @Test
+ fun `Test cross-module Experimental usage in alpha via Gradle model`() {
+
+ /* ktlint-disable max-line-length */
+ val provider = project()
+ .name("provider")
+ .type(ProjectDescription.Type.LIBRARY)
+ .report(false)
+ .files(
+ JetpackRequiresOptIn,
+ ktSample("sample.annotation.provider.ExperimentalSampleAnnotation"),
+ javaSample("sample.annotation.provider.ExperimentalSampleAnnotationJava"),
+ javaSample("sample.annotation.provider.RequiresOptInSampleAnnotationJava"),
+ javaSample("sample.annotation.provider.RequiresOptInSampleAnnotationJavaDuplicate"),
+ javaSample("sample.annotation.provider.RequiresAndroidXOptInSampleAnnotationJava"),
+ javaSample("sample.annotation.provider.RequiresAndroidXOptInSampleAnnotationJavaDuplicate"),
+ gradle(
+ """
+ apply plugin: 'com.android.library'
+ group=sample.annotation.provider
+ version=1.0.0-beta02
+ """
+ ).indented(),
+ )
+ /* ktlint-enable max-line-length */
+
+ val consumer = project()
+ .name("consumer")
+ .type(ProjectDescription.Type.LIBRARY)
+ .dependsOn(provider)
+ .files(
+ JetpackOptIn,
+ ktSample("androidx.sample.consumer.OutsideGroupExperimentalAnnotatedClass"),
+ gradle(
+ """
+ apply plugin: 'com.android.library'
+ group=androidx.sample.consumer
+ version=1.0.0-alpha01
+ """
+ ).indented(),
+ )
+
+ /* ktlint-disable max-line-length */
+ val expected = """
+No warnings.
+ """.trimIndent()
+ /* ktlint-enable max-line-length */
+
+ check(provider, consumer).expect(expected)
+ }
}
diff --git a/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt b/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt
index 1f720cb..e0a9e70 100644
--- a/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt
+++ b/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt
@@ -29,6 +29,7 @@
override val issues = listOf(
EagerConfigurationDetector.ISSUE,
InternalApiUsageDetector.ISSUE,
+ WithPluginClasspathUsageDetector.ISSUE,
)
override val vendor = Vendor(
diff --git a/lint/lint-gradle/src/main/java/androidx/lint/gradle/WithPluginClasspathUsageDetector.kt b/lint/lint-gradle/src/main/java/androidx/lint/gradle/WithPluginClasspathUsageDetector.kt
new file mode 100644
index 0000000..22bcafb
--- /dev/null
+++ b/lint/lint-gradle/src/main/java/androidx/lint/gradle/WithPluginClasspathUsageDetector.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lint.gradle
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+class WithPluginClasspathUsageDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableMethodNames(): List<String> = listOf("withPluginClasspath")
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ val evaluator = context.evaluator
+
+ val message = "Avoid usage of GradleRunner#withPluginClasspath, which is broken. " +
+ "Instead use something like https://github.com/autonomousapps/" +
+ "dependency-analysis-gradle-plugin/tree/main/testkit#gradle-testkit-support-plugin"
+
+ val incident = Incident(context)
+ .issue(ISSUE)
+ .location(context.getNameLocation(node))
+ .message(message)
+ .scope(node)
+
+ if (evaluator.isMemberInClass(node.resolve(), "org.gradle.testkit.runner.GradleRunner")) {
+ context.report(incident)
+ }
+ }
+
+ companion object {
+ val ISSUE: Issue = Issue.create(
+ id = "WithPluginClasspathUsage",
+ briefDescription = "Flags usage of GradleRunner#withPluginClasspath",
+ explanation = """
+ This check flags usage of `GradleRunner#withPluginClasspath` in tests,
+ as it might lead to potential issues or it is discouraged in certain contexts.
+ """,
+ category = Category.CORRECTNESS,
+ priority = 5,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ WithPluginClasspathUsageDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+}
diff --git a/lint/lint-gradle/src/test/java/androidx/lint/gradle/Stubs.kt b/lint/lint-gradle/src/test/java/androidx/lint/gradle/Stubs.kt
index 730d09b..fdeb2ce 100644
--- a/lint/lint-gradle/src/test/java/androidx/lint/gradle/Stubs.kt
+++ b/lint/lint-gradle/src/test/java/androidx/lint/gradle/Stubs.kt
@@ -130,5 +130,19 @@
package com.android.build.gradle.internal.lint
abstract class VariantInputs
""".trimIndent()
+ ),
+ kotlin(
+ """
+ package org.gradle.testkit.runner
+
+ class GradleRunner {
+ companion object {
+ fun create(): GradleRunner = GradleRunner()
+ }
+ fun withProjectDir(projectDir: java.io.File): GradleRunner = this
+ fun withPluginClasspath(): GradleRunner = this
+ fun build(): org.gradle.testkit.runner.BuildResult = TODO()
+ }
+ """.trimIndent()
)
)
diff --git a/lint/lint-gradle/src/test/java/androidx/lint/gradle/WithPluginClasspathUsageDetectorTest.kt b/lint/lint-gradle/src/test/java/androidx/lint/gradle/WithPluginClasspathUsageDetectorTest.kt
new file mode 100644
index 0000000..b3a1c1e
--- /dev/null
+++ b/lint/lint-gradle/src/test/java/androidx/lint/gradle/WithPluginClasspathUsageDetectorTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lint.gradle
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class WithPluginClasspathUsageDetectorTest : GradleLintDetectorTest(
+ detector = WithPluginClasspathUsageDetector(),
+ issues = listOf(WithPluginClasspathUsageDetector.ISSUE)
+) {
+ @Test
+ fun `Test withPluginClassPath usage`() {
+
+ val input = kotlin(
+ """
+ import org.gradle.testkit.runner.GradleRunner
+ import java.io.File
+
+ class TestClass {
+ fun testMethod() {
+ GradleRunner.create()
+ .withProjectDir(File("path/to/project"))
+ .withPluginClasspath()
+ .build()
+ }
+ }
+ """.trimIndent()
+ )
+
+ val message = "Avoid usage of GradleRunner#withPluginClasspath, which is broken. " +
+ "Instead use something like https://github.com/autonomousapps/" +
+ "dependency-analysis-gradle-plugin/tree/main/testkit#gradle-testkit-support-plugin"
+
+ val expected = """
+ src/TestClass.kt:8: Error: $message [WithPluginClasspathUsage]
+ .withPluginClasspath()
+ ~~~~~~~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.trimIndent()
+
+ check(input).expect(expected)
+ }
+}
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RemotePlaybackClient.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RemotePlaybackClient.java
index d35efb8..51fbfe1 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RemotePlaybackClient.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/RemotePlaybackClient.java
@@ -701,8 +701,10 @@
+ ", itemId=" + itemIdResult
+ ", itemStatus=" + itemStatus);
}
- callback.onResult(data, sessionIdResult, sessionStatus,
- itemIdResult, itemStatus);
+ if (callback != null) {
+ callback.onResult(data, sessionIdResult, sessionStatus,
+ itemIdResult, itemStatus);
+ }
return;
}
}
@@ -773,7 +775,9 @@
Bundle data) {
Log.w(TAG, "Received invalid result data from " + intent.getAction()
+ ": data=" + bundleToString(data));
- callback.onError(null, MediaControlIntent.ERROR_UNKNOWN, data);
+ if (callback != null) {
+ callback.onError(null, MediaControlIntent.ERROR_UNKNOWN, data);
+ }
}
void handleError(Intent intent, ActionCallback callback,
@@ -791,7 +795,9 @@
+ ", code=" + code
+ ", data=" + bundleToString(data));
}
- callback.onError(error, code, data);
+ if (callback != null) {
+ callback.onError(error, code, data);
+ }
}
private void detectFeatures() {
diff --git a/navigation/navigation-common/api/current.ignore b/navigation/navigation-common/api/current.ignore
index b244952..db54ad9 100644
--- a/navigation/navigation-common/api/current.ignore
+++ b/navigation/navigation-common/api/current.ignore
@@ -1,3 +1,3 @@
// Baseline format: 1.0
ChangedType: androidx.navigation.NavDestination.ClassType#value():
- Method androidx.navigation.NavDestination.ClassType.value has changed return type from kotlin.reflect.KClass<?> to Class<?>
+ Method androidx.navigation.NavDestination.ClassType.value has changed return type from kotlin.reflect.KClass<?> to java.lang.Class<?>
diff --git a/navigation/navigation-common/api/restricted_current.ignore b/navigation/navigation-common/api/restricted_current.ignore
index b244952..db54ad9 100644
--- a/navigation/navigation-common/api/restricted_current.ignore
+++ b/navigation/navigation-common/api/restricted_current.ignore
@@ -1,3 +1,3 @@
// Baseline format: 1.0
ChangedType: androidx.navigation.NavDestination.ClassType#value():
- Method androidx.navigation.NavDestination.ClassType.value has changed return type from kotlin.reflect.KClass<?> to Class<?>
+ Method androidx.navigation.NavDestination.ClassType.value has changed return type from kotlin.reflect.KClass<?> to java.lang.Class<?>
diff --git a/navigation/navigation-common/build.gradle b/navigation/navigation-common/build.gradle
index 77c8ed9..b5e5e8d 100644
--- a/navigation/navigation-common/build.gradle
+++ b/navigation/navigation-common/build.gradle
@@ -27,6 +27,7 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
+ alias(libs.plugins.kotlinSerialization)
}
android {
@@ -49,6 +50,7 @@
implementation("androidx.core:core-ktx:1.1.0")
implementation("androidx.collection:collection-ktx:1.1.0")
implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+ implementation(libs.kotlinSerializationCore)
api(libs.kotlinStdlib)
testImplementation(project(":navigation:navigation-testing"))
@@ -58,6 +60,7 @@
testImplementation(libs.truth)
testImplementation(libs.kotlinStdlib)
testImplementation(libs.kotlinCoroutinesTest)
+ testImplementation(libs.kotlinTest)
androidTestImplementation(libs.kotlinTestJunit)
androidTestImplementation(libs.testExtJunit)
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavArgumentTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavArgumentTest.kt
index 29b5117..d6b19e3 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavArgumentTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavArgumentTest.kt
@@ -64,4 +64,15 @@
assertThat(intArrArgument.verify("intArrayArg", bundle)).isTrue()
assertThat(intArrNonNullArgument.verify("intArrayArg", bundle)).isFalse()
}
+
+ @Test
+ fun setDefaultValuePresent() {
+ val argument = NavArgument.Builder()
+ .setType(NavType.IntType)
+ .setIsNullable(false)
+ .setUnknownDefaultValuePresent(true)
+ .build()
+
+ assertThat(argument.isDefaultValuePresent).isTrue()
+ }
}
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
index 95b5385..9364be4 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
@@ -140,6 +140,25 @@
}
}
}
+
+ @Test
+ fun navDestinationDefaultValuePresent() {
+ val destination = provider.navDestination(DESTINATION_ID) {
+ argument("arg1") {
+ type = NavType.StringType
+ unknownDefaultValuePresent = true
+ }
+ argument("arg2") {
+ type = NavType.StringType
+ unknownDefaultValuePresent = false
+ }
+ }
+ val arg1 = destination.arguments["arg1"]
+ assertThat(arg1?.isDefaultValuePresent).isTrue()
+
+ val arg2 = destination.arguments["arg2"]
+ assertThat(arg2?.isDefaultValuePresent).isFalse()
+ }
}
private const val DESTINATION_ID = 1
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavArgument.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavArgument.kt
index 741e07b..a8eb63a 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavArgument.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavArgument.kt
@@ -28,7 +28,8 @@
type: NavType<Any?>,
isNullable: Boolean,
defaultValue: Any?,
- defaultValuePresent: Boolean
+ defaultValuePresent: Boolean,
+ unknownDefaultValuePresent: Boolean,
) {
/**
* The type of this NavArgument.
@@ -120,6 +121,7 @@
private var isNullable = false
private var defaultValue: Any? = null
private var defaultValuePresent = false
+ private var unknownDefaultValuePresent = false
/**
* Set the type of the argument.
@@ -157,6 +159,21 @@
}
/**
+ * Set whether there is an unknown default value present.
+ *
+ * Use with caution!! In general you should let [setDefaultValue] to automatically set
+ * this state. This state should be set to true only if all these conditions are met:
+ *
+ * 1. There is default value present
+ * 2. You do not have access to actual default value (thus you can't use [defaultValue])
+ * 3. You know the default value will never ever be null if [isNullable] is true.
+ */
+ internal fun setUnknownDefaultValuePresent(unknownDefaultValuePresent: Boolean): Builder {
+ this.unknownDefaultValuePresent = unknownDefaultValuePresent
+ return this
+ }
+
+ /**
* Build the NavArgument specified by this builder.
* If the type is not set, the builder will infer the type from the default argument value.
* If there is no default value, the type will be unspecified.
@@ -164,7 +181,13 @@
*/
public fun build(): NavArgument {
val finalType = type ?: NavType.inferFromValueType(defaultValue) as NavType<Any?>
- return NavArgument(finalType, isNullable, defaultValue, defaultValuePresent)
+ return NavArgument(
+ finalType,
+ isNullable,
+ defaultValue,
+ defaultValuePresent,
+ unknownDefaultValuePresent
+ )
}
}
@@ -178,7 +201,7 @@
this.type = type
this.isNullable = isNullable
this.defaultValue = defaultValue
- isDefaultValuePresent = defaultValuePresent
+ isDefaultValuePresent = defaultValuePresent || unknownDefaultValuePresent
}
}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
index d370c0e..237dd1f 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
@@ -251,6 +251,22 @@
}
/**
+ * Set whether there is an unknown default value present.
+ *
+ * Use with caution!! In general you should let [defaultValue] to automatically set this state.
+ * This state should be set to true only if all these conditions are met:
+ *
+ * 1. There is default value present
+ * 2. You do not have access to actual default value (thus you can't use [defaultValue])
+ * 3. You know the default value will never ever be null if [nullable] is true.
+ */
+ internal var unknownDefaultValuePresent: Boolean = false
+ set(value) {
+ field = value
+ builder.setUnknownDefaultValuePresent(value)
+ }
+
+ /**
* Builds the NavArgument by calling [NavArgument.Builder.build].
*/
public fun build(): NavArgument {
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
index 4b367cc..61f52df 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
@@ -184,34 +184,13 @@
} else {
type
}
- if (type.endsWith("[]")) {
- className = className.substring(0, className.length - 2)
- val clazz = Class.forName(className)
- when {
- Parcelable::class.java.isAssignableFrom(clazz) -> {
- return ParcelableArrayType(clazz as Class<Parcelable>)
- }
- Serializable::class.java.isAssignableFrom(clazz) -> {
- return SerializableArrayType(clazz as Class<Serializable>)
- }
- }
- } else {
- val clazz = Class.forName(className)
- when {
- Parcelable::class.java.isAssignableFrom(clazz) -> {
- return ParcelableType(clazz as Class<Any?>)
- }
- Enum::class.java.isAssignableFrom(clazz) -> {
- return EnumType(clazz as Class<Enum<*>>)
- }
- Serializable::class.java.isAssignableFrom(clazz) -> {
- return SerializableType(clazz as Class<Serializable>)
- }
- }
- }
- throw IllegalArgumentException(
+ val isArray = type.endsWith("[]")
+ if (isArray) className = className.substring(0, className.length - 2)
+ return requireNotNull(
+ parseSerializableOrParcelableType(className, isArray)
+ ) {
"$className is not Serializable or Parcelable."
- )
+ }
} catch (e: ClassNotFoundException) {
throw RuntimeException(e)
}
@@ -220,6 +199,33 @@
return StringType
}
+ @Suppress("UNCHECKED_CAST")
+ internal fun parseSerializableOrParcelableType(
+ className: String,
+ isArray: Boolean
+ ): NavType<*>? {
+ val clazz = Class.forName(className)
+ return when {
+ Parcelable::class.java.isAssignableFrom(clazz) -> {
+ if (isArray) {
+ ParcelableArrayType(clazz as Class<Parcelable>)
+ } else {
+ ParcelableType(clazz as Class<Any?>)
+ }
+ }
+ Enum::class.java.isAssignableFrom(clazz) && !isArray ->
+ EnumType(clazz as Class<Enum<*>>)
+ Serializable::class.java.isAssignableFrom(clazz) -> {
+ if (isArray) {
+ SerializableArrayType(clazz as Class<Serializable>)
+ } else {
+ return SerializableType(clazz as Class<Serializable>)
+ }
+ }
+ else -> null
+ }
+ }
+
@Suppress("UNCHECKED_CAST") // needed for cast to NavType<Any>
@JvmStatic
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/NavTypeConverter.kt b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/NavTypeConverter.kt
new file mode 100644
index 0000000..17ee8b6
--- /dev/null
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/NavTypeConverter.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalSerializationApi::class)
+
+package androidx.navigation.serialization
+
+import androidx.navigation.NavType
+import androidx.navigation.NavType.Companion.parseSerializableOrParcelableType
+import kotlin.reflect.KType
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.descriptors.SerialDescriptor
+
+private interface InternalCommonType
+
+/**
+ * Marker for Native Kotlin Primitives and Collections
+ */
+private enum class Native : InternalCommonType {
+ INT,
+ BOOL,
+ FLOAT,
+ LONG,
+ STRING,
+ INT_ARRAY,
+ BOOL_ARRAY,
+ FLOAT_ARRAY,
+ LONG_ARRAY,
+ ARRAY,
+ ARRAY_LIST,
+ SET,
+ HASHSET,
+ MAP,
+ HASH_MAP,
+ UNKNOWN
+}
+
+/**
+ * Marker for custom classes, objects, enums, and other Native Kotlin types that are not
+ * included in [Native]
+ */
+private data class Custom(val className: String) : InternalCommonType
+
+/**
+ * Converts an argument type to a native NavType.
+ *
+ * Native NavTypes includes NavType objects declared within [NavType.Companion], or types that are
+ * either java Serializable, Parcelable, or Enum.
+ *
+ * Throws IllegalArgumentException if the argument does not belong to any of the above
+ */
+internal fun SerialDescriptor.getNavType(): NavType<*> {
+ return when (val internalType = this.toInternalType()) {
+ Native.INT -> NavType.IntType
+ Native.BOOL -> NavType.BoolType
+ Native.FLOAT -> NavType.FloatType
+ Native.LONG -> NavType.LongType
+ Native.STRING -> NavType.StringType
+ Native.INT_ARRAY -> NavType.IntArrayType
+ Native.BOOL_ARRAY -> NavType.BoolArrayType
+ Native.FLOAT_ARRAY -> NavType.FloatArrayType
+ Native.LONG_ARRAY -> NavType.LongArrayType
+ Native.ARRAY -> {
+ val typeParameter = getElementDescriptor(0).toInternalType()
+ if (typeParameter == Native.STRING) return NavType.StringArrayType
+ if (typeParameter is Custom) {
+ return requireNotNull(
+ convertCustomToNavType(typeParameter.className, true)
+ )
+ }
+ throw IllegalArgumentException()
+ }
+ is Custom -> {
+ return requireNotNull(
+ convertCustomToNavType(internalType.className, false)
+ )
+ }
+ else -> throw IllegalArgumentException()
+ }
+}
+
+private fun convertCustomToNavType(className: String, isArray: Boolean): NavType<*>? {
+ // To convert name to a Class<*>, subclasses need to be delimited with `$`. So we need to
+ // replace the `.` delimiters in serial names to `$` for subclasses.
+ val sequence = className.splitToSequence(".")
+ var finalClassName = ""
+ sequence.fold(false) { isSubclass, current ->
+ if (isSubclass) {
+ finalClassName += "$"
+ } else {
+ if (finalClassName.isNotEmpty()) finalClassName += "."
+ }
+ finalClassName += current
+ if (isSubclass) true else current.toCharArray().first().isUpperCase()
+ }
+ // then try to parse it to a Serializable or Parcelable
+ return try {
+ parseSerializableOrParcelableType(finalClassName, isArray)
+ } catch (e: ClassNotFoundException) {
+ null
+ }
+}
+
+/**
+ * Convert KType to an [InternalCommonType].
+ *
+ * Conversion is based on KType name. The KType could be any of the native Kotlin types that
+ * are supported in [Native], or it could be a custom KType (custom class, object or enum).
+ */
+private fun KType.toInternalType(): InternalCommonType {
+ // first we need to parse KType name to the right format
+ // extract base class without type parameters
+ val typeParamRegex = Regex(pattern = "^[^<]*", options = setOf(RegexOption.IGNORE_CASE))
+ val trimmedTypeParam = typeParamRegex.find(this.toString())
+ // remove the warning that was appended to KType name due to missing kotlin reflect library
+ val trimEndingRegex = Regex("(\\S+)")
+ val trimmedEnding = trimEndingRegex.find(trimmedTypeParam!!.value)
+ val finalName = trimmedEnding?.value
+ // we assert the nullability directly with isNullable properties so its more reliable
+ ?.replace("?", "")
+ // we also replace the delimiter `$` with `.` for child classes to match the format of
+ // serial names
+ ?.replace("$", ".")
+
+ return when (finalName) {
+ null -> Native.UNKNOWN
+ "int" -> Native.INT
+ "java.lang.Integer" -> Native.INT
+ "boolean" -> Native.BOOL
+ "java.lang.Boolean" -> Native.BOOL
+ "float" -> Native.FLOAT
+ "java.lang.Float" -> Native.FLOAT
+ "long" -> Native.LONG
+ "java.lang.Long" -> Native.LONG
+ "java.lang.String" -> Native.STRING
+ "kotlin.IntArray" -> Native.INT_ARRAY
+ "kotlin.BooleanArray" -> Native.BOOL_ARRAY
+ "kotlin.FloatArray" -> Native.FLOAT_ARRAY
+ "kotlin.LongArray" -> Native.LONG_ARRAY
+ "kotlin.Array" -> Native.ARRAY
+ "java.util.ArrayList" -> Native.ARRAY_LIST
+ // KType List mapped to ArrayList because serialized name does not differentiate them
+ "java.util.List" -> Native.ARRAY_LIST
+ "java.util.Set" -> Native.SET
+ "java.util.HashSet" -> Native.HASHSET
+ "java.util.Map" -> Native.MAP
+ "java.util.HashMap" -> Native.HASH_MAP
+ else -> Custom(finalName)
+ }
+}
+
+/**
+ * Convert SerialDescriptor to an InternalCommonType.
+ *
+ * The descriptor's associated argument could be any of the native Kotlin types that
+ * are supported in [Native], or it could be a custom type (custom class, object or enum).
+ */
+private fun SerialDescriptor.toInternalType(): InternalCommonType {
+ val serialName = serialName.replace("?", "")
+ return when {
+ serialName == "kotlin.Int" -> Native.INT
+ serialName == "kotlin.Boolean" -> Native.BOOL
+ serialName == "kotlin.Float" -> Native.FLOAT
+ serialName == "kotlin.Long" -> Native.LONG
+ serialName == "kotlin.String" -> Native.STRING
+ serialName == "kotlin.IntArray" -> Native.INT_ARRAY
+ serialName == "kotlin.BooleanArray" -> Native.BOOL_ARRAY
+ serialName == "kotlin.FloatArray" -> Native.FLOAT_ARRAY
+ serialName == "kotlin.LongArray" -> Native.LONG_ARRAY
+ serialName == "kotlin.Array" -> Native.ARRAY
+ // serial name for both List and ArrayList
+ serialName.startsWith("kotlin.collections.ArrayList") -> Native.ARRAY_LIST
+ serialName.startsWith("kotlin.collections.LinkedHashSet") -> Native.SET
+ serialName.startsWith("kotlin.collections.HashSet") -> Native.HASHSET
+ serialName.startsWith("kotlin.collections.LinkedHashMap") -> Native.MAP
+ serialName.startsWith("kotlin.collections.HashMap") -> Native.HASH_MAP
+ else -> Custom(serialName)
+ }
+}
+
+/**
+ * Returns true if a serialized argument type matches the given KType.
+ *
+ * Matching starts by matching the base class of the type and then matching type parameters
+ * in their declared order. Nested TypeParameters are matched recursively in the same manner.
+ *
+ * Limitation: For custom Types, TypeParameters are erased in serialization by default.
+ * The type is preserved only if there is a class field of type T, and even then we cannot know
+ * that this type is in fact the TypeParameter. Therefore, matching custom types with
+ * type parameters only works under two conditions:
+ *
+ * 1. A class field of that type must be declared for each generic type
+ * 2. These class fields must be declared in primary constructor in same order
+ *
+ * For example, this declaration will work:
+ * `class TestClass<T: Any, K: Any>(val arg: T, val arg2: K)`
+ * and these variations will not work:
+ * `class TestClass<T: Any, K: Any>(val arg: K, val arg2: T)`
+ * `class TestClass<T: Any, K: Any>(val other: Int, val arg: K, val arg2: T)`
+ *
+ * If TypeParameters of custom classes cannot be matched, the custom class will be matched to
+ * a KType purely based on the class's fully qualified name and will not be able to differentiate
+ * between different TypeParameters. This can lead to indeterminate matching behavior.
+ */
+internal fun SerialDescriptor.matchKType(kType: KType): Boolean {
+ if (this.isNullable != kType.isMarkedNullable) return false
+ if (this.toInternalType() != kType.toInternalType()) return false
+ var index = 0
+ // recursive match nested TypeParameters
+ while (index < elementsCount && index < kType.arguments.size) {
+ val descriptor = getElementDescriptor(index)
+ val childKType = kType.arguments.getOrNull(index)?.type ?: return false
+ val result = descriptor.matchKType(childKType)
+ if (!result) return false
+ index++
+ }
+ return true
+}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteSerializer.kt b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteSerializer.kt
new file mode 100644
index 0000000..c6ba7d7
--- /dev/null
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteSerializer.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
+
+package androidx.navigation.serialization
+
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import kotlin.reflect.KType
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.PolymorphicSerializer
+import kotlinx.serialization.descriptors.capturedKClass
+
+/**
+ * Generates a route pattern for use in Navigation functions such as [::navigate] from
+ * a serializer of class T where T is a concrete class or object.
+ *
+ * The generated route pattern contains the path, path args, and query args. Non-nullable arg types
+ * are appended as path args, while nullable arg types are appended as query args.
+ */
+internal fun <T> KSerializer<T>.generateRoutePattern(): String {
+ assertNotAbstractClass {
+ throw IllegalArgumentException(
+ "Cannot generate route pattern from polymorphic class " +
+ "${descriptor.capturedKClass?.simpleName}. Routes can only be generated from " +
+ "concrete classes or objects."
+ )
+ }
+
+ val path = descriptor.serialName
+
+ var pathArg = ""
+ var queryArg = ""
+
+ // TODO refactor to use RouteBuilder when implementing route with args to ensure
+ // same logic for both route generation
+ for (i in 0 until descriptor.elementsCount) {
+ val argName = descriptor.getElementName(i)
+ // If it has default value, from the perspective of DeepLinks this arg is not
+ // a core arg and so we append it as a query
+ if (descriptor.isElementOptional(i)) {
+ val symbol = if (queryArg.isEmpty()) "?" else "&"
+ queryArg += "$symbol$argName={$argName}"
+ } else {
+ pathArg += "/{$argName}"
+ }
+ }
+
+ return path + pathArg + queryArg
+}
+
+/**
+ * Returns a list of [NamedNavArgument].
+ *
+ * By default this method only supports conversion to NavTypes that are declared in
+ * [NavType.Companion] class. To convert non-natively supported types, the custom NavType must be
+ * provided via [typeMap].
+ *
+ * Short summary of NavArgument generation principles:
+ * 1. NavArguments will only be generated on variables with kotlin backing fields
+ * 2. Arg Name is based on variable name
+ * 3. Nullability is based on variable Type's nullability
+ * 4. defaultValuePresent is based on whether variable has default value
+ *
+ * This generator does not check for validity as a NavType.
+ * This means if a NavType is not nullable (i.e. Int), and the KType was Int?, it relies on the
+ * navArgument builder to throw exception.
+ *
+ * @param [typeMap] A mapping of KType to the custom NavType<*>. For example given
+ * an argument of "val userId: UserId", the map should
+ * contain [typeOf<UserId>() to MyNavType]. Custom NavTypes take priority over native
+ * NavTypes. This means you can override native NavTypes such as [NavType.IntType] with your own
+ * implementation of NavType<Int>.
+ */
+internal fun <T> KSerializer<T>.generateNavArguments(
+ typeMap: Map<KType, NavType<*>>? = null
+): List<NamedNavArgument> {
+ assertNotAbstractClass {
+ throw IllegalArgumentException(
+ "Cannot generate NavArguments for polymorphic serializer $this. Arguments " +
+ "can only be generated from concrete classes or objects."
+ )
+ }
+
+ return List(descriptor.elementsCount) { index ->
+ val name = descriptor.getElementName(index)
+ navArgument(name) {
+ val element = descriptor.getElementDescriptor(index)
+ val isNullable = element.isNullable
+ val customType = typeMap?.keys
+ ?.find { kType -> element.matchKType(kType) }
+ ?.let { typeMap[it] }
+ type = customType ?: try {
+ element.getNavType()
+ } catch (e: IllegalArgumentException) {
+ throw IllegalArgumentException(
+ "Cannot cast $name of type ${element.serialName} to a NavType. Make sure " +
+ "to provide custom NavType for this argument."
+ )
+ }
+ nullable = isNullable
+ if (descriptor.isElementOptional(index)) {
+ // Navigation mostly just cares about defaultValuePresent state for
+ // non-nullable args to verify DeepLinks at a later stage.
+ // We know that non-nullable types cannot have null values, so it is
+ // safe to mark this as true without knowing actual value.
+ unknownDefaultValuePresent = true
+ }
+ }
+ }
+}
+
+private fun <T> KSerializer<T>.assertNotAbstractClass(handler: () -> Unit) {
+ // abstract class
+ if (this is PolymorphicSerializer) {
+ handler()
+ }
+}
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavArgumentGeneratorTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavArgumentGeneratorTest.kt
new file mode 100644
index 0000000..6c13e48
--- /dev/null
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavArgumentGeneratorTest.kt
@@ -0,0 +1,911 @@
+ /*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.serialization
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavArgument
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.typeOf
+import kotlin.test.assertFailsWith
+import kotlin.test.fail
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.serializer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class NavArgumentGeneratorTest {
+ @Test
+ fun convertToInt() {
+ @Serializable class TestClass(val arg: Int)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.IntType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToIntNullableIllegal() {
+ @Serializable class TestClass(val arg: Int?)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ serializer<TestClass>().generateNavArguments()
+ }
+ assertThat(exception.message).isEqualTo("integer does not allow nullable values")
+ }
+
+ @Test
+ fun convertToString() {
+ @Serializable class TestClass(val arg: String)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.StringType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToStringNullable() {
+ @Serializable class TestClass(val arg: String?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.StringType
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToBoolean() {
+ @Serializable class TestClass(val arg: Boolean)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.BoolType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToBooleanNullableIllegal() {
+ @Serializable class TestClass(val arg: Boolean?)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ serializer<TestClass>().generateNavArguments()
+ }
+ assertThat(exception.message).isEqualTo("boolean does not allow nullable values")
+ }
+
+ @Test
+ fun convertToFloat() {
+ @Serializable class TestClass(val arg: Float)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.FloatType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToFloatNullableIllegal() {
+ @Serializable class TestClass(val arg: Float?)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ serializer<TestClass>().generateNavArguments()
+ }
+ assertThat(exception.message).isEqualTo("float does not allow nullable values")
+ }
+
+ @Test
+ fun convertToLong() {
+ @Serializable class TestClass(val arg: Long)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.LongType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToLongNullableIllegal() {
+ @Serializable class TestClass(val arg: Long?)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ serializer<TestClass>().generateNavArguments()
+ }
+ assertThat(exception.message).isEqualTo("long does not allow nullable values")
+ }
+
+ @Test
+ fun convertToIntArray() {
+ @Serializable class TestClass(val arg: IntArray)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.IntArrayType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToIntArrayNullable() {
+ @Serializable class TestClass(val arg: IntArray?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.IntArrayType
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToLongArray() {
+ @Serializable class TestClass(val arg: LongArray)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.LongArrayType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToLongArrayNullable() {
+ @Serializable class TestClass(val arg: LongArray?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.LongArrayType
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToFloatArray() {
+ @Serializable class TestClass(val arg: FloatArray)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.FloatArrayType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToFloatArrayNullable() {
+ @Serializable class TestClass(val arg: FloatArray?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.FloatArrayType
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToBoolArray() {
+ @Serializable class TestClass(val arg: BooleanArray)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.BoolArrayType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToBoolArrayNullable() {
+ @Serializable class TestClass(val arg: BooleanArray?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.BoolArrayType
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToStringArray() {
+ @Serializable class TestClass(val arg: Array<String>)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.StringArrayType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToStringArrayNullable() {
+ @Serializable class TestClass(val arg: Array<String>?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.StringArrayType
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToParcelable() {
+ @Serializable
+ class TestParcelable : Parcelable {
+ override fun describeContents() = 0
+ override fun writeToParcel(dest: Parcel, flags: Int) {}
+ }
+
+ @Serializable
+ class TestClass(val arg: TestParcelable)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.ParcelableType(TestParcelable::class.java)
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToParcelableNullable() {
+ @Serializable
+ class TestParcelable : Parcelable {
+ override fun describeContents() = 0
+ override fun writeToParcel(dest: Parcel, flags: Int) {}
+ }
+
+ @Serializable
+ class TestClass(val arg: TestParcelable?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.ParcelableType(TestParcelable::class.java)
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToParcelableArray() {
+ @Serializable
+ class TestParcelable : Parcelable {
+ override fun describeContents() = 0
+ override fun writeToParcel(dest: Parcel, flags: Int) {}
+ }
+
+ @Serializable
+ class TestClass(val arg: Array<TestParcelable>)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.ParcelableArrayType(TestParcelable::class.java)
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToParcelableArrayNullable() {
+ @Serializable
+ class TestParcelable : Parcelable {
+ override fun describeContents() = 0
+ override fun writeToParcel(dest: Parcel, flags: Int) {}
+ }
+
+ @Serializable
+ class TestClass(val arg: Array<TestParcelable>?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.ParcelableArrayType(TestParcelable::class.java)
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToSerializable() {
+ @Serializable
+ class TestSerializable : java.io.Serializable
+
+ @Serializable
+ class TestClass(val arg: TestSerializable)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.SerializableType(TestSerializable::class.java)
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToSerializableNullable() {
+ @Serializable
+ class TestSerializable : java.io.Serializable
+
+ @Serializable
+ class TestClass(val arg: TestSerializable?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.SerializableType(TestSerializable::class.java)
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToSerializableArray() {
+ @Serializable
+ class TestSerializable : java.io.Serializable
+
+ @Serializable
+ class TestClass(val arg: Array<TestSerializable>)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.SerializableArrayType(TestSerializable::class.java)
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToSerializableArrayNullable() {
+ @Serializable
+ class TestSerializable : java.io.Serializable
+
+ @Serializable
+ class TestClass(val arg: Array<TestSerializable>?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.SerializableArrayType(TestSerializable::class.java)
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToEnum() {
+ @Serializable
+ class TestClass(val arg: TestEnum)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.EnumType(TestEnum::class.java)
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertToEnumNullableIllegal() {
+ @Serializable
+ class TestClass(val arg: TestEnum?)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ serializer<TestClass>().generateNavArguments()
+ }
+ assertThat(exception.message).isEqualTo("androidx.navigation.serialization." +
+ "NavArgumentGeneratorTest\$TestEnum does not allow nullable values")
+ }
+
+ @Test
+ fun convertToEnumArray() {
+ @Serializable
+ class TestClass(val arg: Array<TestEnum>)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.SerializableArrayType(TestEnum::class.java)
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertWithDefaultValue() {
+ @Serializable class TestClass(val arg: String = "test")
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.StringType
+ nullable = false
+ unknownDefaultValuePresent = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isTrue()
+ }
+
+ @Test
+ fun convertNullableWithDefaultValue() {
+ @Serializable class TestClass(val arg: String? = "test")
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.StringType
+ nullable = true
+ unknownDefaultValuePresent = true
+ // since String? is nullable, we cannot know for sure the default value is not null
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isTrue()
+ }
+
+ @Test
+ fun convertNullableWithNullDefaultValue() {
+ @Serializable class TestClass(val arg: String? = null)
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.StringType
+ nullable = true
+ unknownDefaultValuePresent = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isTrue()
+ }
+
+ @Test
+ fun convertIllegalCustomType() {
+ @Serializable class TestClass(val arg: ArrayList<String>)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ serializer<TestClass>().generateNavArguments()
+ }
+
+ assertThat(exception.message).isEqualTo(
+ "Cannot cast arg of type kotlin.collections.ArrayList to a NavType. " +
+ "Make sure to provide custom NavType for this argument."
+ )
+ }
+
+ @Test
+ fun convertCustomType() {
+ @Serializable class TestClass(val arg: ArrayList<String>)
+
+ val CustomNavType = object : NavType<ArrayList<String>>(false) {
+ override fun put(bundle: Bundle, key: String, value: ArrayList<String>) { }
+ override fun get(bundle: Bundle, key: String): ArrayList<String> = arrayListOf()
+ override fun parseValue(value: String): ArrayList<String> = arrayListOf()
+ }
+
+ val converted = serializer<TestClass>().generateNavArguments(
+ mapOf(typeOf<ArrayList<String>>() to CustomNavType)
+ )
+ val expected = navArgument("arg") {
+ type = CustomNavType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertCustomTypeNullable() {
+ @Serializable class TestClass(val arg: ArrayList<String>?)
+
+ val CustomNavType = object : NavType<ArrayList<String>?>(true) {
+ override fun put(bundle: Bundle, key: String, value: ArrayList<String>?) { }
+ override fun get(bundle: Bundle, key: String): ArrayList<String> = arrayListOf()
+ override fun parseValue(value: String): ArrayList<String> = arrayListOf()
+ }
+
+ val converted = serializer<TestClass>().generateNavArguments(
+ mapOf(typeOf<ArrayList<String>?>() to CustomNavType)
+ )
+ val expected = navArgument("arg") {
+ type = CustomNavType
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertCustomTypeNullableIllegal() {
+ val CustomNavType = object : NavType<ArrayList<String>>(false) {
+ override val name = "customNavType"
+ override fun put(bundle: Bundle, key: String, value: ArrayList<String>) { }
+ override fun get(bundle: Bundle, key: String): ArrayList<String> = arrayListOf()
+ override fun parseValue(value: String): ArrayList<String> = arrayListOf()
+ }
+
+ // CustomNavType does not allow nullable but we declare the arg as nullable here
+ @Serializable class TestClass(val arg: ArrayList<String>?)
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ serializer<TestClass>().generateNavArguments(
+ mapOf(typeOf<ArrayList<String>?>() to CustomNavType)
+ )
+ }
+ assertThat(exception.message).isEqualTo(
+ "customNavType does not allow nullable values"
+ )
+ }
+
+ @Test
+ fun convertMultiple() {
+ @Serializable class TestClass(val arg: Int, val arg2: String?)
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expectedInt = navArgument("arg") {
+ type = NavType.IntType
+ nullable = false
+ }
+ val expectedString = navArgument("arg2") {
+ type = NavType.StringType
+ nullable = true
+ }
+ assertThat(converted).containsExactlyInOrder(expectedInt, expectedString)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ assertThat(converted[1].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertMultipleWithDefaultValues() {
+ @Serializable class TestClass(val arg: Int = 0, val arg2: String? = "test")
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expectedInt = navArgument("arg") {
+ type = NavType.IntType
+ nullable = false
+ unknownDefaultValuePresent = true
+ }
+ val expectedString = navArgument("arg2") {
+ type = NavType.StringType
+ nullable = true
+ unknownDefaultValuePresent = true
+ }
+ assertThat(converted).containsExactlyInOrder(expectedInt, expectedString)
+ assertThat(converted[0].argument.isDefaultValuePresent).isTrue()
+ assertThat(converted[1].argument.isDefaultValuePresent).isTrue()
+ }
+
+ @Test
+ fun convertMultipleCustomTypes() {
+ @Serializable class TestClass(val arg: ArrayList<String>?, val arg2: ArrayList<Int>)
+
+ val CustomStringList = object : NavType<ArrayList<String>?>(true) {
+ override fun put(bundle: Bundle, key: String, value: ArrayList<String>?) { }
+ override fun get(bundle: Bundle, key: String): ArrayList<String> = arrayListOf()
+ override fun parseValue(value: String): ArrayList<String> = arrayListOf()
+ }
+
+ val CustomIntList = object : NavType<ArrayList<Int>>(true) {
+ override fun put(bundle: Bundle, key: String, value: ArrayList<Int>) { }
+ override fun get(bundle: Bundle, key: String): ArrayList<Int> = arrayListOf()
+ override fun parseValue(value: String): ArrayList<Int> = arrayListOf()
+ }
+
+ val converted = serializer<TestClass>().generateNavArguments(
+ mapOf(
+ typeOf<ArrayList<String>?>() to CustomStringList,
+ typeOf<ArrayList<Int>>() to CustomIntList)
+ )
+ val expectedStringList = navArgument("arg") {
+ type = CustomStringList
+ nullable = true
+ }
+ val expectedIntList = navArgument("arg2") {
+ type = CustomIntList
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expectedStringList, expectedIntList)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ assertThat(converted[1].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertMultipleCustomTypesWithDefaultValue() {
+ @Serializable class TestClass(
+ val arg: ArrayList<String>? = arrayListOf(),
+ val arg2: ArrayList<Int> = arrayListOf()
+ )
+
+ val CustomStringList = object : NavType<ArrayList<String>?>(true) {
+ override fun put(bundle: Bundle, key: String, value: ArrayList<String>?) { }
+ override fun get(bundle: Bundle, key: String): ArrayList<String> = arrayListOf()
+ override fun parseValue(value: String): ArrayList<String> = arrayListOf()
+ }
+
+ val CustomIntList = object : NavType<ArrayList<Int>>(true) {
+ override fun put(bundle: Bundle, key: String, value: ArrayList<Int>) { }
+ override fun get(bundle: Bundle, key: String): ArrayList<Int> = arrayListOf()
+ override fun parseValue(value: String): ArrayList<Int> = arrayListOf()
+ }
+
+ val converted = serializer<TestClass>().generateNavArguments(
+ mapOf(
+ typeOf<ArrayList<String>?>() to CustomStringList,
+ typeOf<ArrayList<Int>>() to CustomIntList)
+ )
+ val expectedStringList = navArgument("arg") {
+ type = CustomStringList
+ nullable = true
+ unknownDefaultValuePresent = true
+ }
+ val expectedIntList = navArgument("arg2") {
+ type = CustomIntList
+ nullable = false
+ unknownDefaultValuePresent = true
+ }
+ assertThat(converted).containsExactlyInOrder(expectedStringList, expectedIntList)
+ assertThat(converted[0].argument.isDefaultValuePresent).isTrue()
+ assertThat(converted[1].argument.isDefaultValuePresent).isTrue()
+ }
+
+ @Test
+ fun convertNestedCustomTypes() {
+ @Serializable class TestClass(val arg: ArrayList<List<String>>)
+
+ val CustomStringList = object : NavType<ArrayList<List<String>>>(false) {
+ override fun put(bundle: Bundle, key: String, value: ArrayList<List<String>>) { }
+ override fun get(bundle: Bundle, key: String): ArrayList<List<String>> = arrayListOf()
+ override fun parseValue(value: String): ArrayList<List<String>> = arrayListOf()
+ }
+
+ val converted = serializer<TestClass>().generateNavArguments(
+ mapOf(typeOf<ArrayList<List<String>>>() to CustomStringList)
+ )
+ val expectedStringList = navArgument("arg") {
+ type = CustomStringList
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expectedStringList)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertNativeAndCustomTypes() {
+ @Serializable class TestClass(val arg: String, val arg2: ArrayList<Int>)
+
+ val CustomIntList = object : NavType<ArrayList<Int>>(true) {
+ override fun put(bundle: Bundle, key: String, value: ArrayList<Int>) { }
+ override fun get(bundle: Bundle, key: String): ArrayList<Int> = arrayListOf()
+ override fun parseValue(value: String): ArrayList<Int> = arrayListOf()
+ }
+
+ val converted = serializer<TestClass>().generateNavArguments(
+ mapOf(typeOf<ArrayList<Int>>() to CustomIntList)
+ )
+ val expectedString = navArgument("arg") {
+ type = NavType.StringType
+ nullable = false
+ }
+ val expectedIntList = navArgument("arg2") {
+ type = CustomIntList
+ nullable = false
+ }
+
+ assertThat(converted).containsExactlyInOrder(expectedString, expectedIntList)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ assertThat(converted[1].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertPrioritizesProvidedNavType() {
+ val CustomIntNavType = object : NavType<Int>(true) {
+ override fun put(bundle: Bundle, key: String, value: Int) { }
+ override fun get(bundle: Bundle, key: String): Int = 0
+ override fun parseValue(value: String): Int = 0
+ }
+
+ @Serializable class TestClass(val arg: Int)
+
+ val converted = serializer<TestClass>().generateNavArguments(
+ mapOf(typeOf<Int>() to CustomIntNavType)
+ )
+ val expected = navArgument("arg") {
+ type = CustomIntNavType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0]).isNotEqualTo(NavType.IntType)
+ assertThat(converted[0].argument.isDefaultValuePresent).isFalse()
+ }
+
+ @Test
+ fun convertOnlyIfArgHasBackingField() {
+ @Serializable
+ class TestClass {
+ val noBackingField: Int
+ get() = 0
+ }
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ assertThat(converted).isEmpty()
+ }
+
+ @Test
+ fun convertArgFromClassBody() {
+ @Serializable
+ class TestClass {
+ val arg: Int = 0
+ }
+
+ val converted = serializer<TestClass>().generateNavArguments()
+ val expected = navArgument("arg") {
+ type = NavType.IntType
+ nullable = false
+ unknownDefaultValuePresent = true
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ assertThat(converted[0].argument.isDefaultValuePresent).isTrue()
+ }
+
+ @Test
+ fun nonSerializableClassInvalid() {
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass
+
+ assertFailsWith<SerializationException> {
+ // the class must be serializable
+ serializer<TestClass>().generateNavArguments()
+ }
+ }
+
+ @Test
+ fun abstractClassInvalid() {
+ @Serializable
+ abstract class TestClass(val arg: Int)
+
+ val serializer = serializer<TestClass>()
+ val exception = assertFailsWith<IllegalArgumentException> {
+ serializer.generateNavArguments()
+ }
+ assertThat(exception.message).isEqualTo(
+ "Cannot generate NavArguments for polymorphic serializer " +
+ "kotlinx.serialization.PolymorphicSerializer(baseClass: " +
+ "class androidx.navigation.serialization." +
+ "NavArgumentGeneratorTest\$abstractClassInvalid\$TestClass (Kotlin reflection " +
+ "is not available)). Arguments can only be generated from concrete classes " +
+ "or objects."
+ )
+ }
+
+ @Test
+ fun childClassOfAbstract_duplicateArgs() {
+ @Serializable
+ abstract class TestAbstractClass(val arg: Int)
+
+ @Serializable
+ class TestClass(val arg2: Int) : TestAbstractClass(0)
+
+ val serializer = serializer<TestClass>()
+ val converted = serializer.generateNavArguments()
+ // args will be duplicated
+ val expectedInt = navArgument("arg") {
+ type = NavType.IntType
+ nullable = false
+ }
+ val expectedInt2 = navArgument("arg2") {
+ type = NavType.IntType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expectedInt, expectedInt2)
+ }
+
+ @Test
+ fun childClassOfSealed_withArgs() {
+ val serializer = serializer<SealedClass.TestClass>()
+ val converted = serializer.generateNavArguments()
+ // child class overrides parent variable so only child variables are generated as args
+ val expected = navArgument("arg2") {
+ type = NavType.IntType
+ nullable = false
+ }
+ assertThat(converted).containsExactlyInOrder(expected)
+ }
+
+ // writing our own assert so we don't need to override NamedNavArgument's equals
+ // and hashcode which will need to be public api.
+ private fun assertThat(actual: List<NamedNavArgument>) = actual
+
+ private fun List<NamedNavArgument>.containsExactlyInOrder(
+ vararg expectedArgs: NamedNavArgument
+ ) {
+ if (expectedArgs.size != this.size) {
+ fail("expected list has size ${expectedArgs.size} and actual list has size $size}")
+ }
+ for (i in indices) {
+ val actual = this[i]
+ val expected = expectedArgs[i]
+ if (expected.name != actual.name) {
+ fail("expected name ${expected.name}, was actually ${actual.name}")
+ }
+
+ if (!expected.argument.isEqual(actual.argument)) {
+ fail("""expected ${expected.name} to be:
+ | ${expected.argument}
+ | but was:
+ | ${actual.argument}
+ """.trimMargin())
+ }
+ }
+ }
+
+ private fun NavArgument.isEqual(other: NavArgument): Boolean {
+ if (this === other) return true
+ if (javaClass != other.javaClass) return false
+ if (isNullable != other.isNullable) return false
+ if (isDefaultValuePresent != other.isDefaultValuePresent) return false
+ if (type != other.type) return false
+ // In context of serialization, we can only tell if defaultValue is present but don't know
+ // actual value, so we cannot compare it to the generated defaultValue. But if
+ // there is no defaultValue, we expect them both to be null.
+ return if (!isDefaultValuePresent) {
+ defaultValue == null && other.defaultValue == null
+ } else true
+ }
+
+ @Serializable
+ enum class TestEnum
+}
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavTypeConverterTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavTypeConverterTest.kt
new file mode 100644
index 0000000..d3d17bf
--- /dev/null
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/serialization/NavTypeConverterTest.kt
@@ -0,0 +1,661 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.serialization
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.navigation.NavType
+import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.typeOf
+import kotlin.test.Test
+import kotlin.test.assertFails
+import kotlin.test.assertFailsWith
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.serializer
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class NavTypeConverterTest {
+
+ @Test
+ fun matchInt() {
+ val descriptor = serializer<Int>().descriptor
+ val kType = typeOf<Int>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchIntNullable() {
+ val descriptor = serializer<Int?>().descriptor
+ val kType = typeOf<Int?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<Int>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchBoolean() {
+ val descriptor = serializer<Boolean>().descriptor
+ val kType = typeOf<Boolean>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchBooleanNullable() {
+ val descriptor = serializer<Boolean?>().descriptor
+ val kType = typeOf<Boolean?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<Boolean>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchFloat() {
+ val descriptor = serializer<Float>().descriptor
+ val kType = typeOf<Float>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchFloatNullable() {
+ val descriptor = serializer<Float?>().descriptor
+ val kType = typeOf<Float?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<Float>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchLong() {
+ val descriptor = serializer<Long>().descriptor
+ val kType = typeOf<Long>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchLongNullable() {
+ val descriptor = serializer<Long?>().descriptor
+ val kType = typeOf<Long?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<Long>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchString() {
+ val descriptor = serializer<String>().descriptor
+ val kType = typeOf<String>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchStringNullable() {
+ val descriptor = serializer<String?>().descriptor
+ val kType = typeOf<String?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<String>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchIntArray() {
+ val descriptor = serializer<IntArray>().descriptor
+ val kType = typeOf<IntArray>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchIntArrayNullable() {
+ val descriptor = serializer<IntArray?>().descriptor
+ val kType = typeOf<IntArray?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<IntArray>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchArrayOfInt() {
+ val descriptor = serializer<Array<Int>>().descriptor
+ val kType = typeOf<Array<Int>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val wrongTypeParameter = serializer<Array<Boolean>>().descriptor
+ assertThat(wrongTypeParameter.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchBooleanArray() {
+ val descriptor = serializer<BooleanArray>().descriptor
+ val kType = typeOf<BooleanArray>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchBooleanArrayNullable() {
+ val descriptor = serializer<BooleanArray?>().descriptor
+ val kType = typeOf<BooleanArray?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<BooleanArray>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchFloatArray() {
+ val descriptor = serializer<FloatArray>().descriptor
+ val kType = typeOf<FloatArray>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchFloatArrayNullable() {
+ val descriptor = serializer<FloatArray?>().descriptor
+ val kType = typeOf<FloatArray?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<FloatArray>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchLongArray() {
+ val descriptor = serializer<LongArray>().descriptor
+ val kType = typeOf<LongArray>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchLongArrayNullable() {
+ val descriptor = serializer<LongArray?>().descriptor
+ val kType = typeOf<LongArray?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<LongArray>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchStringArray() {
+ val descriptor = serializer<Array<String>>().descriptor
+ val kType = typeOf<Array<String>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchStringArrayNullable() {
+ val descriptor = serializer<Array<String>?>().descriptor
+ val kType = typeOf<Array<String>?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<Array<String>>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchArrayList() {
+ val descriptor = serializer<ArrayList<Int>>().descriptor
+ val kType = typeOf<ArrayList<Int>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchArrayListNullable() {
+ val descriptor = serializer<ArrayList<Int>?>().descriptor
+ val kType = typeOf<ArrayList<Int>?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<ArrayList<Int>>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchArrayListTypeParamNullable() {
+ val descriptor = serializer<ArrayList<Int?>>().descriptor
+ val kType = typeOf<ArrayList<Int?>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<ArrayList<Int>>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchArrayListAllNullable() {
+ val descriptor = serializer<ArrayList<Int?>?>().descriptor
+ val kType = typeOf<ArrayList<Int?>?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchSet() {
+ val descriptor = serializer<Set<Int>>().descriptor
+ val kType = typeOf<Set<Int>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchSetNullable() {
+ val descriptor = serializer<Set<Int>?>().descriptor
+ val kType = typeOf<Set<Int>?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<Set<Int>>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchSetTypeParamNullable() {
+ val descriptor = serializer<Set<Int?>>().descriptor
+ val kType = typeOf<Set<Int?>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<Set<Int>>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchSetAllNullable() {
+ val descriptor = serializer<Set<Int?>?>().descriptor
+ val kType = typeOf<Set<Int?>?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchMutableSet() {
+ val descriptor = serializer<MutableSet<Int>>().descriptor
+ val kType = typeOf<MutableSet<Int>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchMutableSetAndSet() {
+ val descriptor = serializer<MutableSet<Int>>().descriptor
+ val kType = typeOf<Set<Int>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchHashSet() {
+ val descriptor = serializer<HashSet<Int>>().descriptor
+ val kType = typeOf<HashSet<Int>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchHashSetNullable() {
+ val descriptor = serializer<HashSet<Int>?>().descriptor
+ val kType = typeOf<HashSet<Int>?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<Set<Int>>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchHashSetTypeParamNullable() {
+ val descriptor = serializer<HashSet<Int?>>().descriptor
+ val kType = typeOf<HashSet<Int?>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<HashSet<Int>>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchHashSetAllNullable() {
+ val descriptor = serializer<HashSet<Int?>?>().descriptor
+ val kType = typeOf<HashSet<Int?>?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchMap() {
+ val descriptor = serializer<Map<Int, String>>().descriptor
+ val kType = typeOf<Map<Int, String>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchMapNullable() {
+ val descriptor = serializer<Map<Int, String>?>().descriptor
+ val kType = typeOf<Map<Int, String>?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<Set<Int>>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchMapTypeParamNullable() {
+ val descriptor = serializer<Map<Int?, String>>().descriptor
+ val kType = typeOf<Map<Int?, String>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val nonNullable = serializer<Map<Int, String>>().descriptor
+ assertThat(nonNullable.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchMapAllNullable() {
+ val descriptor = serializer<Map<Int?, String?>?>().descriptor
+ val kType = typeOf<Map<Int?, String?>?>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchObject() {
+ val descriptor = serializer<TestObject>().descriptor
+ val kType = typeOf<TestObject>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchEnumClass() {
+ val descriptor = serializer<TestEnum>().descriptor
+ val kType = typeOf<TestEnum>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchWrongTypeParameter() {
+ val descriptor = serializer<Set<Int>>().descriptor
+ val kType = typeOf<Set<Boolean>>()
+ assertThat(descriptor.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchWrongOrderTypeParameter() {
+ val descriptor = serializer<Map<String, Int>>().descriptor
+ val kType = typeOf<Map<Int, String>>()
+ assertThat(descriptor.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchNestedTypeParameter() {
+ val descriptor = serializer<List<List<Int>>>().descriptor
+ val kType = typeOf<List<List<Int>>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val notNested = serializer<List<Int>>().descriptor
+ assertThat(notNested.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchMultiNestedTypeParameter() {
+ val descriptor = serializer<Map<List<Int>, Set<Boolean>>>().descriptor
+ val kType = typeOf<Map<List<Int>, Set<Boolean>>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val wrongOrder = serializer<Map<Set<Boolean>, List<Int>>>().descriptor
+ assertThat(wrongOrder.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchThriceNestedTypeParameter() {
+ val descriptor = serializer<List<Set<List<Boolean>>>>().descriptor
+ val kType = typeOf<List<Set<List<Boolean>>>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchNativeClassWithCustomTypeParameter() {
+ @Serializable
+ class TestClass(val arg: Int, val arg2: String)
+
+ val descriptor = serializer<List<TestClass>>().descriptor
+ val kType = typeOf<List<TestClass>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchNativeClassWithNestedCustomTypeParameter() {
+ @Serializable
+ open class Nested(val arg: Int)
+
+ @Serializable
+ class TestClass<T : Nested>(val arg: Nested)
+
+ val descriptor = serializer<List<TestClass<Nested>>>().descriptor
+ val kType = typeOf<List<TestClass<Nested>>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchCustomClass() {
+ @Serializable
+ class TestClass(val arg: Int, val arg2: String)
+
+ val descriptor = serializer<TestClass>().descriptor
+ val kType = typeOf<TestClass>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchCustomParcelable() {
+ val descriptor = serializer<TestParcelable>().descriptor
+ val kType = typeOf<TestParcelable>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchCustomSerializable() {
+ val descriptor = serializer<TestSerializable>().descriptor
+ val kType = typeOf<TestSerializable>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+ }
+
+ @Test
+ fun matchCustomClassTypeParameterWithSingleArg() {
+ @Serializable
+ class TestClass<T : Any>(val arg: T)
+
+ val descriptor = serializer<TestClass<String>>().descriptor
+ val kType = typeOf<TestClass<String>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val descriptor2 = serializer<TestClass<Int>>().descriptor
+ assertThat(descriptor2.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchCustomClassTypeParameterWithMultipleArg() {
+ @Serializable
+ class TestClass<T : Any, K : Any>(val arg: T, val arg2: K, val arg3: Int)
+
+ val descriptor = serializer<TestClass<String, Int>>().descriptor
+ val kType = typeOf<TestClass<String, Int>>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val descriptor2 = serializer<TestClass<Int, String>>().descriptor
+ assertThat(descriptor2.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchCustomClassTypeParameterWithNoArgFails() {
+ @Serializable
+ class TestClass<T : Any>
+
+ val descriptor = serializer<TestClass<Int>>().descriptor
+ val kType = typeOf<TestClass<String>>()
+
+ // type T is erased in serialization so we cannot differentiate between Int/String
+ val isMatch = descriptor.matchKType(kType)
+ val result = assertFails {
+ assertThat(isMatch).isFalse()
+ }
+ assertThat(result.message).isEqualTo("expected to be false")
+ }
+
+ @Test
+ fun matchCustomNestedType() {
+ val descriptor = serializer<TestBaseClass.Nested>().descriptor
+ val kType = typeOf<TestBaseClass.Nested>()
+ assertThat(descriptor.matchKType(kType)).isTrue()
+
+ val baseDescriptor = serializer<TestBaseClass>().descriptor
+ assertThat(baseDescriptor.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun matchChildOfAbstract() {
+ @Serializable
+ abstract class Abstract
+
+ @Serializable
+ class FirstChild : Abstract()
+
+ @Serializable
+ class SecondChild : Abstract()
+
+ val firstChildDescriptor = serializer<FirstChild>().descriptor
+ val kType = typeOf<FirstChild>()
+ assertThat(firstChildDescriptor.matchKType(kType)).isTrue()
+
+ val secondChildDescriptor = serializer<SecondChild>().descriptor
+ assertThat(secondChildDescriptor.matchKType(kType)).isFalse()
+ }
+
+ @Test
+ fun getNavTypeNativePrimitive() {
+ val intType = serializer<Int>().descriptor.getNavType()
+ assertThat(intType).isEqualTo(NavType.IntType)
+
+ val boolType = serializer<Boolean>().descriptor.getNavType()
+ assertThat(boolType).isEqualTo(NavType.BoolType)
+
+ val floatType = serializer<Float>().descriptor.getNavType()
+ assertThat(floatType).isEqualTo(NavType.FloatType)
+
+ val longType = serializer<Long>().descriptor.getNavType()
+ assertThat(longType).isEqualTo(NavType.LongType)
+
+ val stringType = serializer<String>().descriptor.getNavType()
+ assertThat(stringType).isEqualTo(NavType.StringType)
+ }
+
+ @Test
+ fun getNavTypeNativeArray() {
+ val intType = serializer<IntArray>().descriptor.getNavType()
+ assertThat(intType).isEqualTo(NavType.IntArrayType)
+
+ val boolType = serializer<BooleanArray>().descriptor.getNavType()
+ assertThat(boolType).isEqualTo(NavType.BoolArrayType)
+
+ val floatType = serializer<FloatArray>().descriptor.getNavType()
+ assertThat(floatType).isEqualTo(NavType.FloatArrayType)
+
+ val longType = serializer<LongArray>().descriptor.getNavType()
+ assertThat(longType).isEqualTo(NavType.LongArrayType)
+
+ val stringType = serializer<Array<String>>().descriptor.getNavType()
+ assertThat(stringType).isEqualTo(NavType.StringArrayType)
+ }
+
+ @Test
+ fun getNavTypeParcelable() {
+ val type = serializer<TestParcelable>().descriptor.getNavType()
+ assertThat(type).isEqualTo(NavType.ParcelableType(TestParcelable::class.java))
+ }
+
+ @Test
+ fun getNavTypeParcelableArray() {
+ val type = serializer<Array<TestParcelable>>().descriptor.getNavType()
+ assertThat(type).isEqualTo(
+ NavType.ParcelableArrayType(TestParcelable::class.java)
+ )
+ }
+
+ @Test
+ fun getNavTypeSerializable() {
+ val type = serializer<TestSerializable>().descriptor.getNavType()
+ assertThat(type).isEqualTo(
+ NavType.SerializableType(TestSerializable::class.java)
+ )
+ }
+
+ @Test
+ fun getNavTypeSerializableArray() {
+ val type = serializer<Array<TestSerializable>>().descriptor.getNavType()
+ assertThat(type).isEqualTo(
+ NavType.SerializableArrayType(TestSerializable::class.java)
+ )
+ }
+
+ @Test
+ fun getNavTypeEnumSerializable() {
+ val type = serializer<TestEnum>().descriptor.getNavType()
+ assertThat(type).isEqualTo(
+ NavType.EnumType(TestEnum::class.java)
+ )
+ }
+
+ @Test
+ fun getNavTypeEnumArraySerializable() {
+ val type = serializer<Array<TestEnum>>().descriptor.getNavType()
+ assertThat(type).isEqualTo(
+ NavType.SerializableArrayType(TestEnum::class.java)
+ )
+ }
+
+ @Test
+ fun getNavTypeUnsupportedArray() {
+ assertFailsWith<IllegalArgumentException> {
+ serializer<Array<Double>>().descriptor.getNavType()
+ }
+
+ class TestClass
+ assertFailsWith<IllegalArgumentException> {
+ serializer<Array<TestClass>>().descriptor.getNavType()
+ }
+
+ assertFailsWith<IllegalArgumentException> {
+ serializer<Array<List<Double>>>().descriptor.getNavType()
+ }
+ }
+
+ @Serializable
+ class TestBaseClass(val arg: Int) {
+ @Serializable
+ class Nested
+ }
+
+ @Serializable
+ class TestObject {
+ val arg: String = "test"
+ }
+
+ @Serializable
+ enum class TestEnum {
+ First,
+ Second
+ }
+
+ @Serializable
+ class TestParcelable(val arg: Int, val arg2: String) : Parcelable {
+ override fun describeContents() = 0
+ override fun writeToParcel(dest: Parcel, flags: Int) { }
+ }
+
+ @Serializable
+ class TestSerializable(val arg: Int, val arg2: String) : java.io.Serializable
+}
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/serialization/RoutePatternTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/serialization/RoutePatternTest.kt
new file mode 100644
index 0000000..1e469cd
--- /dev/null
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/serialization/RoutePatternTest.kt
@@ -0,0 +1,458 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.serialization
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.serializer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+const val PATH_SERIAL_NAME = "www.test.com"
+
+@RunWith(JUnit4::class)
+class RoutePatternTest {
+
+ @Test
+ fun basePath() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(PATH_SERIAL_NAME)
+ }
+
+ @Test
+ fun pathArg() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: String)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}"
+ )
+ }
+
+ @Test
+ fun multiplePathArg() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: String, val arg2: Int)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}/{arg2}"
+ )
+ }
+
+ @Test
+ fun pathArgNullable() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: String?)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}"
+ )
+ }
+
+ @Test
+ fun multiplePathArgNullable() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: String?, val arg2: Int?)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}/{arg2}"
+ )
+ }
+
+ @Test
+ fun queryArg() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: String = "test")
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME?arg={arg}"
+ )
+ }
+
+ @Test
+ fun multipleQueryArg() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: String = "test", val arg2: Int = 0)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME?arg={arg}&arg2={arg2}"
+ )
+ }
+
+ @Test
+ fun queryArgNullable() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: String? = "test")
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME?arg={arg}"
+ )
+ }
+
+ @Test
+ fun queryArgWithNullDefaultValue() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: Int? = null)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME?arg={arg}"
+ )
+ }
+
+ @Test
+ fun multipleQueryArgNullable() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: String? = "test", val arg2: Int? = 0)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME?arg={arg}&arg2={arg2}"
+ )
+ }
+
+ @Test
+ fun pathAndQueryArg() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val pathArg: String, val queryArg: Int = 0)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{pathArg}?queryArg={queryArg}"
+ )
+ }
+
+ @Test
+ fun pathAndQueryArgInReverseOrder() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val queryArg: Int = 0, val pathArg: String)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{pathArg}?queryArg={queryArg}"
+ )
+ }
+
+ @Test
+ fun pathAndQueryArgNullable() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val pathArg: String?, val queryArg: Int? = 0)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{pathArg}?queryArg={queryArg}"
+ )
+ }
+
+ @Test
+ fun withSecondaryConstructor() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: String) {
+ constructor(arg2: Int) : this(arg2.toString())
+ }
+
+ // only class members would show up on routePattern
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}"
+ )
+ }
+
+ @Test
+ fun withCompanionObject() {
+ assertThatRoutePatternFrom(serializer<ClassWithCompanionObject>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}"
+ )
+ }
+
+ @Test
+ fun withCompanionParameter() {
+ assertThatRoutePatternFrom(serializer<ClassWithCompanionParam>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}"
+ )
+ }
+
+ @Test
+ fun withFunction() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: String) {
+ fun testFun() { }
+ }
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}"
+ )
+ }
+
+ @Test
+ fun customParamType() {
+ @Serializable
+ class CustomType
+
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val custom: CustomType)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{custom}"
+ )
+ }
+
+ @Test
+ fun nestedCustomParamType() {
+ @Serializable
+ class NestedCustomType
+
+ @Serializable
+ class CustomType(val nested: NestedCustomType)
+
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val custom: CustomType)
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{custom}"
+ )
+ }
+
+ @Test
+ fun customSerializerParamType() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(
+ val arg: Int,
+ @Serializable(with = CustomSerializer::class)
+ val arg2: NonSerializedClass
+ )
+
+ // args will be duplicated
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}/{arg2}"
+ )
+ }
+
+ @Test
+ fun paramWithNoBackingField() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass {
+ val noBackingField: Int
+ get() = 0
+ }
+
+ // only members with backing field should appear on routePattern
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ PATH_SERIAL_NAME
+ )
+ }
+
+ @Test
+ fun queryArgFromClassBody() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass {
+ val arg: Int = 0
+ }
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME?arg={arg}"
+ )
+ }
+
+ @Test
+ fun pathArgFromClassBody() {
+ @Serializable
+ class CustomType
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass {
+ lateinit var arg: CustomType
+ }
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}"
+ )
+ }
+
+ @Test
+ fun nonSerializableClassInvalid() {
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass
+
+ assertFailsWith<SerializationException> {
+ // the class must be serializable
+ serializer<TestClass>().generateRoutePattern()
+ }
+ }
+
+ @Test
+ fun abstractClassInvalid() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ abstract class TestClass
+
+ val patternException = assertFailsWith<IllegalArgumentException> {
+ serializer<TestClass>().generateRoutePattern()
+ }
+ assertThat(patternException.message).isEqualTo(
+ "Cannot generate route pattern from polymorphic class TestClass. Routes " +
+ "can only be generated from concrete classes or objects."
+ )
+ }
+
+ @Test
+ fun childClassOfAbstract() {
+ @Serializable
+ abstract class TestAbstractClass
+
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass : TestAbstractClass()
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ PATH_SERIAL_NAME
+ )
+ }
+
+ @Test
+ fun childClassOfAbstract_duplicateArgs() {
+ @Serializable
+ abstract class TestAbstractClass(val arg: Int)
+
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg2: Int) : TestAbstractClass(0)
+
+ // args will be duplicated
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}/{arg2}"
+ )
+ }
+
+ @Test
+ fun childClassOfSealed_withArgs() {
+ // child class overrides parent variable so only child variable shows up in route pattern
+ assertThatRoutePatternFrom(serializer<SealedClass.TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg2}"
+ )
+ }
+
+ @Test
+ fun childClassOfInterface() {
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ class TestClass(val arg: Int) : TestInterface
+
+ assertThatRoutePatternFrom(serializer<TestClass>()).isEqualTo(
+ "$PATH_SERIAL_NAME/{arg}"
+ )
+ }
+
+ @Test
+ fun routeFromPlainObject() {
+ assertThatRoutePatternFrom(serializer<TestObject>()).isEqualTo(
+ PATH_SERIAL_NAME
+ )
+ }
+
+ @Test
+ fun routeFromObject_argsNotSerialized() {
+ // object variables are not serialized and does not show up on routePattern
+ assertThatRoutePatternFrom(serializer<TestObjectWithArg>()).isEqualTo(
+ PATH_SERIAL_NAME
+ )
+ }
+}
+
+private fun <T> assertThatRoutePatternFrom(serializer: KSerializer<T>) =
+ serializer.generateRoutePattern()
+
+private fun String.isEqualTo(other: String) {
+ assertThat(this).isEqualTo(other)
+}
+
+@Serializable
+@SerialName(PATH_SERIAL_NAME)
+private class ClassWithCompanionObject(val arg: Int) {
+ companion object TestObject
+}
+
+@Serializable
+@SerialName(PATH_SERIAL_NAME)
+private class ClassWithCompanionParam(val arg: Int) {
+ companion object {
+ val companionVal: String = "hello"
+ }
+}
+
+@Serializable
+@SerialName(PATH_SERIAL_NAME)
+internal object TestObject
+
+@Serializable
+@SerialName(PATH_SERIAL_NAME)
+internal object TestObjectWithArg {
+ val arg: Int = 0
+}
+
+@Serializable
+internal sealed class SealedClass {
+ abstract val arg: Int
+
+ @Serializable
+ @SerialName(PATH_SERIAL_NAME)
+ // same value for arg and arg2
+ class TestClass(val arg2: Int) : SealedClass() {
+ override val arg: Int
+ get() = arg2
+ }
+}
+
+private class NonSerializedClass(val longArg: Long)
+
+private class CustomSerializer : KSerializer<NonSerializedClass> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(
+ "Date", PrimitiveKind.LONG
+ )
+ override fun serialize(encoder: Encoder, value: NonSerializedClass) =
+ encoder.encodeLong(value.longArg)
+ override fun deserialize(decoder: Decoder): NonSerializedClass =
+ NonSerializedClass(decoder.decodeLong())
+}
+
+private interface TestInterface
diff --git a/navigation/navigation-compose/api/current.txt b/navigation/navigation-compose/api/current.txt
index 008c5b8..fb10176 100644
--- a/navigation/navigation-compose/api/current.txt
+++ b/navigation/navigation-compose/api/current.txt
@@ -33,11 +33,13 @@
}
public final class NavGraphBuilderKt {
+ method @Deprecated public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? popExitTransition, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedContentScope,? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
method @Deprecated public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
- method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? popExitTransition, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedContentScope,? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+ method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? popExitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.SizeTransform?>? sizeTransform, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedContentScope,? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
method public static void dialog(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional androidx.compose.ui.window.DialogProperties dialogProperties, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
- method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? popExitTransition, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method @Deprecated public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? popExitTransition, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method @Deprecated public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? popExitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.SizeTransform?>? sizeTransform, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavHostControllerKt {
@@ -47,8 +49,10 @@
public final class NavHostKt {
method @Deprecated @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
- method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> popExitTransition);
- method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional String? route, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> popExitTransition, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method @Deprecated @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> popExitTransition);
+ method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition> popExitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.SizeTransform?>? sizeTransform);
+ method @Deprecated @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional String? route, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> popExitTransition, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional String? route, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition> popExitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.SizeTransform?>? sizeTransform, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method @Deprecated @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
diff --git a/navigation/navigation-compose/api/restricted_current.txt b/navigation/navigation-compose/api/restricted_current.txt
index 008c5b8..fb10176 100644
--- a/navigation/navigation-compose/api/restricted_current.txt
+++ b/navigation/navigation-compose/api/restricted_current.txt
@@ -33,11 +33,13 @@
}
public final class NavGraphBuilderKt {
+ method @Deprecated public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? popExitTransition, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedContentScope,? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
method @Deprecated public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
- method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? popExitTransition, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedContentScope,? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
+ method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? popExitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.SizeTransform?>? sizeTransform, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedContentScope,? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
method public static void dialog(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional androidx.compose.ui.window.DialogProperties dialogProperties, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
- method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? popExitTransition, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method @Deprecated public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition?>? popExitTransition, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method @Deprecated public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, optional java.util.List<androidx.navigation.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? enterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? exitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition?>? popEnterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition?>? popExitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.SizeTransform?>? sizeTransform, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
public final class NavHostControllerKt {
@@ -47,8 +49,10 @@
public final class NavHostKt {
method @Deprecated @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
- method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> popExitTransition);
- method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional String? route, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> popExitTransition, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method @Deprecated @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> popExitTransition);
+ method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition> popExitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.SizeTransform?>? sizeTransform);
+ method @Deprecated @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional String? route, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,? extends androidx.compose.animation.ExitTransition> popExitTransition, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional String? route, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition> enterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition> exitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.EnterTransition> popEnterTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.ExitTransition> popExitTransition, optional kotlin.jvm.functions.Function1<androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation.NavBackStackEntry>,androidx.compose.animation.SizeTransform?>? sizeTransform, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method @Deprecated @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
}
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index 3641c95..fb1c6ba 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-import androidx.build.Publish
-import androidx.build.RunApiTasks
+import androidx.build.LibraryType
plugins {
id("AndroidXPlugin")
@@ -61,10 +60,9 @@
androidx {
name = "Compose Navigation"
- publish = Publish.SNAPSHOT_AND_RELEASE
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2020"
description = "Compose integration with Navigation"
- runApiTasks = new RunApiTasks.Yes()
legacyDisableKotlinStrictApiMode = true
samples(projectOrArtifact(":navigation:navigation-compose:navigation-compose-samples"))
}
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavigationDemos.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavigationDemos.kt
index 9611569..113d02d 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavigationDemos.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavigationDemos.kt
@@ -29,6 +29,7 @@
ComposableDemo("Navigation with Args") { NavWithArgsDemo() },
ComposableDemo("Navigation by DeepLink") { NavByDeepLinkDemo() },
ComposableDemo("Navigation PopUpTo") { NavPopUpToDemo() },
- ComposableDemo("Navigation SingleTop") { NavSingleTopDemo() }
+ ComposableDemo("Navigation SingleTop") { NavSingleTopDemo() },
+ ComposableDemo("Size Transform Demo") { SizeTransformDemo() }
)
)
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/SizeTransformDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/SizeTransformDemo.kt
new file mode 100644
index 0000000..8084f92
--- /dev/null
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/SizeTransformDemo.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.compose.demos
+
+import androidx.compose.runtime.Composable
+import androidx.navigation.compose.samples.SizeTransformNav
+
+@Composable
+fun SizeTransformDemo() {
+ SizeTransformNav()
+}
diff --git a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/SizeTransformSample.kt b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/SizeTransformSample.kt
new file mode 100644
index 0000000..ce55fdb
--- /dev/null
+++ b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/SizeTransformSample.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.navigation.compose.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.SizeTransform
+import androidx.compose.animation.core.keyframes
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+
+@Sampled
+@Composable
+fun SizeTransformNav() {
+ val navController = rememberNavController()
+ Box {
+ NavHost(navController, startDestination = "collapsed") {
+ composable("collapsed",
+ enterTransition = { EnterTransition.None },
+ exitTransition = { ExitTransition.None },
+ sizeTransform = {
+ SizeTransform { initialSize, targetSize ->
+ keyframes {
+ durationMillis = 500
+ IntSize(initialSize.width,
+ (initialSize.height + targetSize.height) / 2) at 150
+ }
+ }
+ }) {
+ CollapsedScreen { navController.navigate("expanded") }
+ }
+ composable("expanded",
+ enterTransition = { EnterTransition.None },
+ exitTransition = { ExitTransition.None },
+ sizeTransform = {
+ SizeTransform { initialSize, targetSize ->
+ keyframes {
+ durationMillis = 500
+ IntSize(targetSize.width, initialSize.height + 400) at 150
+ }
+ }
+ }) {
+ ExpandedScreen { navController.popBackStack() }
+ }
+ }
+ }
+}
+
+@Composable
+fun CollapsedScreen(onNavigate: () -> Unit) {
+ Box(Modifier.clickable { onNavigate() }.size(40.dp).background(Green))
+}
+
+@Composable
+fun ExpandedScreen(onNavigate: () -> Unit) {
+ Box(Modifier.clickable { onNavigate() }.size(500.dp).background(Blue))
+}
+
+private val Blue = Color(0xFF2196F3)
+private val Green = Color(0xFF4CAF50)
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostScreenShotTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostScreenShotTest.kt
index 1fcbd3e..33c9de8 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostScreenShotTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostScreenShotTest.kt
@@ -22,6 +22,10 @@
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.annotation.RequiresApi
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.SizeTransform
+import androidx.compose.animation.core.keyframes
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.background
@@ -36,6 +40,7 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onParent
+import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.navigation.NavHostController
@@ -174,8 +179,72 @@
"testNavHostPredictiveBackAnimations"
)
}
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun testNavHostSizeTransform() {
+ lateinit var navController: NavHostController
+ composeTestRule.setContent {
+ navController = rememberNavController()
+ Box {
+ NavHost(navController, startDestination = FIRST) {
+ composable(FIRST,
+ enterTransition = { EnterTransition.None },
+ exitTransition = { ExitTransition.None },
+ sizeTransform = {
+ SizeTransform { initialSize, targetSize ->
+ keyframes {
+ durationMillis = 500
+ IntSize(initialSize.width,
+ (initialSize.height + targetSize.height) / 2) at 150
+ }
+ }
+ }) {
+ Box(Modifier.size(40.dp).background(Green)) {
+ BasicText(FIRST)
+ }
+ }
+ composable(SECOND,
+ enterTransition = { EnterTransition.None },
+ exitTransition = { ExitTransition.None },
+ sizeTransform = {
+ SizeTransform { initialSize, targetSize ->
+ keyframes {
+ durationMillis = 500
+ IntSize(targetSize.width, initialSize.height + 400) at 150
+ }
+ }
+ }) {
+ Box(Modifier.size(500.dp).background(Blue)) {
+ BasicText(SECOND)
+ }
+ }
+ }
+ }
+ }
+
+ // don't start drawing second yet
+ composeTestRule.runOnIdle {
+ composeTestRule.mainClock.autoAdvance = false
+ navController.navigate(SECOND)
+ }
+
+ composeTestRule.waitForIdle()
+ // the image should show a blue square of the second destination half way
+ // down the screen.
+ composeTestRule.mainClock.advanceTimeBy(75)
+
+ composeTestRule.onNodeWithText(SECOND).onParent()
+ .captureToImage().assertAgainstGolden(
+ screenshotRule,
+ "testNavHostSizeTransform"
+ )
+ }
}
private const val FIRST = "first"
private const val SECOND = "second"
private const val THIRD = "third"
+
+private val Blue = Color(0xFF2196F3)
+private val Green = Color(0xFF4CAF50)
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavGraphNavigator.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavGraphNavigator.kt
index 58f5c51..4f7e97d 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavGraphNavigator.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavGraphNavigator.kt
@@ -19,6 +19,7 @@
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.SizeTransform
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraph
import androidx.navigation.NavGraphNavigator
@@ -51,5 +52,8 @@
internal var popExitTransition: (@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = null
+
+ internal var sizeTransform: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)? = null
}
}
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigator.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigator.kt
index 50cdf30..fbc96b1 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigator.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/ComposeNavigator.kt
@@ -20,6 +20,7 @@
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.SizeTransform
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.navigation.NavBackStackEntry
@@ -122,6 +123,9 @@
internal var popExitTransition: (@JvmSuppressWildcards
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = null
+
+ internal var sizeTransform: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)? = null
}
internal companion object {
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
index 31be078..cf5a320 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
@@ -20,6 +20,7 @@
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.SizeTransform
import androidx.compose.runtime.Composable
import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NamedNavArgument
@@ -75,6 +76,10 @@
* @param popExitTransition callback to determine the destination's popExit transition
* @param content composable for the destination
*/
+@Deprecated(
+ message = "Deprecated in favor of composable builder that supports sizeTransform",
+ level = DeprecationLevel.HIDDEN
+)
public fun NavGraphBuilder.composable(
route: String,
arguments: List<NamedNavArgument> = emptyList(),
@@ -112,6 +117,58 @@
}
/**
+ * Add the [Composable] to the [NavGraphBuilder]
+ *
+ * @param route route for the destination
+ * @param arguments list of arguments to associate with destination
+ * @param deepLinks list of deep links to associate with the destinations
+ * @param enterTransition callback to determine the destination's enter transition
+ * @param exitTransition callback to determine the destination's exit transition
+ * @param popEnterTransition callback to determine the destination's popEnter transition
+ * @param popExitTransition callback to determine the destination's popExit transition
+ * @param sizeTransform callback to determine the destination's sizeTransform.
+ * @param content composable for the destination
+ */
+public fun NavGraphBuilder.composable(
+ route: String,
+ arguments: List<NamedNavArgument> = emptyList(),
+ deepLinks: List<NavDeepLink> = emptyList(),
+ enterTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
+ exitTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
+ popEnterTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? =
+ enterTransition,
+ popExitTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? =
+ exitTransition,
+ sizeTransform: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)? = null,
+ content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit
+) {
+ addDestination(
+ ComposeNavigator.Destination(
+ provider[ComposeNavigator::class],
+ content
+ ).apply {
+ this.route = route
+ arguments.forEach { (argumentName, argument) ->
+ addArgument(argumentName, argument)
+ }
+ deepLinks.forEach { deepLink ->
+ addDeepLink(deepLink)
+ }
+ this.enterTransition = enterTransition
+ this.exitTransition = exitTransition
+ this.popEnterTransition = popEnterTransition
+ this.popExitTransition = popExitTransition
+ this.sizeTransform = sizeTransform
+ }
+ )
+}
+
+/**
* Construct a nested [NavGraph]
*
* @sample androidx.navigation.compose.samples.NestedNavInGraphWithArgs
@@ -133,16 +190,8 @@
deepLinks: List<NavDeepLink> = emptyList(),
builder: NavGraphBuilder.() -> Unit
) {
- addDestination(
- NavGraphBuilder(provider, startDestination, route).apply(builder).build().apply {
- arguments.forEach { (argumentName, argument) ->
- addArgument(argumentName, argument)
- }
- deepLinks.forEach { deepLink ->
- addDeepLink(deepLink)
- }
- }
- )
+ navigation(startDestination, route, arguments, deepLinks, null, null,
+ null, null, null, builder)
}
/**
@@ -161,6 +210,10 @@
*
* @return the newly constructed nested NavGraph
*/
+@Deprecated(
+ message = "Deprecated in favor of navigation builder that supports sizeTransform",
+ level = DeprecationLevel.HIDDEN
+)
public fun NavGraphBuilder.navigation(
startDestination: String,
route: String,
@@ -178,6 +231,48 @@
)? = exitTransition,
builder: NavGraphBuilder.() -> Unit
) {
+ navigation(startDestination, route, arguments, deepLinks, enterTransition, exitTransition,
+ popEnterTransition, popExitTransition, null, builder)
+}
+
+/**
+ * Construct a nested [NavGraph]
+ *
+ * @sample androidx.navigation.compose.samples.SizeTransformNav
+ *
+ * @param startDestination the starting destination's route for this NavGraph
+ * @param route the destination's unique route
+ * @param arguments list of arguments to associate with destination
+ * @param deepLinks list of deep links to associate with the destinations
+ * @param enterTransition callback to define enter transitions for destination in this NavGraph
+ * @param exitTransition callback to define exit transitions for destination in this NavGraph
+ * @param popEnterTransition callback to define pop enter transitions for destination in this
+ * NavGraph
+ * @param popExitTransition callback to define pop exit transitions for destination in this NavGraph
+ * @param sizeTransform callback to define the size transform for destinations in this NavGraph
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed nested NavGraph
+ */
+public fun NavGraphBuilder.navigation(
+ startDestination: String,
+ route: String,
+ arguments: List<NamedNavArgument> = emptyList(),
+ deepLinks: List<NavDeepLink> = emptyList(),
+ enterTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
+ exitTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
+ popEnterTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?
+ )? = enterTransition,
+ popExitTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?
+ )? = exitTransition,
+ sizeTransform: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)? = null,
+ builder: NavGraphBuilder.() -> Unit
+) {
addDestination(
NavGraphBuilder(provider, startDestination, route).apply(builder).build().apply {
arguments.forEach { (argumentName, argument) ->
@@ -191,6 +286,7 @@
this.exitTransition = exitTransition
this.popEnterTransition = popEnterTransition
this.popExitTransition = popExitTransition
+ this.sizeTransform = sizeTransform
}
}
)
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
index 0109a42..f47c16d 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
@@ -23,6 +23,7 @@
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.SeekableTransitionState
import androidx.compose.animation.core.rememberTransition
import androidx.compose.animation.core.tween
@@ -114,6 +115,10 @@
* @param popExitTransition callback to define popExit transitions for destination in this host
* @param builder the builder used to construct the graph
*/
+@Deprecated(
+ message = "Deprecated in favor of NavHost that supports sizeTransform",
+ level = DeprecationLevel.HIDDEN
+)
@Composable
public fun NavHost(
navController: NavHostController,
@@ -151,6 +156,63 @@
* Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
* the provided [navController].
*
+ * The builder passed into this method is [remember]ed. This means that for this NavHost, the
+ * contents of the builder cannot be changed.
+ *
+ * @param navController the navController for this host
+ * @param startDestination the route for the start destination
+ * @param modifier The modifier to be applied to the layout.
+ * @param contentAlignment The [Alignment] of the [AnimatedContent]
+ * @param route the route for the graph
+ * @param enterTransition callback to define enter transitions for destination in this host
+ * @param exitTransition callback to define exit transitions for destination in this host
+ * @param popEnterTransition callback to define popEnter transitions for destination in this host
+ * @param popExitTransition callback to define popExit transitions for destination in this host
+ * @param sizeTransform callback to define the size transform for destinations in this host
+ * @param builder the builder used to construct the graph
+ */
+@Composable
+public fun NavHost(
+ navController: NavHostController,
+ startDestination: String,
+ modifier: Modifier = Modifier,
+ contentAlignment: Alignment = Alignment.Center,
+ route: String? = null,
+ enterTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) =
+ { fadeIn(animationSpec = tween(700)) },
+ exitTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) =
+ { fadeOut(animationSpec = tween(700)) },
+ popEnterTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) = enterTransition,
+ popExitTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) = exitTransition,
+ sizeTransform: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)? = null,
+ builder: NavGraphBuilder.() -> Unit
+) {
+ NavHost(
+ navController,
+ remember(route, startDestination, builder) {
+ navController.createGraph(startDestination, route, builder)
+ },
+ modifier,
+ contentAlignment,
+ enterTransition,
+ exitTransition,
+ popEnterTransition,
+ popExitTransition,
+ sizeTransform
+ )
+}
+
+/**
+ * Provides in place in the Compose hierarchy for self contained navigation to occur.
+ *
+ * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
+ * the provided [navController].
+ *
* The graph passed into this method is [remember]ed. This means that for this NavHost, the graph
* cannot be changed.
*
@@ -184,6 +246,10 @@
* @param popEnterTransition callback to define popEnter transitions for destination in this host
* @param popExitTransition callback to define popExit transitions for destination in this host
*/
+@Deprecated(
+ message = "Deprecated in favor of NavHost that supports sizeTransform",
+ level = DeprecationLevel.HIDDEN
+)
@SuppressLint("StateFlowValueCalledInComposition")
@Composable
public fun NavHost(
@@ -200,6 +266,54 @@
popExitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) =
exitTransition,
) {
+ NavHost(
+ navController,
+ graph,
+ modifier,
+ contentAlignment,
+ enterTransition,
+ exitTransition,
+ popEnterTransition,
+ popExitTransition
+ )
+}
+
+/**
+ * Provides in place in the Compose hierarchy for self contained navigation to occur.
+ *
+ * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
+ * the provided [navController].
+ *
+ * @param navController the navController for this host
+ * @param graph the graph for this host
+ * @param modifier The modifier to be applied to the layout.
+ * @param contentAlignment The [Alignment] of the [AnimatedContent]
+ * @param enterTransition callback to define enter transitions for destination in this host
+ * @param exitTransition callback to define exit transitions for destination in this host
+ * @param popEnterTransition callback to define popEnter transitions for destination in this host
+ * @param popExitTransition callback to define popExit transitions for destination in this host
+ * @param sizeTransform callback to define the size transform for destinations in this host
+ */
+@SuppressLint("StateFlowValueCalledInComposition")
+@Composable
+public fun NavHost(
+ navController: NavHostController,
+ graph: NavGraph,
+ modifier: Modifier = Modifier,
+ contentAlignment: Alignment = Alignment.Center,
+ enterTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) =
+ { fadeIn(animationSpec = tween(700)) },
+ exitTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) =
+ { fadeOut(animationSpec = tween(700)) },
+ popEnterTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) = enterTransition,
+ popExitTransition: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) = exitTransition,
+ sizeTransform: (@JvmSuppressWildcards
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)? = null
+) {
val lifecycleOwner = LocalLifecycleOwner.current
val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
@@ -222,7 +336,6 @@
var progress by remember { mutableFloatStateOf(0f) }
var inPredictiveBack by remember { mutableStateOf(false) }
PredictiveBackHandler(currentBackStack.size > 1) { backEvent ->
- inPredictiveBack = true
progress = 0f
val currentBackStackEntry = currentBackStack.lastOrNull()
composeNavigator.prepareForTransition(currentBackStackEntry!!)
@@ -230,6 +343,7 @@
composeNavigator.prepareForTransition(previousEntry)
try {
backEvent.collect {
+ inPredictiveBack = true
progress = it.progress
}
inPredictiveBack = false
@@ -291,6 +405,15 @@
}
}
+ val finalSizeTransform:
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform? = {
+ val targetDestination = targetState.destination as ComposeNavigator.Destination
+
+ targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
+ destination.createSizeTransform(this)
+ } ?: sizeTransform?.invoke(this)
+ }
+
DisposableEffect(true) {
onDispose {
visibleEntries.forEach { entry ->
@@ -330,7 +453,8 @@
else -> initialZIndex + 1f
}.also { zIndices[targetState.id] = it }
- ContentTransform(finalEnter(this), finalExit(this), targetZIndex)
+ ContentTransform(finalEnter(this), finalExit(this), targetZIndex,
+ finalSizeTransform(this))
} else {
EnterTransition.None togetherWith ExitTransition.None
}
@@ -409,3 +533,11 @@
is ComposeNavGraphNavigator.ComposeNavGraph -> this.popExitTransition?.invoke(scope)
else -> null
}
+
+private fun NavDestination.createSizeTransform(
+ scope: AnimatedContentTransitionScope<NavBackStackEntry>
+): SizeTransform? = when (this) {
+ is ComposeNavigator.Destination -> this.sizeTransform?.invoke(scope)
+ is ComposeNavGraphNavigator.ComposeNavGraph -> this.sizeTransform?.invoke(scope)
+ else -> null
+}
diff --git a/navigation/navigation-fragment/build.gradle b/navigation/navigation-fragment/build.gradle
index 9533257..17bfab0 100644
--- a/navigation/navigation-fragment/build.gradle
+++ b/navigation/navigation-fragment/build.gradle
@@ -58,5 +58,8 @@
}
android {
+ defaultConfig {
+ multiDexEnabled true
+ }
namespace "androidx.navigation.fragment"
}
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
index 1ac6d52..8c877ef 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -25,6 +25,8 @@
import androidx.activity.addCallback
import androidx.core.os.bundleOf
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.navigation.NavDestination.Companion.createRoute
@@ -48,6 +50,8 @@
import androidx.testutils.test
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
import kotlin.test.assertFailsWith
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
@@ -1614,6 +1618,8 @@
Intents.init()
+ val destroyActivityLatch = CountDownLatch(1)
+
with(ActivityScenario.launchActivityForResult<TestActivity>(intent)) {
moveToState(Lifecycle.State.CREATED)
onActivity { activity ->
@@ -1632,8 +1638,17 @@
// The parent will be constructed in a new Activity after navigateUp()
navController.navigateUp()
}
+
+ activity.lifecycle.addObserver(object : LifecycleEventObserver {
+ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+ if (event.targetState == Lifecycle.State.DESTROYED) {
+ destroyActivityLatch.countDown()
+ }
+ }
+ })
}
+ assertThat(destroyActivityLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
assertThat(this.state).isEqualTo(Lifecycle.State.DESTROYED)
}
diff --git a/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml b/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml
index e9127f7..1cfa523 100644
--- a/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml
+++ b/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha07)" variant="all" version="8.4.0-alpha07">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
<issue
id="BanThreadSleep"
@@ -55,4 +55,13 @@
file="src/main/kotlin/androidx/navigation/safeargs/gradle/SafeArgsPlugin.kt"/>
</issue>
+ <issue
+ id="WithPluginClasspathUsage"
+ message="Avoid usage of GradleRunner#withPluginClasspath, which is broken. Instead use something like https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/testkit#gradle-testkit-support-plugin"
+ errorLine1=" .withProjectDir(projectRoot()).withPluginClasspath()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/test/kotlin/androidx/navigation/safeargs/gradle/BasePluginTest.kt"/>
+ </issue>
+
</issues>
diff --git a/paging/paging-common/api/current.txt b/paging/paging-common/api/current.txt
index 149fe1c..92ade99 100644
--- a/paging/paging-common/api/current.txt
+++ b/paging/paging-common/api/current.txt
@@ -149,8 +149,6 @@
}
public enum LoadType {
- method public static androidx.paging.LoadType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.paging.LoadType[] values();
enum_constant public static final androidx.paging.LoadType APPEND;
enum_constant public static final androidx.paging.LoadType PREPEND;
enum_constant public static final androidx.paging.LoadType REFRESH;
@@ -583,8 +581,6 @@
}
public enum RemoteMediator.InitializeAction {
- method public static androidx.paging.RemoteMediator.InitializeAction valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.paging.RemoteMediator.InitializeAction[] values();
enum_constant public static final androidx.paging.RemoteMediator.InitializeAction LAUNCH_INITIAL_REFRESH;
enum_constant public static final androidx.paging.RemoteMediator.InitializeAction SKIP_INITIAL_REFRESH;
}
@@ -605,8 +601,6 @@
}
public enum TerminalSeparatorType {
- method public static androidx.paging.TerminalSeparatorType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.paging.TerminalSeparatorType[] values();
enum_constant public static final androidx.paging.TerminalSeparatorType FULLY_COMPLETE;
enum_constant public static final androidx.paging.TerminalSeparatorType SOURCE_COMPLETE;
}
diff --git a/paging/paging-common/api/restricted_current.txt b/paging/paging-common/api/restricted_current.txt
index 149fe1c..92ade99 100644
--- a/paging/paging-common/api/restricted_current.txt
+++ b/paging/paging-common/api/restricted_current.txt
@@ -149,8 +149,6 @@
}
public enum LoadType {
- method public static androidx.paging.LoadType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.paging.LoadType[] values();
enum_constant public static final androidx.paging.LoadType APPEND;
enum_constant public static final androidx.paging.LoadType PREPEND;
enum_constant public static final androidx.paging.LoadType REFRESH;
@@ -583,8 +581,6 @@
}
public enum RemoteMediator.InitializeAction {
- method public static androidx.paging.RemoteMediator.InitializeAction valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.paging.RemoteMediator.InitializeAction[] values();
enum_constant public static final androidx.paging.RemoteMediator.InitializeAction LAUNCH_INITIAL_REFRESH;
enum_constant public static final androidx.paging.RemoteMediator.InitializeAction SKIP_INITIAL_REFRESH;
}
@@ -605,8 +601,6 @@
}
public enum TerminalSeparatorType {
- method public static androidx.paging.TerminalSeparatorType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.paging.TerminalSeparatorType[] values();
enum_constant public static final androidx.paging.TerminalSeparatorType FULLY_COMPLETE;
enum_constant public static final androidx.paging.TerminalSeparatorType SOURCE_COMPLETE;
}
diff --git a/paging/paging-common/build.gradle b/paging/paging-common/build.gradle
index 91110be..2cf84b9 100644
--- a/paging/paging-common/build.gradle
+++ b/paging/paging-common/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
+
import androidx.build.PlatformIdentifier
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import androidx.build.Publish
@@ -33,9 +33,6 @@
id("com.android.library")
}
-def macEnabled = KmpPlatformsKt.enableMac(project)
-def linuxEnabled = KmpPlatformsKt.enableLinux(project)
-
androidXMultiplatform {
jvm()
mac()
@@ -103,27 +100,22 @@
}
}
- if (macEnabled || linuxEnabled) {
- nativeMain {
- dependsOn(commonMain)
- dependencies {
- implementation(libs.atomicFu)
- }
- }
- nativeTest {
- dependsOn(commonTest)
+ nativeMain {
+ dependsOn(commonMain)
+ dependencies {
+ implementation(libs.atomicFu)
}
}
- if (macEnabled) {
- darwinMain {
- dependsOn(nativeMain)
- }
+ nativeTest {
+ dependsOn(commonTest)
}
- if (linuxEnabled) {
- linuxMain {
- dependsOn(nativeMain)
- }
+ darwinMain {
+ dependsOn(nativeMain)
+ }
+
+ linuxMain {
+ dependsOn(nativeMain)
}
targets.all { target ->
diff --git a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedListConfig.kt b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedListConfig.kt
index 0e4645f..a1e4299 100644
--- a/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedListConfig.kt
+++ b/paging/paging-common/src/commonJvmAndroidMain/kotlin/androidx/paging/PagedListConfig.kt
@@ -28,7 +28,8 @@
*/
@Suppress(
"FunctionName",
- "DEPRECATION"
+ "DEPRECATION",
+ "ReferencesDeprecated"
)
@JvmSynthetic
public fun Config(
diff --git a/paging/paging-testing/api/current.txt b/paging/paging-testing/api/current.txt
index ed16206..44bcd15 100644
--- a/paging/paging-testing/api/current.txt
+++ b/paging/paging-testing/api/current.txt
@@ -2,8 +2,6 @@
package androidx.paging.testing {
@VisibleForTesting public enum ErrorRecovery {
- method public static androidx.paging.testing.ErrorRecovery valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.paging.testing.ErrorRecovery[] values();
enum_constant public static final androidx.paging.testing.ErrorRecovery RETRY;
enum_constant public static final androidx.paging.testing.ErrorRecovery RETURN_CURRENT_SNAPSHOT;
enum_constant public static final androidx.paging.testing.ErrorRecovery THROW;
diff --git a/paging/paging-testing/api/restricted_current.txt b/paging/paging-testing/api/restricted_current.txt
index ed16206..44bcd15 100644
--- a/paging/paging-testing/api/restricted_current.txt
+++ b/paging/paging-testing/api/restricted_current.txt
@@ -2,8 +2,6 @@
package androidx.paging.testing {
@VisibleForTesting public enum ErrorRecovery {
- method public static androidx.paging.testing.ErrorRecovery valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.paging.testing.ErrorRecovery[] values();
enum_constant public static final androidx.paging.testing.ErrorRecovery RETRY;
enum_constant public static final androidx.paging.testing.ErrorRecovery RETURN_CURRENT_SNAPSHOT;
enum_constant public static final androidx.paging.testing.ErrorRecovery THROW;
diff --git a/paging/paging-testing/build.gradle b/paging/paging-testing/build.gradle
index dfd3274..9ee1935 100644
--- a/paging/paging-testing/build.gradle
+++ b/paging/paging-testing/build.gradle
@@ -22,7 +22,6 @@
* modifying its settings.
*/
import androidx.build.LibraryType
-import androidx.build.KmpPlatformsKt
import androidx.build.PlatformIdentifier
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import androidx.build.Publish
@@ -33,9 +32,6 @@
id("com.android.library")
}
-def macEnabled = KmpPlatformsKt.enableMac(project)
-def linuxEnabled = KmpPlatformsKt.enableLinux(project)
-
androidXMultiplatform {
jvm()
mac()
@@ -92,23 +88,17 @@
}
}
- if (macEnabled || linuxEnabled) {
- nativeMain {
- dependsOn(commonMain)
- dependencies {
- implementation(libs.atomicFu)
- }
+ nativeMain {
+ dependsOn(commonMain)
+ dependencies {
+ implementation(libs.atomicFu)
}
}
- if (macEnabled) {
- darwinMain {
- dependsOn(nativeMain)
- }
+ darwinMain {
+ dependsOn(nativeMain)
}
- if (linuxEnabled) {
- linuxMain {
- dependsOn(nativeMain)
- }
+ linuxMain {
+ dependsOn(nativeMain)
}
targets.all { target ->
diff --git a/placeholder-tests/build.gradle b/placeholder-tests/build.gradle.kts
similarity index 88%
rename from placeholder-tests/build.gradle
rename to placeholder-tests/build.gradle.kts
index 20e7b60e..d442e25 100644
--- a/placeholder-tests/build.gradle
+++ b/placeholder-tests/build.gradle.kts
@@ -27,10 +27,6 @@
androidTestImplementation(libs.testRules)
}
-androidx {
- name = "Dummy Project for test runner"
-}
-
android {
- namespace "androidx.testinfra.placeholderintegrationtest"
+ namespace = "androidx.testinfra.placeholderintegrationtest"
}
diff --git a/playground-common/androidx-shared.properties b/playground-common/androidx-shared.properties
index 5b6eb52..3ad3638 100644
--- a/playground-common/androidx-shared.properties
+++ b/playground-common/androidx-shared.properties
@@ -40,6 +40,8 @@
android.useAndroidX=true
android.nonTransitiveRClass=true
android.experimental.lint.missingBaselineIsEmptyBaseline=true
+# Remove when AGP defaults to 2.1.0
+android.prefabVersion=2.1.0
# Generate versioned API files
androidx.writeVersionedApiFiles=true
@@ -60,7 +62,7 @@
android.experimental.dependency.excludeLibraryComponentsFromConstraints=true
# Disallow resolving dependencies at configuration time, which is a slight performance problem
android.dependencyResolutionAtConfigurationTime.disallow=true
-android.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarnings,android.dependencyResolutionAtConfigurationTime.disallow,android.experimental.lint.missingBaselineIsEmptyBaseline,android.lint.printStackTrace,android.lint.baselineOmitLineNumbers,android.experimental.disableCompileSdkChecks,android.overrideVersionCheck,android.r8.maxWorkers,android.experimental.privacysandboxsdk.enable,android.experimental.lint.reservedMemoryPerTask,android.experimental.dependency.excludeLibraryComponentsFromConstraints
+android.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarnings,android.dependencyResolutionAtConfigurationTime.disallow,android.experimental.lint.missingBaselineIsEmptyBaseline,android.lint.printStackTrace,android.lint.baselineOmitLineNumbers,android.experimental.disableCompileSdkChecks,android.overrideVersionCheck,android.r8.maxWorkers,android.experimental.privacysandboxsdk.enable,android.experimental.lint.reservedMemoryPerTask,android.experimental.dependency.excludeLibraryComponentsFromConstraints,android.prefabVersion
# Workaround for b/162074215
android.includeDependencyInfoInApks=false
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index 5970697..c33684d 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -26,5 +26,5 @@
# Disable docs
androidx.enableDocumentation=false
androidx.playground.snapshotBuildId=11349412
-androidx.playground.metalavaBuildId=11427892
+androidx.playground.metalavaBuildId=11490059
androidx.studio.type=playground
\ No newline at end of file
diff --git a/privacysandbox/ads/ads-adservices-java/build.gradle b/privacysandbox/ads/ads-adservices-java/build.gradle
index 0da0d9d..e0897af 100644
--- a/privacysandbox/ads/ads-adservices-java/build.gradle
+++ b/privacysandbox/ads/ads-adservices-java/build.gradle
@@ -37,7 +37,6 @@
// To use CallbackToFutureAdapter
implementation "androidx.concurrent:concurrent-futures:1.1.0"
- implementation(libs.guavaAndroid)
api(libs.guavaListenableFuture)
implementation project(path: ':privacysandbox:ads:ads-adservices')
diff --git a/privacysandbox/ads/ads-adservices/api/current.ignore b/privacysandbox/ads/ads-adservices/api/current.ignore
deleted file mode 100644
index 05e0309..0000000
--- a/privacysandbox/ads/ads-adservices/api/current.ignore
+++ /dev/null
@@ -1,18 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest#shouldRecordObservation():
- Added method androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest.shouldRecordObservation()
-
-
-RemovedMethod: androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest#getShouldRecordObservation():
- Removed method androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest.getShouldRecordObservation()
-
-
-AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdCounterKeys():
- Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdCounterKeys()
-AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdFilters():
- Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdFilters()
-AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdRenderId():
- Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdRenderId()
-
-AddedField: androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome#Companion:
- Added field androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome.Companion
diff --git a/privacysandbox/ads/ads-adservices/api/restricted_current.ignore b/privacysandbox/ads/ads-adservices/api/restricted_current.ignore
deleted file mode 100644
index 05e0309..0000000
--- a/privacysandbox/ads/ads-adservices/api/restricted_current.ignore
+++ /dev/null
@@ -1,18 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest#shouldRecordObservation():
- Added method androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest.shouldRecordObservation()
-
-
-RemovedMethod: androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest#getShouldRecordObservation():
- Removed method androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest.getShouldRecordObservation()
-
-
-AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdCounterKeys():
- Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdCounterKeys()
-AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdFilters():
- Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdFilters()
-AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdRenderId():
- Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdRenderId()
-
-AddedField: androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome#Companion:
- Added field androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome.Companion
diff --git a/privacysandbox/plugins/plugins-privacysandbox-library/lint-baseline.xml b/privacysandbox/plugins/plugins-privacysandbox-library/lint-baseline.xml
new file mode 100644
index 0000000..21096cd
--- /dev/null
+++ b/privacysandbox/plugins/plugins-privacysandbox-library/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
+
+ <issue
+ id="WithPluginClasspathUsage"
+ message="Avoid usage of GradleRunner#withPluginClasspath, which is broken. Instead use something like https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/testkit#gradle-testkit-support-plugin"
+ errorLine1=" .withPluginClasspath()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/test/java/androidx/privacysandboxlibraryplugin/PrivacySandboxLibraryPluginTest.kt"/>
+ </issue>
+
+</issues>
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/build.gradle b/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
index 444737c..f8c0828 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
+++ b/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
@@ -129,6 +129,7 @@
androidTestBundleDex(project(":privacysandbox:sdkruntime:test-sdks:v2"))
// V3 was released as V4 (original release postponed)
androidTestBundleDex(project(":privacysandbox:sdkruntime:test-sdks:v4"))
+ androidTestBundleDex(project(":privacysandbox:sdkruntime:test-sdks:v5"))
}
android {
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdkTable.xml b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdkTable.xml
index 1b159d4..957ab77 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdkTable.xml
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdkTable.xml
@@ -39,4 +39,9 @@
<compat-config-path>RuntimeEnabledSdks/v4.xml</compat-config-path>
<package-name>androidx.privacysandbox.sdkruntime.testsdk.v4</package-name>
</runtime-enabled-sdk>
+ <runtime-enabled-sdk>
+ <compat-config-path>RuntimeEnabledSdks/v5.xml</compat-config-path>
+ <package-name>androidx.privacysandbox.sdkruntime.testsdk.v5</package-name>
+ <version-major>5</version-major>
+ </runtime-enabled-sdk>
</runtime-enabled-sdk-table>
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v5.xml b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v5.xml
new file mode 100644
index 0000000..8d21c64
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v5.xml
@@ -0,0 +1,19 @@
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<compat-config>
+ <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.v5.CompatProvider</compat-entrypoint>
+ <dex-path>test-sdks/v5/classes.dex</dex-path>
+</compat-config>
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt
index 81d35cf..af1cd4f 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt
@@ -19,13 +19,10 @@
import android.content.ContextWrapper
import android.os.Binder
import android.os.Bundle
-import androidx.privacysandbox.sdkruntime.client.activity.LocalSdkActivityHandlerRegistry
import androidx.privacysandbox.sdkruntime.client.activity.SdkActivity
import androidx.privacysandbox.sdkruntime.client.loader.CatchingSdkActivityHandler
import androidx.privacysandbox.sdkruntime.client.loader.asTestSdk
-import androidx.privacysandbox.sdkruntime.client.loader.extractSdkProviderFieldValue
import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
-import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.LOAD_SDK_INTERNAL_ERROR
import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.LOAD_SDK_SDK_DEFINED_ERROR
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkInfo
import androidx.test.core.app.ActivityScenario
@@ -133,47 +130,6 @@
}
@Test
- fun loadSdk_whenLocalSdkFailedToLoad_throwsInternalErrorException() {
- val context = ApplicationProvider.getApplicationContext<Context>()
- val managerCompat = SdkSandboxManagerCompat.from(context)
-
- val result = assertThrows(LoadSdkCompatException::class.java) {
- runBlocking {
- managerCompat.loadSdk(
- TestSdkConfigs.forSdkName("invalidEntryPoint").packageName,
- Bundle()
- )
- }
- }
-
- assertThat(result.loadSdkErrorCode).isEqualTo(LOAD_SDK_INTERNAL_ERROR)
- assertThat(result.message).isEqualTo("Failed to instantiate local SDK")
- }
-
- @Test
- fun loadSdk_afterUnloading_loadSdkAgain() {
- val context = ApplicationProvider.getApplicationContext<Context>()
- val managerCompat = SdkSandboxManagerCompat.from(context)
-
- val sdkName = TestSdkConfigs.CURRENT.packageName
-
- val sdkToUnload = runBlocking {
- managerCompat.loadSdk(sdkName, Bundle())
- }
-
- managerCompat.unloadSdk(sdkName)
-
- val reloadedSdk = runBlocking {
- managerCompat.loadSdk(sdkName, Bundle())
- }
-
- assertThat(managerCompat.getSandboxedSdks())
- .containsExactly(reloadedSdk)
- assertThat(reloadedSdk.getInterface())
- .isNotEqualTo(sdkToUnload.getInterface())
- }
-
- @Test
@SdkSuppress(maxSdkVersion = 33)
fun unloadSdk_whenNoLocalSdkLoadedAndApiBelow34_doesntThrow() {
val context = ApplicationProvider.getApplicationContext<Context>()
@@ -191,47 +147,13 @@
runBlocking {
managerCompat.loadSdk(sdkName, Bundle())
}
- val sdkProvider = managerCompat.getLocallyLoadedSdk(sdkName)!!.sdkProvider
-
managerCompat.unloadSdk(sdkName)
- val isBeforeUnloadSdkCalled = sdkProvider.extractSdkProviderFieldValue<Boolean>(
- fieldName = "isBeforeUnloadSdkCalled"
- )
-
- assertThat(isBeforeUnloadSdkCalled)
- .isTrue()
-
assertThat(managerCompat.getSandboxedSdks())
.isEmpty()
}
@Test
- fun unloadSdk_unregisterActivityHandlers() {
- val context = ApplicationProvider.getApplicationContext<Context>()
- val managerCompat = SdkSandboxManagerCompat.from(context)
-
- val packageName = TestSdkConfigs.forSdkName("v4").packageName
- val localSdk = runBlocking {
- managerCompat.loadSdk(
- packageName,
- Bundle()
- )
- }
-
- val testSdk = localSdk.asTestSdk()
- val token = testSdk.registerSdkSandboxActivityHandler(CatchingSdkActivityHandler())
-
- val registeredBefore = LocalSdkActivityHandlerRegistry.isRegistered(token)
- assertThat(registeredBefore).isTrue()
-
- managerCompat.unloadSdk(packageName)
-
- val registeredAfter = LocalSdkActivityHandlerRegistry.isRegistered(token)
- assertThat(registeredAfter).isFalse()
- }
-
- @Test
@SdkSuppress(maxSdkVersion = 33)
fun addSdkSandboxProcessDeathCallback_whenApiBelow34_doesntThrow() {
val context = ApplicationProvider.getApplicationContext<Context>()
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/LocalControllerTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/LocalControllerTest.kt
index 1d4260a..2c99ab4 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/LocalControllerTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/LocalControllerTest.kt
@@ -19,11 +19,12 @@
import android.os.Binder
import android.os.Bundle
import androidx.privacysandbox.sdkruntime.client.activity.LocalSdkActivityHandlerRegistry
-import androidx.privacysandbox.sdkruntime.client.loader.LocalSdkProvider
import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
import androidx.privacysandbox.sdkruntime.core.activity.ActivityHolder
import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
+import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
@@ -35,26 +36,51 @@
@RunWith(AndroidJUnit4::class)
class LocalControllerTest {
- private lateinit var locallyLoadedSdks: LocallyLoadedSdks
+ private lateinit var localSdkRegistry: StubLocalSdkRegistry
private lateinit var appOwnedSdkRegistry: StubAppOwnedSdkInterfaceRegistry
private lateinit var controller: LocalController
@Before
fun setUp() {
- locallyLoadedSdks = LocallyLoadedSdks()
+ localSdkRegistry = StubLocalSdkRegistry()
appOwnedSdkRegistry = StubAppOwnedSdkInterfaceRegistry()
- controller = LocalController(SDK_PACKAGE_NAME, locallyLoadedSdks, appOwnedSdkRegistry)
+ controller = LocalController(SDK_PACKAGE_NAME, localSdkRegistry, appOwnedSdkRegistry)
}
@Test
- fun getSandboxedSdks_returnsResultsFromLocallyLoadedSdks() {
+ fun loadSdk_whenSdkRegistryReturnsResult_returnResultFromSdkRegistry() {
+ val expectedResult = SandboxedSdkCompat(Binder())
+ localSdkRegistry.loadSdkResult = expectedResult
+
+ val sdkParams = Bundle()
+ val callback = StubLoadSdkCallback()
+
+ controller.loadSdk(SDK_PACKAGE_NAME, sdkParams, Runnable::run, callback)
+
+ assertThat(callback.lastResult).isEqualTo(expectedResult)
+ assertThat(callback.lastError).isNull()
+
+ assertThat(localSdkRegistry.lastLoadSdkName).isEqualTo(SDK_PACKAGE_NAME)
+ assertThat(localSdkRegistry.lastLoadSdkParams).isSameInstanceAs(sdkParams)
+ }
+
+ @Test
+ fun loadSdk_whenSdkRegistryThrowsException_rethrowsExceptionFromSdkRegistry() {
+ val expectedError = LoadSdkCompatException(RuntimeException(), Bundle())
+ localSdkRegistry.loadSdkError = expectedError
+
+ val callback = StubLoadSdkCallback()
+
+ controller.loadSdk(SDK_PACKAGE_NAME, Bundle(), Runnable::run, callback)
+
+ assertThat(callback.lastError).isEqualTo(expectedError)
+ assertThat(callback.lastResult).isNull()
+ }
+
+ @Test
+ fun getSandboxedSdks_returnsResultsFromLocalSdkRegistry() {
val sandboxedSdk = SandboxedSdkCompat(Binder())
- locallyLoadedSdks.put(
- "sdk", LocallyLoadedSdks.Entry(
- sdkProvider = NoOpSdkProvider(),
- sdk = sandboxedSdk
- )
- )
+ localSdkRegistry.getLoadedSdksResult = listOf(sandboxedSdk)
val result = controller.getSandboxedSdks()
assertThat(result).containsExactly(sandboxedSdk)
@@ -98,7 +124,7 @@
val anotherSdkController = LocalController(
"LocalControllerTest.anotherSdk",
- locallyLoadedSdks,
+ localSdkRegistry,
appOwnedSdkRegistry
)
val anotherSdkHandler = object : SdkSandboxActivityHandlerCompat {
@@ -130,14 +156,50 @@
assertThat(registeredHandler).isNull()
}
- private class NoOpSdkProvider : LocalSdkProvider(Any()) {
- override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
+ private class StubLocalSdkRegistry : SdkRegistry {
+
+ var getLoadedSdksResult: List<SandboxedSdkCompat> = emptyList()
+
+ var loadSdkResult: SandboxedSdkCompat? = null
+ var loadSdkError: LoadSdkCompatException? = null
+
+ var lastLoadSdkName: String? = null
+ var lastLoadSdkParams: Bundle? = null
+
+ override fun isResponsibleFor(sdkName: String): Boolean {
throw IllegalStateException("Unexpected call")
}
- override fun beforeUnloadSdk() {
+ override fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
+ lastLoadSdkName = sdkName
+ lastLoadSdkParams = params
+
+ if (loadSdkError != null) {
+ throw loadSdkError!!
+ }
+
+ return loadSdkResult!!
+ }
+
+ override fun unloadSdk(sdkName: String) {
throw IllegalStateException("Unexpected call")
}
+
+ override fun getLoadedSdks(): List<SandboxedSdkCompat> = getLoadedSdksResult
+ }
+
+ private class StubLoadSdkCallback : SdkSandboxControllerCompat.LoadSdkCallback {
+
+ var lastResult: SandboxedSdkCompat? = null
+ var lastError: LoadSdkCompatException? = null
+
+ override fun onResult(result: SandboxedSdkCompat) {
+ lastResult = result
+ }
+
+ override fun onError(error: LoadSdkCompatException) {
+ lastError = error
+ }
}
private class StubAppOwnedSdkInterfaceRegistry : AppOwnedSdkRegistry {
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/impl/LocalSdkRegistryTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/impl/LocalSdkRegistryTest.kt
new file mode 100644
index 0000000..b386ed3
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/impl/LocalSdkRegistryTest.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.sdkruntime.client.controller.impl
+
+import android.content.Context
+import android.os.Bundle
+import androidx.privacysandbox.sdkruntime.client.TestSdkConfigs
+import androidx.privacysandbox.sdkruntime.client.activity.LocalSdkActivityHandlerRegistry
+import androidx.privacysandbox.sdkruntime.client.loader.CatchingSdkActivityHandler
+import androidx.privacysandbox.sdkruntime.client.loader.asTestSdk
+import androidx.privacysandbox.sdkruntime.client.loader.extractSdkProviderFieldValue
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkInfo
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LocalSdkRegistryTest {
+
+ private lateinit var context: Context
+ private lateinit var localSdkRegistry: LocalSdkRegistry
+
+ @Before
+ fun setUp() {
+ context = ApplicationProvider.getApplicationContext()
+ localSdkRegistry = LocalSdkRegistry.create(context, LocalAppOwnedSdkRegistry())
+ }
+
+ @Test
+ fun isResponsibleFor_LocalSdk_returnsTrue() {
+ val result = localSdkRegistry.isResponsibleFor(TestSdkConfigs.CURRENT.packageName)
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun isResponsibleFor_NonLocalSdk_returnsFalse() {
+ val result = localSdkRegistry.isResponsibleFor("non-local-sdk")
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun loadSdk_whenLocalSdkExists_returnsLocallyLoadedSdk() {
+ val result = localSdkRegistry.loadSdk(
+ TestSdkConfigs.CURRENT.packageName,
+ Bundle()
+ )
+
+ assertThat(result.getInterface()!!.javaClass.classLoader)
+ .isNotSameInstanceAs(localSdkRegistry.javaClass.classLoader)
+
+ assertThat(result.getSdkInfo())
+ .isEqualTo(
+ SandboxedSdkInfo(
+ name = TestSdkConfigs.CURRENT.packageName,
+ version = 42
+ )
+ )
+
+ assertThat(localSdkRegistry.getLoadedSdks()).containsExactly(result)
+ }
+
+ @Test
+ fun loadSdk_whenLocalSdkExists_rethrowsExceptionFromLocallyLoadedSdk() {
+ val params = Bundle()
+ params.putBoolean("needFail", true)
+
+ val result = Assert.assertThrows(LoadSdkCompatException::class.java) {
+ localSdkRegistry.loadSdk(
+ TestSdkConfigs.CURRENT.packageName,
+ params
+ )
+ }
+
+ assertThat(result.extraInformation).isEqualTo(params)
+ assertThat(result.loadSdkErrorCode).isEqualTo(
+ LoadSdkCompatException.LOAD_SDK_SDK_DEFINED_ERROR
+ )
+ assertThat(localSdkRegistry.getLoadedSdks()).isEmpty()
+ }
+
+ @Test
+ fun loadSdk_whenLocalSdkFailedToLoad_throwsInternalErrorException() {
+ val result = Assert.assertThrows(LoadSdkCompatException::class.java) {
+ localSdkRegistry.loadSdk(
+ TestSdkConfigs.forSdkName("invalidEntryPoint").packageName,
+ Bundle()
+ )
+ }
+
+ assertThat(result.loadSdkErrorCode).isEqualTo(
+ LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR
+ )
+ assertThat(result.message).isEqualTo("Failed to instantiate local SDK")
+ assertThat(localSdkRegistry.getLoadedSdks()).isEmpty()
+ }
+
+ @Test
+ fun loadSdk_whenSdkAlreadyLoaded_throwsSdkAlreadyLoadedException() {
+ val sdkName = TestSdkConfigs.CURRENT.packageName
+ val firstTimeLoadedSdk = localSdkRegistry.loadSdk(sdkName, Bundle())
+
+ val result = Assert.assertThrows(LoadSdkCompatException::class.java) {
+ localSdkRegistry.loadSdk(sdkName, Bundle())
+ }
+
+ assertThat(result.loadSdkErrorCode).isEqualTo(
+ LoadSdkCompatException.LOAD_SDK_ALREADY_LOADED
+ )
+ assertThat(localSdkRegistry.getLoadedSdks()).containsExactly(firstTimeLoadedSdk)
+ }
+
+ @Test
+ fun loadSdk_whenNoLocalSdkExists_throwsSdkNotFoundException() {
+ val result = Assert.assertThrows(LoadSdkCompatException::class.java) {
+ localSdkRegistry.loadSdk(
+ "sdk-doesnt-exist",
+ Bundle()
+ )
+ }
+
+ assertThat(result.loadSdkErrorCode).isEqualTo(LoadSdkCompatException.LOAD_SDK_NOT_FOUND)
+ assertThat(localSdkRegistry.getLoadedSdks()).isEmpty()
+ }
+
+ @Test
+ fun loadSdk_afterUnloading_loadSdkAgain() {
+ val sdkName = TestSdkConfigs.CURRENT.packageName
+ val sdkToUnload = localSdkRegistry.loadSdk(sdkName, Bundle())
+
+ localSdkRegistry.unloadSdk(sdkName)
+ val reloadedSdk = localSdkRegistry.loadSdk(sdkName, Bundle())
+
+ assertThat(localSdkRegistry.getLoadedSdks())
+ .containsExactly(reloadedSdk)
+ assertThat(reloadedSdk.getInterface())
+ .isNotEqualTo(sdkToUnload.getInterface())
+ }
+
+ @Test
+ fun unloadSdk_whenLocalSdkLoaded_unloadLocallyLoadedSdk() {
+ val sdkName = TestSdkConfigs.CURRENT.packageName
+ localSdkRegistry.loadSdk(sdkName, Bundle())
+ val sdkProvider = localSdkRegistry.getLoadedSdkProvider(sdkName)!!
+
+ localSdkRegistry.unloadSdk(sdkName)
+
+ val isBeforeUnloadSdkCalled = sdkProvider.extractSdkProviderFieldValue<Boolean>(
+ fieldName = "isBeforeUnloadSdkCalled"
+ )
+ assertThat(isBeforeUnloadSdkCalled).isTrue()
+ assertThat(localSdkRegistry.getLoadedSdks()).isEmpty()
+ }
+
+ @Test
+ fun unloadSdk_whenNoLocalSdkLoaded_doesntThrow() {
+ localSdkRegistry.unloadSdk(TestSdkConfigs.CURRENT.packageName)
+ }
+
+ @Test
+ fun unloadSdk_unregisterActivityHandlers() {
+ val packageName = TestSdkConfigs.CURRENT.packageName
+ val localSdk = localSdkRegistry.loadSdk(
+ packageName,
+ Bundle()
+ )
+
+ val testSdk = localSdk.asTestSdk()
+ val token = testSdk.registerSdkSandboxActivityHandler(CatchingSdkActivityHandler())
+
+ val registeredBefore = LocalSdkActivityHandlerRegistry.isRegistered(token)
+ assertThat(registeredBefore).isTrue()
+
+ localSdkRegistry.unloadSdk(packageName)
+
+ val registeredAfter = LocalSdkActivityHandlerRegistry.isRegistered(token)
+ assertThat(registeredAfter).isFalse()
+ }
+}
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
index e04bd1ff..1de3a50 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
@@ -28,19 +28,15 @@
import androidx.annotation.RequiresApi
import androidx.core.os.BuildCompat
import androidx.core.os.asOutcomeReceiver
-import androidx.privacysandbox.sdkruntime.client.activity.LocalSdkActivityHandlerRegistry
import androidx.privacysandbox.sdkruntime.client.activity.LocalSdkActivityStarter
-import androidx.privacysandbox.sdkruntime.client.config.LocalSdkConfigsHolder
import androidx.privacysandbox.sdkruntime.client.controller.AppOwnedSdkRegistry
-import androidx.privacysandbox.sdkruntime.client.controller.LocalControllerFactory
-import androidx.privacysandbox.sdkruntime.client.controller.LocallyLoadedSdks
+import androidx.privacysandbox.sdkruntime.client.controller.SdkRegistry
import androidx.privacysandbox.sdkruntime.client.controller.impl.LocalAppOwnedSdkRegistry
+import androidx.privacysandbox.sdkruntime.client.controller.impl.LocalSdkRegistry
import androidx.privacysandbox.sdkruntime.client.controller.impl.PlatformAppOwnedSdkRegistry
-import androidx.privacysandbox.sdkruntime.client.loader.SdkLoader
import androidx.privacysandbox.sdkruntime.core.AdServicesInfo
import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
-import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.LOAD_SDK_ALREADY_LOADED
import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.LOAD_SDK_NOT_FOUND
import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.toLoadCompatSdkException
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
@@ -94,10 +90,8 @@
*/
class SdkSandboxManagerCompat private constructor(
private val platformApi: PlatformApi,
- private val configHolder: LocalSdkConfigsHolder,
- private val localLocallyLoadedSdks: LocallyLoadedSdks,
- private val appOwnedSdkRegistry: AppOwnedSdkRegistry,
- private val sdkLoader: SdkLoader
+ private val localSdkRegistry: SdkRegistry,
+ private val appOwnedSdkRegistry: AppOwnedSdkRegistry
) {
/**
* Load SDK in a SDK sandbox java process or locally.
@@ -128,23 +122,10 @@
sdkName: String,
params: Bundle
): SandboxedSdkCompat {
- if (localLocallyLoadedSdks.isLoaded(sdkName)) {
- throw LoadSdkCompatException(LOAD_SDK_ALREADY_LOADED, "$sdkName already loaded")
+ val isLocalSdk = localSdkRegistry.isResponsibleFor(sdkName)
+ if (isLocalSdk) {
+ return localSdkRegistry.loadSdk(sdkName, params)
}
-
- val sdkConfig = configHolder.getSdkConfig(sdkName)
- if (sdkConfig != null) {
- val sdkProvider = sdkLoader.loadSdk(sdkConfig)
- val sandboxedSdkCompat = sdkProvider.onLoadSdk(params)
- localLocallyLoadedSdks.put(
- sdkName, LocallyLoadedSdks.Entry(
- sdkProvider = sdkProvider,
- sdk = sandboxedSdkCompat
- )
- )
- return sandboxedSdkCompat
- }
-
return platformApi.loadSdk(sdkName, params)
}
@@ -158,12 +139,11 @@
* @see [SdkSandboxManager.unloadSdk]
*/
fun unloadSdk(sdkName: String) {
- val localEntry = localLocallyLoadedSdks.remove(sdkName)
- if (localEntry == null) {
- platformApi.unloadSdk(sdkName)
+ val isLocalSdk = localSdkRegistry.isResponsibleFor(sdkName)
+ if (isLocalSdk) {
+ localSdkRegistry.unloadSdk(sdkName)
} else {
- localEntry.sdkProvider.beforeUnloadSdk()
- LocalSdkActivityHandlerRegistry.unregisterAllActivityHandlersForSdk(sdkName)
+ platformApi.unloadSdk(sdkName)
}
}
@@ -210,7 +190,7 @@
*/
fun getSandboxedSdks(): List<SandboxedSdkCompat> {
val platformResult = platformApi.getSandboxedSdks()
- val localResult = localLocallyLoadedSdks.getLoadedSdks()
+ val localResult = localSdkRegistry.getLoadedSdks()
return platformResult + localResult
}
@@ -265,10 +245,6 @@
platformApi.startSdkSandboxActivity(fromActivity, sdkActivityToken)
}
- @TestOnly
- internal fun getLocallyLoadedSdk(sdkName: String): LocallyLoadedSdks.Entry? =
- localLocallyLoadedSdks.get(sdkName)
-
private interface PlatformApi {
@DoNotInline
suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat
@@ -426,18 +402,13 @@
val reference = sInstances[context]
var instance = reference?.get()
if (instance == null) {
- val configHolder = LocalSdkConfigsHolder.load(context)
- val localSdks = LocallyLoadedSdks()
val appOwnedSdkRegistry = AppOwnedSdkRegistryFactory.create(context)
- val controllerFactory = LocalControllerFactory(localSdks, appOwnedSdkRegistry)
- val sdkLoader = SdkLoader.create(context, controllerFactory)
+ val localSdkRegistry = LocalSdkRegistry.create(context, appOwnedSdkRegistry)
val platformApi = PlatformApiFactory.create(context)
instance = SdkSandboxManagerCompat(
platformApi,
- configHolder,
- localSdks,
- appOwnedSdkRegistry,
- sdkLoader
+ localSdkRegistry,
+ appOwnedSdkRegistry
)
sInstances[context] = WeakReference(instance)
}
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalController.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalController.kt
index a57674c..5780689 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalController.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalController.kt
@@ -31,7 +31,7 @@
*/
internal class LocalController(
private val sdkPackageName: String,
- private val locallyLoadedSdks: LocallyLoadedSdks,
+ private val localSdkRegistry: SdkRegistry,
private val appOwnedSdkRegistry: AppOwnedSdkRegistry
) : SdkSandboxControllerCompat.SandboxControllerImpl {
@@ -41,18 +41,20 @@
executor: Executor,
callback: SdkSandboxControllerCompat.LoadSdkCallback
) {
- executor.execute {
- callback.onError(
- LoadSdkCompatException(
- LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
- "Shouldn't be called"
- )
- )
+ try {
+ val result = localSdkRegistry.loadSdk(sdkName, params)
+ executor.execute {
+ callback.onResult(result)
+ }
+ } catch (ex: LoadSdkCompatException) {
+ executor.execute {
+ callback.onError(ex)
+ }
}
}
override fun getSandboxedSdks(): List<SandboxedSdkCompat> {
- return locallyLoadedSdks.getLoadedSdks()
+ return localSdkRegistry.getLoadedSdks()
}
override fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> =
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalControllerFactory.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalControllerFactory.kt
index e6d1f0c..37932c81 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalControllerFactory.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocalControllerFactory.kt
@@ -24,12 +24,12 @@
* Create [LocalController] instance for specific sdk.
*/
internal class LocalControllerFactory(
- private val locallyLoadedSdks: LocallyLoadedSdks,
+ private val localSdkRegistry: SdkRegistry,
private val appOwnedSdkRegistry: AppOwnedSdkRegistry
) : SdkLoader.ControllerFactory {
override fun createControllerFor(
sdkConfig: LocalSdkConfig
): SdkSandboxControllerCompat.SandboxControllerImpl {
- return LocalController(sdkConfig.packageName, locallyLoadedSdks, appOwnedSdkRegistry)
+ return LocalController(sdkConfig.packageName, localSdkRegistry, appOwnedSdkRegistry)
}
}
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocallyLoadedSdks.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocallyLoadedSdks.kt
deleted file mode 100644
index 73c3248..0000000
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/LocallyLoadedSdks.kt
+++ /dev/null
@@ -1,56 +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.privacysandbox.sdkruntime.client.controller
-
-import androidx.privacysandbox.sdkruntime.client.loader.LocalSdkProvider
-import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
-import org.jetbrains.annotations.TestOnly
-
-/**
- * Represents list of locally loaded SDKs.
- * Shared between:
- * 1) [androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat]
- * 2) [androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat]
- */
-internal class LocallyLoadedSdks {
-
- private val sdks = HashMap<String, Entry>()
-
- fun isLoaded(sdkName: String): Boolean {
- return sdks.containsKey(sdkName)
- }
-
- fun put(sdkName: String, entry: Entry) {
- sdks[sdkName] = entry
- }
-
- @TestOnly
- fun get(sdkName: String): Entry? = sdks[sdkName]
-
- fun remove(sdkName: String): Entry? {
- return sdks.remove(sdkName)
- }
-
- fun getLoadedSdks(): List<SandboxedSdkCompat> {
- return sdks.values.map { it.sdk }
- }
-
- data class Entry(
- val sdkProvider: LocalSdkProvider,
- val sdk: SandboxedSdkCompat
- )
-}
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/SdkRegistry.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/SdkRegistry.kt
new file mode 100644
index 0000000..eeed123
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/SdkRegistry.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.sdkruntime.client.controller
+
+import android.os.Bundle
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
+
+/**
+ * Responsible for lifecycle of particular SDKs (local, sandbox, test, etc).
+ */
+internal interface SdkRegistry {
+
+ /**
+ * Checks if SDK could be loaded / unloaded by this SdkRegistry.
+ *
+ * @param sdkName name of the SDK to be loaded / unloaded.
+ * @return true if SDK could be loaded / unloaded by this SdkRegistry or false otherwise
+ */
+ fun isResponsibleFor(sdkName: String): Boolean
+
+ /**
+ * Loads SDK.
+ *
+ * @param sdkName name of the SDK to be loaded.
+ * @param params additional parameters to be passed to the SDK in the form of a [Bundle]
+ * as agreed between the client and the SDK.
+ * @return [SandboxedSdkCompat] from SDK on a successful run.
+ * @throws [LoadSdkCompatException] on fail or when SdkRegistry not responsible for this SDK.
+ */
+ fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat
+
+ /**
+ * Unloads an SDK that has been previously loaded by SdkRegistry.
+ *
+ * @param sdkName name of the SDK to be unloaded.
+ */
+ fun unloadSdk(sdkName: String)
+
+ /**
+ * Fetches information about Sdks that are loaded by this SdkRegistry.
+ *
+ * @return List of [SandboxedSdkCompat] containing all currently loaded sdks
+ */
+ fun getLoadedSdks(): List<SandboxedSdkCompat>
+}
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/LocalSdkRegistry.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/LocalSdkRegistry.kt
new file mode 100644
index 0000000..58ee51d
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/LocalSdkRegistry.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.sdkruntime.client.controller.impl
+
+import android.content.Context
+import android.os.Bundle
+import android.util.Log
+import androidx.privacysandbox.sdkruntime.client.activity.LocalSdkActivityHandlerRegistry
+import androidx.privacysandbox.sdkruntime.client.config.LocalSdkConfigsHolder
+import androidx.privacysandbox.sdkruntime.client.controller.AppOwnedSdkRegistry
+import androidx.privacysandbox.sdkruntime.client.controller.LocalControllerFactory
+import androidx.privacysandbox.sdkruntime.client.controller.SdkRegistry
+import androidx.privacysandbox.sdkruntime.client.loader.LocalSdkProvider
+import androidx.privacysandbox.sdkruntime.client.loader.SdkLoader
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
+import org.jetbrains.annotations.TestOnly
+
+/**
+ * Responsible for lifecycle of SDKs bundled with app.
+ * Shared between:
+ * 1) [androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat]
+ * 2) [androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat]
+ */
+internal class LocalSdkRegistry(
+ private val configHolder: LocalSdkConfigsHolder,
+) : SdkRegistry {
+ private lateinit var sdkLoader: SdkLoader
+
+ private val sdks = HashMap<String, Entry>()
+
+ override fun isResponsibleFor(sdkName: String): Boolean {
+ return configHolder.getSdkConfig(sdkName) != null
+ }
+
+ override fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
+ val sdkConfig = configHolder.getSdkConfig(sdkName)
+ if (sdkConfig == null) {
+ throw LoadSdkCompatException(
+ LoadSdkCompatException.LOAD_SDK_NOT_FOUND,
+ "$sdkName not bundled with app"
+ )
+ }
+
+ synchronized(sdks) {
+ if (sdks.containsKey(sdkName)) {
+ throw LoadSdkCompatException(
+ LoadSdkCompatException.LOAD_SDK_ALREADY_LOADED,
+ "$sdkName already loaded"
+ )
+ }
+
+ val sdkProvider = sdkLoader.loadSdk(sdkConfig)
+ val sandboxedSdkCompat = sdkProvider.onLoadSdk(params)
+ sdks.put(
+ sdkName, Entry(
+ sdkProvider = sdkProvider,
+ sdk = sandboxedSdkCompat
+ )
+ )
+ return sandboxedSdkCompat
+ }
+ }
+
+ override fun unloadSdk(sdkName: String) {
+ val loadedEntry = synchronized(sdks) {
+ sdks.remove(sdkName)
+ }
+ if (loadedEntry == null) {
+ Log.w(LOG_TAG, "Unloading SDK that is not loaded - $sdkName")
+ return
+ }
+
+ loadedEntry.sdkProvider.beforeUnloadSdk()
+ LocalSdkActivityHandlerRegistry.unregisterAllActivityHandlersForSdk(sdkName)
+ }
+
+ override fun getLoadedSdks(): List<SandboxedSdkCompat> = synchronized(sdks) {
+ return sdks.values.map { it.sdk }
+ }
+
+ @TestOnly
+ fun getLoadedSdkProvider(sdkName: String): LocalSdkProvider? = synchronized(sdks) {
+ return sdks[sdkName]?.sdkProvider
+ }
+
+ private data class Entry(
+ val sdkProvider: LocalSdkProvider,
+ val sdk: SandboxedSdkCompat
+ )
+
+ companion object {
+ const val LOG_TAG = "LocalSdkRegistry"
+
+ /**
+ * Create and initialize all required components for loading SDKs bundled with app.
+ *
+ * @param context App context
+ * @param appOwnedSdkRegistry AppOwnedSdkRegistry for [LocalControllerFactory]
+ * @return LocalSdkRegistry that could load SDKs bundled with app.
+ */
+ fun create(
+ context: Context,
+ appOwnedSdkRegistry: AppOwnedSdkRegistry
+ ): LocalSdkRegistry {
+ val configHolder = LocalSdkConfigsHolder.load(context)
+
+ val localSdkRegistry = LocalSdkRegistry(configHolder)
+ localSdkRegistry.sdkLoader = SdkLoader.create(
+ context,
+ LocalControllerFactory(
+ localSdkRegistry,
+ appOwnedSdkRegistry
+ )
+ )
+
+ return localSdkRegistry
+ }
+ }
+}
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt
index c3a7b6d..be6a112 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt
@@ -60,26 +60,78 @@
}
@Test
- fun loadSdk_withoutLocalImpl_throwsLoadSdkCompatException() {
+ fun loadSdk_withoutLocalImpl_throwsLoadSdkNotFoundException() {
val controllerCompat = SdkSandboxControllerCompat.from(context)
- Assert.assertThrows(LoadSdkCompatException::class.java) {
+ val exception = Assert.assertThrows(LoadSdkCompatException::class.java) {
runBlocking {
controllerCompat.loadSdk("SDK", Bundle())
}
}
+
+ assertThat(exception.loadSdkErrorCode).isEqualTo(LoadSdkCompatException.LOAD_SDK_NOT_FOUND)
}
@Test
- fun loadSdk_withLocalImpl_throwsLoadSdkCompatException() {
+ fun loadSdk_clientApiBelow5_throwsLoadSdkNotFoundException() {
+ // Emulate loading via client lib with version below 5
+ Versions.handShake(4)
+
SdkSandboxControllerCompat.injectLocalImpl(TestStubImpl())
val controllerCompat = SdkSandboxControllerCompat.from(context)
- Assert.assertThrows(LoadSdkCompatException::class.java) {
+ val exception = Assert.assertThrows(LoadSdkCompatException::class.java) {
runBlocking {
controllerCompat.loadSdk("SDK", Bundle())
}
}
+
+ assertThat(exception.loadSdkErrorCode).isEqualTo(LoadSdkCompatException.LOAD_SDK_NOT_FOUND)
+ }
+
+ @Test
+ fun loadSdk_withLocalImpl_returnsLoadedSdkFromLocalImpl() {
+ // Emulate loading via client lib with version 5
+ Versions.handShake(5)
+
+ val expectedResult = SandboxedSdkCompat(Binder())
+ val stubLocalImpl = TestStubImpl(
+ loadSdkResult = expectedResult
+ )
+ SdkSandboxControllerCompat.injectLocalImpl(stubLocalImpl)
+ val controllerCompat = SdkSandboxControllerCompat.from(context)
+
+ val sdkName = "SDK"
+ val sdkParams = Bundle()
+ val result = runBlocking {
+ controllerCompat.loadSdk(sdkName, sdkParams)
+ }
+
+ assertThat(result).isSameInstanceAs(expectedResult)
+ assertThat(stubLocalImpl.lastLoadSdkName).isEqualTo(sdkName)
+ assertThat(stubLocalImpl.lastLoadSdkParams).isEqualTo(sdkParams)
+ }
+
+ @Test
+ fun loadSdk_withLocalImpl_rethrowsExceptionFromLocalImpl() {
+ // Emulate loading via client lib with version 5
+ Versions.handShake(5)
+
+ val expectedError = LoadSdkCompatException(RuntimeException(), Bundle())
+ SdkSandboxControllerCompat.injectLocalImpl(
+ TestStubImpl(
+ loadSdkError = expectedError
+ )
+ )
+ val controllerCompat = SdkSandboxControllerCompat.from(context)
+
+ val exception = Assert.assertThrows(LoadSdkCompatException::class.java) {
+ runBlocking {
+ controllerCompat.loadSdk("SDK", Bundle())
+ }
+ }
+
+ assertThat(exception).isSameInstanceAs(expectedError)
}
@Test
@@ -217,23 +269,38 @@
internal class TestStubImpl(
private val sandboxedSdks: List<SandboxedSdkCompat> = emptyList(),
- private val appOwnedSdks: List<AppOwnedSdkSandboxInterfaceCompat> = emptyList()
+ private val appOwnedSdks: List<AppOwnedSdkSandboxInterfaceCompat> = emptyList(),
+ private val loadSdkResult: SandboxedSdkCompat? = null,
+ private val loadSdkError: LoadSdkCompatException? = null,
) : SdkSandboxControllerCompat.SandboxControllerImpl {
var token: IBinder? = null
+ var lastLoadSdkName: String? = null
+ var lastLoadSdkParams: Bundle? = null
+
override fun loadSdk(
sdkName: String,
params: Bundle,
executor: Executor,
callback: SdkSandboxControllerCompat.LoadSdkCallback
) {
- executor.execute {
- callback.onError(
- LoadSdkCompatException(
- LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
- "Shouldn't be called"
+ lastLoadSdkName = sdkName
+ lastLoadSdkParams = params
+
+ if (loadSdkResult != null) {
+ executor.execute {
+ callback.onResult(loadSdkResult)
+ }
+ } else {
+ executor.execute {
+ callback.onError(
+ loadSdkError
+ ?: LoadSdkCompatException(
+ LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
+ "Shouldn't be called without setting result or error"
+ )
)
- )
+ }
}
}
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt
index 0b9bcf4..543f61f 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt
@@ -41,13 +41,17 @@
executor: Executor,
callback: SdkSandboxControllerCompat.LoadSdkCallback
) {
- executor.execute {
- callback.onError(
- LoadSdkCompatException(
- LOAD_SDK_NOT_FOUND,
- "Not supported for locally loaded SDKs yet"
+ if (clientVersion >= 5) {
+ implFromClient.loadSdk(sdkName, params, executor, callback)
+ } else {
+ executor.execute {
+ callback.onError(
+ LoadSdkCompatException(
+ LOAD_SDK_NOT_FOUND,
+ "Client library version doesn't support locally loaded SDKs"
+ )
)
- )
+ }
}
}
diff --git a/privacysandbox/sdkruntime/test-sdks/v5/build.gradle b/privacysandbox/sdkruntime/test-sdks/v5/build.gradle
new file mode 100644
index 0000000..a3ff5fe
--- /dev/null
+++ b/privacysandbox/sdkruntime/test-sdks/v5/build.gradle
@@ -0,0 +1,63 @@
+/*
+ * 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 com.android.build.api.artifact.SingleArtifact
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace "androidx.privacysandbox.sdkruntime.testsdk.v5"
+}
+
+dependencies {
+ implementation(project(":privacysandbox:sdkruntime:sdkruntime-provider"))
+}
+
+/*
+ * Allow integration tests to consume the APK produced by this project
+ */
+configurations {
+ testSdkApk {
+ canBeConsumed = true
+ canBeResolved = false
+ attributes {
+ attribute(
+ LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
+ objects.named(LibraryElements, "testSdkApk")
+ )
+ }
+ }
+}
+
+androidComponents {
+ beforeVariants(selector().all()) { enabled = buildType == 'release' }
+ onVariants(selector().all().withBuildType("release"), { variant ->
+ artifacts {
+ testSdkApk(variant.artifacts.get(SingleArtifact.APK.INSTANCE))
+ }
+ })
+}
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/test-sdks/v5/src/main/AndroidManifest.xml b/privacysandbox/sdkruntime/test-sdks/v5/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5c1d335
--- /dev/null
+++ b/privacysandbox/sdkruntime/test-sdks/v5/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?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">
+</manifest>
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v5/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v5/CompatProvider.kt
new file mode 100644
index 0000000..2fa0278
--- /dev/null
+++ b/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v5/CompatProvider.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.sdkruntime.testsdk.v5
+
+import android.content.Context
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.view.View
+import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
+import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
+import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+
+@Suppress("unused") // Reflection usage from tests in privacysandbox:sdkruntime:sdkruntime-client
+class CompatProvider : SandboxedSdkProviderCompat() {
+ @JvmField
+ var onLoadSdkBinder: Binder? = null
+
+ @JvmField
+ var lastOnLoadSdkParams: Bundle? = null
+
+ @JvmField
+ var isBeforeUnloadSdkCalled = false
+
+ @Throws(LoadSdkCompatException::class)
+ override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
+ val result = SdkImpl(context!!)
+ onLoadSdkBinder = result
+
+ lastOnLoadSdkParams = params
+ if (params.getBoolean("needFail", false)) {
+ throw LoadSdkCompatException(RuntimeException(), params)
+ }
+ return SandboxedSdkCompat(result)
+ }
+
+ override fun beforeUnloadSdk() {
+ isBeforeUnloadSdkCalled = true
+ }
+
+ override fun getView(
+ windowContext: Context,
+ params: Bundle,
+ width: Int,
+ height: Int
+ ): View {
+ return View(windowContext)
+ }
+
+ internal class SdkImpl(
+ private val context: Context
+ ) : Binder() {
+ fun getSandboxedSdks(): List<SandboxedSdkCompat> =
+ SdkSandboxControllerCompat.from(context).getSandboxedSdks()
+
+ fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> =
+ SdkSandboxControllerCompat.from(context).getAppOwnedSdkSandboxInterfaces()
+
+ fun registerSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat): IBinder =
+ SdkSandboxControllerCompat.from(context).registerSdkSandboxActivityHandler(handler)
+
+ fun unregisterSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat) {
+ SdkSandboxControllerCompat.from(context).unregisterSdkSandboxActivityHandler(handler)
+ }
+ }
+}
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
index 2f90309..057b12d 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
@@ -22,6 +22,8 @@
import androidx.privacysandbox.tools.core.generator.ClientProxyTypeGenerator
import androidx.privacysandbox.tools.core.generator.CoreLibInfoAndBinderWrapperConverterGenerator
import androidx.privacysandbox.tools.core.generator.GenerationTarget
+import androidx.privacysandbox.tools.core.generator.PrivacySandboxCancellationExceptionFileGenerator
+import androidx.privacysandbox.tools.core.generator.PrivacySandboxExceptionFileGenerator
import androidx.privacysandbox.tools.core.generator.SdkActivityLauncherWrapperGenerator
import androidx.privacysandbox.tools.core.generator.ServerBinderCodeConverter
import androidx.privacysandbox.tools.core.generator.ServiceFactoryFileGenerator
@@ -154,8 +156,10 @@
private fun generateSuspendFunctionUtilities() {
if (!api.hasSuspendFunctions()) return
TransportCancellationGenerator(basePackageName()).generate().also(::write)
- ThrowableParcelConverterFileGenerator(basePackageName(), target).generate()
+ ThrowableParcelConverterFileGenerator(basePackageName()).generate()
.also(::write)
+ PrivacySandboxExceptionFileGenerator(basePackageName()).generate().also(::write)
+ PrivacySandboxCancellationExceptionFileGenerator(basePackageName()).generate().also(::write)
}
private fun generateSdkActivityLauncherUtilities() {
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt
index 7e422f7..af14632 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/input/com/mysdk/MySdk.kt
@@ -112,4 +112,6 @@
fun onCompleteInterface(myInterface: MyInterface)
fun onCompleteUiInterface(myUiInterface: MyUiInterface)
+
+ suspend fun returnAValueFromCallback(): Response
}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyCallbackClientProxy.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyCallbackClientProxy.kt
index af49258..f5eebd8 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyCallbackClientProxy.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MyCallbackClientProxy.kt
@@ -2,6 +2,9 @@
import android.content.Context
import androidx.privacysandbox.ui.provider.toCoreLibInfo
+import com.mysdk.PrivacySandboxThrowableParcelConverter.fromThrowableParcel
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.suspendCancellableCoroutine
public class MyCallbackClientProxy(
public val remote: IMyCallback,
@@ -23,4 +26,26 @@
remote.onCompleteUiInterface(IMyUiInterfaceCoreLibInfoAndBinderWrapperConverter.toParcelable(myUiInterface.toCoreLibInfo(context),
MyUiInterfaceStubDelegate(myUiInterface, context)))
}
+
+ public override suspend fun returnAValueFromCallback(): Response = suspendCancellableCoroutine {
+ var mCancellationSignal: ICancellationSignal? = null
+ val transactionCallback = object: IResponseTransactionCallback.Stub() {
+ override fun onCancellable(cancellationSignal: ICancellationSignal) {
+ if (it.isCancelled) {
+ cancellationSignal.cancel()
+ }
+ mCancellationSignal = cancellationSignal
+ }
+ override fun onSuccess(result: ParcelableResponse) {
+ it.resumeWith(Result.success(ResponseConverter(context).fromParcelable(result)))
+ }
+ override fun onFailure(throwableParcel: PrivacySandboxThrowableParcel) {
+ it.resumeWithException(fromThrowableParcel(throwableParcel))
+ }
+ }
+ remote.returnAValueFromCallback(transactionCallback)
+ it.invokeOnCancellation {
+ mCancellationSignal?.cancel()
+ }
+ }
}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/PrivacySandboxCancellationException.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/PrivacySandboxCancellationException.kt
new file mode 100644
index 0000000..84cd486
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/PrivacySandboxCancellationException.kt
@@ -0,0 +1,8 @@
+package com.mysdk
+
+import java.util.concurrent.CancellationException
+
+public class PrivacySandboxCancellationException(
+ public override val message: String?,
+ public override val cause: Throwable?,
+) : CancellationException()
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/PrivacySandboxException.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/PrivacySandboxException.kt
new file mode 100644
index 0000000..d165932
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/PrivacySandboxException.kt
@@ -0,0 +1,8 @@
+package com.mysdk
+
+import java.lang.RuntimeException
+
+public class PrivacySandboxException(
+ public override val message: String?,
+ public override val cause: Throwable?,
+) : RuntimeException()
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
index 87c0cf2..0a46ac9 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
@@ -1,8 +1,36 @@
package com.mysdk
+import java.lang.StackTraceElement
import kotlin.coroutines.cancellation.CancellationException
public object PrivacySandboxThrowableParcelConverter {
+ public fun fromThrowableParcel(throwableParcel: PrivacySandboxThrowableParcel): Throwable {
+ val exceptionClass = throwableParcel.exceptionClass
+ val stackTrace = throwableParcel.stackTrace
+ val errorMessage = "[$exceptionClass] ${throwableParcel.errorMessage}"
+ val cause = throwableParcel.cause?.firstOrNull()?.let {
+ fromThrowableParcel(it)
+ }
+ val exception = if (throwableParcel.isCancellationException) {
+ PrivacySandboxCancellationException(errorMessage, cause)
+ } else {
+ PrivacySandboxException(errorMessage, cause)
+ }
+ for (suppressed in throwableParcel.suppressedExceptions) {
+ exception.addSuppressed(fromThrowableParcel(suppressed))
+ }
+ exception.stackTrace =
+ stackTrace.map {
+ StackTraceElement(
+ it.declaringClass,
+ it.methodName,
+ it.fileName,
+ it.lineNumber
+ )
+ }.toTypedArray()
+ return exception
+ }
+
public fun toThrowableParcel(throwable: Throwable): PrivacySandboxThrowableParcel {
val parcel = PrivacySandboxThrowableParcel()
parcel.exceptionClass = throwable::class.qualifiedName
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/PrivacySandboxCancellationException.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/PrivacySandboxCancellationException.kt
new file mode 100644
index 0000000..84cd486
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/PrivacySandboxCancellationException.kt
@@ -0,0 +1,8 @@
+package com.mysdk
+
+import java.util.concurrent.CancellationException
+
+public class PrivacySandboxCancellationException(
+ public override val message: String?,
+ public override val cause: Throwable?,
+) : CancellationException()
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/PrivacySandboxException.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/PrivacySandboxException.kt
new file mode 100644
index 0000000..d165932
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/PrivacySandboxException.kt
@@ -0,0 +1,8 @@
+package com.mysdk
+
+import java.lang.RuntimeException
+
+public class PrivacySandboxException(
+ public override val message: String?,
+ public override val cause: Throwable?,
+) : RuntimeException()
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
index 87c0cf2..0a46ac9 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
@@ -1,8 +1,36 @@
package com.mysdk
+import java.lang.StackTraceElement
import kotlin.coroutines.cancellation.CancellationException
public object PrivacySandboxThrowableParcelConverter {
+ public fun fromThrowableParcel(throwableParcel: PrivacySandboxThrowableParcel): Throwable {
+ val exceptionClass = throwableParcel.exceptionClass
+ val stackTrace = throwableParcel.stackTrace
+ val errorMessage = "[$exceptionClass] ${throwableParcel.errorMessage}"
+ val cause = throwableParcel.cause?.firstOrNull()?.let {
+ fromThrowableParcel(it)
+ }
+ val exception = if (throwableParcel.isCancellationException) {
+ PrivacySandboxCancellationException(errorMessage, cause)
+ } else {
+ PrivacySandboxException(errorMessage, cause)
+ }
+ for (suppressed in throwableParcel.suppressedExceptions) {
+ exception.addSuppressed(fromThrowableParcel(suppressed))
+ }
+ exception.stackTrace =
+ stackTrace.map {
+ StackTraceElement(
+ it.declaringClass,
+ it.methodName,
+ it.fileName,
+ it.lineNumber
+ )
+ }.toTypedArray()
+ return exception
+ }
+
public fun toThrowableParcel(throwable: Throwable): PrivacySandboxThrowableParcel {
val parcel = PrivacySandboxThrowableParcel()
parcel.exceptionClass = throwable::class.qualifiedName
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxCancellationException.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxCancellationException.kt
new file mode 100644
index 0000000..84cd486
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxCancellationException.kt
@@ -0,0 +1,8 @@
+package com.mysdk
+
+import java.util.concurrent.CancellationException
+
+public class PrivacySandboxCancellationException(
+ public override val message: String?,
+ public override val cause: Throwable?,
+) : CancellationException()
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxException.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxException.kt
new file mode 100644
index 0000000..d165932
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxException.kt
@@ -0,0 +1,8 @@
+package com.mysdk
+
+import java.lang.RuntimeException
+
+public class PrivacySandboxException(
+ public override val message: String?,
+ public override val cause: Throwable?,
+) : RuntimeException()
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
index 87c0cf2..0a46ac9 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
@@ -1,8 +1,36 @@
package com.mysdk
+import java.lang.StackTraceElement
import kotlin.coroutines.cancellation.CancellationException
public object PrivacySandboxThrowableParcelConverter {
+ public fun fromThrowableParcel(throwableParcel: PrivacySandboxThrowableParcel): Throwable {
+ val exceptionClass = throwableParcel.exceptionClass
+ val stackTrace = throwableParcel.stackTrace
+ val errorMessage = "[$exceptionClass] ${throwableParcel.errorMessage}"
+ val cause = throwableParcel.cause?.firstOrNull()?.let {
+ fromThrowableParcel(it)
+ }
+ val exception = if (throwableParcel.isCancellationException) {
+ PrivacySandboxCancellationException(errorMessage, cause)
+ } else {
+ PrivacySandboxException(errorMessage, cause)
+ }
+ for (suppressed in throwableParcel.suppressedExceptions) {
+ exception.addSuppressed(fromThrowableParcel(suppressed))
+ }
+ exception.stackTrace =
+ stackTrace.map {
+ StackTraceElement(
+ it.declaringClass,
+ it.methodName,
+ it.fileName,
+ it.lineNumber
+ )
+ }.toTypedArray()
+ return exception
+ }
+
public fun toThrowableParcel(throwable: Throwable): PrivacySandboxThrowableParcel {
val parcel = PrivacySandboxThrowableParcel()
parcel.exceptionClass = throwable::class.qualifiedName
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
index 096aed8..342ef86 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
@@ -31,6 +31,7 @@
import androidx.privacysandbox.tools.core.generator.ServiceFactoryFileGenerator
import androidx.privacysandbox.tools.core.generator.StubDelegatesGenerator
import androidx.privacysandbox.tools.core.generator.ThrowableParcelConverterFileGenerator
+import androidx.privacysandbox.tools.core.generator.TransportCancellationGenerator
import androidx.privacysandbox.tools.core.generator.ValueConverterFileGenerator
import androidx.privacysandbox.tools.core.generator.ValueFileGenerator
import androidx.privacysandbox.tools.core.model.ParsedApi
@@ -249,8 +250,10 @@
output: File
) {
if (!api.hasSuspendFunctions()) return
- ThrowableParcelConverterFileGenerator(basePackageName, GenerationTarget.CLIENT)
+ ThrowableParcelConverterFileGenerator(basePackageName)
.generate().writeTo(output)
+ TransportCancellationGenerator(api.getOnlyService().type.packageName).generate()
+ .writeTo(output)
PrivacySandboxExceptionFileGenerator(basePackageName).generate().writeTo(output)
PrivacySandboxCancellationExceptionFileGenerator(basePackageName).generate().writeTo(output)
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorDiffTest.kt b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorDiffTest.kt
index 2398d20..61639fe 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorDiffTest.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorDiffTest.kt
@@ -29,6 +29,10 @@
"com/sdkwithcallbacks/ParcelableResponse.java",
"com/sdkwithcallbacks/ParcelableMyEnum.java",
"com/sdkwithcallbacks/IMyUiInterface.java",
- "com/sdkwithcallbacks/IMyUiInterfaceCoreLibInfoAndBinderWrapper.java"
+ "com/sdkwithcallbacks/IMyUiInterfaceCoreLibInfoAndBinderWrapper.java",
+ "com/sdkwithcallbacks/PrivacySandboxThrowableParcel.java",
+ "com/sdkwithcallbacks/IResponseTransactionCallback.java",
+ "com/sdkwithcallbacks/ICancellationSignal.java",
+ "com/sdkwithcallbacks/ParcelableStackFrame.java",
)
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt
index 5c2718b..eb553ce 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt
@@ -23,6 +23,8 @@
fun onCompleteInterface(myInterface: MyInterface)
fun onSdkActivityLauncherReceived(myLauncher: SdkActivityLauncher)
+
+ suspend fun testing(): Response
}
@PrivacySandboxValue
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/PrivacySandboxCancellationException.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/PrivacySandboxCancellationException.kt
new file mode 100644
index 0000000..782b0e4
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/PrivacySandboxCancellationException.kt
@@ -0,0 +1,8 @@
+package com.sdkwithcallbacks
+
+import java.util.concurrent.CancellationException
+
+public class PrivacySandboxCancellationException(
+ public override val message: String?,
+ public override val cause: Throwable?,
+) : CancellationException()
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/PrivacySandboxException.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/PrivacySandboxException.kt
new file mode 100644
index 0000000..70a4c2c
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/PrivacySandboxException.kt
@@ -0,0 +1,8 @@
+package com.sdkwithcallbacks
+
+import java.lang.RuntimeException
+
+public class PrivacySandboxException(
+ public override val message: String?,
+ public override val cause: Throwable?,
+) : RuntimeException()
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/PrivacySandboxThrowableParcelConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/PrivacySandboxThrowableParcelConverter.kt
new file mode 100644
index 0000000..ceebba2
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/PrivacySandboxThrowableParcelConverter.kt
@@ -0,0 +1,56 @@
+package com.sdkwithcallbacks
+
+import java.lang.StackTraceElement
+import kotlin.coroutines.cancellation.CancellationException
+
+public object PrivacySandboxThrowableParcelConverter {
+ public fun fromThrowableParcel(throwableParcel: PrivacySandboxThrowableParcel): Throwable {
+ val exceptionClass = throwableParcel.exceptionClass
+ val stackTrace = throwableParcel.stackTrace
+ val errorMessage = "[$exceptionClass] ${throwableParcel.errorMessage}"
+ val cause = throwableParcel.cause?.firstOrNull()?.let {
+ fromThrowableParcel(it)
+ }
+ val exception = if (throwableParcel.isCancellationException) {
+ PrivacySandboxCancellationException(errorMessage, cause)
+ } else {
+ PrivacySandboxException(errorMessage, cause)
+ }
+ for (suppressed in throwableParcel.suppressedExceptions) {
+ exception.addSuppressed(fromThrowableParcel(suppressed))
+ }
+ exception.stackTrace =
+ stackTrace.map {
+ StackTraceElement(
+ it.declaringClass,
+ it.methodName,
+ it.fileName,
+ it.lineNumber
+ )
+ }.toTypedArray()
+ return exception
+ }
+
+ public fun toThrowableParcel(throwable: Throwable): PrivacySandboxThrowableParcel {
+ val parcel = PrivacySandboxThrowableParcel()
+ parcel.exceptionClass = throwable::class.qualifiedName
+ parcel.errorMessage = throwable.message
+ parcel.stackTrace = throwable.stackTrace.map {
+ val stackFrame = ParcelableStackFrame()
+ stackFrame.declaringClass = it.className
+ stackFrame.methodName = it.methodName
+ stackFrame.fileName = it.fileName
+ stackFrame.lineNumber = it.lineNumber
+ stackFrame
+ }.toTypedArray()
+ throwable.cause?.let {
+ parcel.cause = arrayOf(toThrowableParcel(it))
+ }
+ parcel.suppressedExceptions =
+ throwable.suppressedExceptions.map {
+ toThrowableParcel(it)
+ }.toTypedArray()
+ parcel.isCancellationException = throwable is CancellationException
+ return parcel
+ }
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt
index 4530541..a080c95 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt
@@ -12,4 +12,6 @@
public fun onSdkActivityLauncherReceived(myLauncher: SdkActivityLauncher)
public fun onValueReceived(response: Response)
+
+ public suspend fun testing(): Response
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt
index f98dafb..41e46d9 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt
@@ -1,7 +1,9 @@
package com.sdkwithcallbacks
import android.os.Bundle
+import com.sdkwithcallbacks.PrivacySandboxThrowableParcelConverter.toThrowableParcel
import com.sdkwithcallbacks.ResponseConverter.fromParcelable
+import com.sdkwithcallbacks.ResponseConverter.toParcelable
import com.sdkwithcallbacks.SdkActivityLauncherConverter.getLocalOrProxyLauncher
import kotlin.Int
import kotlinx.coroutines.CoroutineScope
@@ -42,4 +44,18 @@
delegate.onValueReceived(fromParcelable(response))
}
}
+
+ public override fun testing(transactionCallback: IResponseTransactionCallback) {
+ val job = coroutineScope.launch {
+ try {
+ val result = delegate.testing()
+ transactionCallback.onSuccess(toParcelable(result))
+ }
+ catch (t: Throwable) {
+ transactionCallback.onFailure(toThrowableParcel(t))
+ }
+ }
+ val cancellationSignal = TransportCancellationCallback() { job.cancel() }
+ transactionCallback.onCancellable(cancellationSignal)
+ }
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/TransportCancellationCallback.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/TransportCancellationCallback.kt
new file mode 100644
index 0000000..7624bef
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/TransportCancellationCallback.kt
@@ -0,0 +1,16 @@
+package com.sdkwithcallbacks
+
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.Unit
+
+internal class TransportCancellationCallback internal constructor(
+ private val onCancel: () -> Unit,
+) : ICancellationSignal.Stub() {
+ private val hasCancelled: AtomicBoolean = AtomicBoolean(false)
+
+ public override fun cancel() {
+ if (hasCancelled.compareAndSet(false, true)) {
+ onCancel()
+ }
+ }
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/PrivacySandboxThrowableParcelConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/PrivacySandboxThrowableParcelConverter.kt
index a7c0dd1..2147b18 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/PrivacySandboxThrowableParcelConverter.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/PrivacySandboxThrowableParcelConverter.kt
@@ -1,6 +1,7 @@
package com.sdk
import java.lang.StackTraceElement
+import kotlin.coroutines.cancellation.CancellationException
public object PrivacySandboxThrowableParcelConverter {
public fun fromThrowableParcel(throwableParcel: PrivacySandboxThrowableParcel): Throwable {
@@ -29,4 +30,27 @@
}.toTypedArray()
return exception
}
+
+ public fun toThrowableParcel(throwable: Throwable): PrivacySandboxThrowableParcel {
+ val parcel = PrivacySandboxThrowableParcel()
+ parcel.exceptionClass = throwable::class.qualifiedName
+ parcel.errorMessage = throwable.message
+ parcel.stackTrace = throwable.stackTrace.map {
+ val stackFrame = ParcelableStackFrame()
+ stackFrame.declaringClass = it.className
+ stackFrame.methodName = it.methodName
+ stackFrame.fileName = it.fileName
+ stackFrame.lineNumber = it.lineNumber
+ stackFrame
+ }.toTypedArray()
+ throwable.cause?.let {
+ parcel.cause = arrayOf(toThrowableParcel(it))
+ }
+ parcel.suppressedExceptions =
+ throwable.suppressedExceptions.map {
+ toThrowableParcel(it)
+ }.toTypedArray()
+ parcel.isCancellationException = throwable is CancellationException
+ return parcel
+ }
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/TransportCancellationCallback.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/TransportCancellationCallback.kt
new file mode 100644
index 0000000..5b544f3
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/TransportCancellationCallback.kt
@@ -0,0 +1,16 @@
+package com.sdk
+
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.Unit
+
+internal class TransportCancellationCallback internal constructor(
+ private val onCancel: () -> Unit,
+) : ICancellationSignal.Stub() {
+ private val hasCancelled: AtomicBoolean = AtomicBoolean(false)
+
+ public override fun cancel() {
+ if (hasCancelled.compareAndSet(false, true)) {
+ onCancel()
+ }
+ }
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
index dddab12..0a46ac9 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
@@ -1,6 +1,7 @@
package com.mysdk
import java.lang.StackTraceElement
+import kotlin.coroutines.cancellation.CancellationException
public object PrivacySandboxThrowableParcelConverter {
public fun fromThrowableParcel(throwableParcel: PrivacySandboxThrowableParcel): Throwable {
@@ -29,4 +30,27 @@
}.toTypedArray()
return exception
}
+
+ public fun toThrowableParcel(throwable: Throwable): PrivacySandboxThrowableParcel {
+ val parcel = PrivacySandboxThrowableParcel()
+ parcel.exceptionClass = throwable::class.qualifiedName
+ parcel.errorMessage = throwable.message
+ parcel.stackTrace = throwable.stackTrace.map {
+ val stackFrame = ParcelableStackFrame()
+ stackFrame.declaringClass = it.className
+ stackFrame.methodName = it.methodName
+ stackFrame.fileName = it.fileName
+ stackFrame.lineNumber = it.lineNumber
+ stackFrame
+ }.toTypedArray()
+ throwable.cause?.let {
+ parcel.cause = arrayOf(toThrowableParcel(it))
+ }
+ parcel.suppressedExceptions =
+ throwable.suppressedExceptions.map {
+ toThrowableParcel(it)
+ }.toTypedArray()
+ parcel.isCancellationException = throwable is CancellationException
+ return parcel
+ }
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TransportCancellationCallback.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TransportCancellationCallback.kt
new file mode 100644
index 0000000..ba1815c
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TransportCancellationCallback.kt
@@ -0,0 +1,16 @@
+package com.mysdk
+
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.Unit
+
+internal class TransportCancellationCallback internal constructor(
+ private val onCancel: () -> Unit,
+) : ICancellationSignal.Stub() {
+ private val hasCancelled: AtomicBoolean = AtomicBoolean(false)
+
+ public override fun cancel() {
+ if (hasCancelled.compareAndSet(false, true)) {
+ onCancel()
+ }
+ }
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/PrivacySandboxThrowableParcelConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/PrivacySandboxThrowableParcelConverter.kt
index 7efbf10..64d53a6 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/PrivacySandboxThrowableParcelConverter.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/PrivacySandboxThrowableParcelConverter.kt
@@ -1,6 +1,7 @@
package com.sdkwithvalues
import java.lang.StackTraceElement
+import kotlin.coroutines.cancellation.CancellationException
public object PrivacySandboxThrowableParcelConverter {
public fun fromThrowableParcel(throwableParcel: PrivacySandboxThrowableParcel): Throwable {
@@ -29,4 +30,27 @@
}.toTypedArray()
return exception
}
+
+ public fun toThrowableParcel(throwable: Throwable): PrivacySandboxThrowableParcel {
+ val parcel = PrivacySandboxThrowableParcel()
+ parcel.exceptionClass = throwable::class.qualifiedName
+ parcel.errorMessage = throwable.message
+ parcel.stackTrace = throwable.stackTrace.map {
+ val stackFrame = ParcelableStackFrame()
+ stackFrame.declaringClass = it.className
+ stackFrame.methodName = it.methodName
+ stackFrame.fileName = it.fileName
+ stackFrame.lineNumber = it.lineNumber
+ stackFrame
+ }.toTypedArray()
+ throwable.cause?.let {
+ parcel.cause = arrayOf(toThrowableParcel(it))
+ }
+ parcel.suppressedExceptions =
+ throwable.suppressedExceptions.map {
+ toThrowableParcel(it)
+ }.toTypedArray()
+ parcel.isCancellationException = throwable is CancellationException
+ return parcel
+ }
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/TransportCancellationCallback.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/TransportCancellationCallback.kt
new file mode 100644
index 0000000..bc3bf98
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/TransportCancellationCallback.kt
@@ -0,0 +1,16 @@
+package com.sdkwithvalues
+
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.Unit
+
+internal class TransportCancellationCallback internal constructor(
+ private val onCancel: () -> Unit,
+) : ICancellationSignal.Stub() {
+ private val hasCancelled: AtomicBoolean = AtomicBoolean(false)
+
+ public override fun cancel() {
+ if (hasCancelled.compareAndSet(false, true)) {
+ onCancel()
+ }
+ }
+}
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
index e516dcf..0ef1d46 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
@@ -155,7 +155,7 @@
}
private fun generateTransactionCallbacks(): List<AidlFileSpec> {
- val annotatedInterfaces = api.services + api.interfaces
+ val annotatedInterfaces = api.services + api.interfaces + api.callbacks
return annotatedInterfaces
.flatMap(AnnotatedInterface::methods)
.filter(Method::isSuspend)
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ThrowableParcelConverterFileGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ThrowableParcelConverterFileGenerator.kt
index ab32014..df55a6e 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ThrowableParcelConverterFileGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ThrowableParcelConverterFileGenerator.kt
@@ -28,7 +28,6 @@
class ThrowableParcelConverterFileGenerator(
private val basePackageName: String,
- private val target: GenerationTarget,
) {
companion object {
const val converterName = "${throwableParcelName}Converter"
@@ -58,10 +57,8 @@
private fun generateConverter() =
TypeSpec.objectBuilder(ClassName(basePackageName, converterName)).build {
- when (target) {
- GenerationTarget.CLIENT -> addFunction(generateFromThrowableParcel())
- GenerationTarget.SERVER -> addFunction(generateToThrowableParcel())
- }
+ addFunction(generateFromThrowableParcel())
+ addFunction(generateToThrowableParcel())
}
private fun generateToThrowableParcel() =
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/Models.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/Models.kt
index 8387d0f..28c2c35 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/Models.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/model/Models.kt
@@ -24,7 +24,7 @@
}
fun ParsedApi.hasSuspendFunctions(): Boolean {
- val annotatedInterfaces = services + interfaces
+ val annotatedInterfaces = services + interfaces + callbacks
return annotatedInterfaces
.flatMap(AnnotatedInterface::methods)
.any(Method::isSuspend)
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt
index a53f8c3..20e573c 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt
@@ -73,7 +73,7 @@
}
private fun validateNonSuspendFunctionsReturnUnit() {
- val annotatedInterfaces = api.services + api.interfaces
+ val annotatedInterfaces = api.services + api.interfaces + api.callbacks
for (annotatedInterface in annotatedInterfaces) {
for (method in annotatedInterface.methods) {
if (!method.isSuspend && method.returnType != Types.unit) {
@@ -87,7 +87,7 @@
}
private fun validateServiceAndInterfaceMethods() {
- val annotatedInterfaces = api.services + api.interfaces
+ val annotatedInterfaces = api.services + api.interfaces + api.callbacks
for (annotatedInterface in annotatedInterfaces) {
for (method in annotatedInterface.methods) {
if (method.parameters.any { !(isValidInterfaceParameterType(it.type)) }) {
@@ -141,12 +141,6 @@
"callback parameter types."
)
}
- if (method.returnType != Types.unit || method.isSuspend) {
- errors.add(
- "Error in ${callback.type.qualifiedName}.${method.name}: callback " +
- "methods should be non-suspending and have no return values."
- )
- }
}
}
}
diff --git a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/validator/ModelValidatorTest.kt b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/validator/ModelValidatorTest.kt
index d6d58a7..fea53d4 100644
--- a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/validator/ModelValidatorTest.kt
+++ b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/validator/ModelValidatorTest.kt
@@ -220,6 +220,32 @@
),
),
),
+ ),
+ interfaces = setOf(
+ AnnotatedInterface(
+ type = Type(packageName = "com.mysdk", simpleName = "MySdkInterface"),
+ methods = listOf(
+ Method(
+ name = "returnSomethingInInterface",
+ parameters = listOf(),
+ returnType = Types.string,
+ isSuspend = false,
+ )
+ )
+ )
+ ),
+ callbacks = setOf(
+ AnnotatedInterface(
+ type = Type(packageName = "com.mysdk", simpleName = "MySdkCallback"),
+ methods = listOf(
+ Method(
+ name = "returnSomethingInCallback",
+ parameters = listOf(),
+ returnType = Types.string,
+ isSuspend = false,
+ )
+ )
+ )
)
)
val validationResult = ModelValidator.validate(api)
@@ -228,7 +254,11 @@
"Error in com.mysdk.MySdk.returnSomethingNow: functions with return values " +
"should be suspending functions.",
"Error in com.mysdk.MySdk.returnSomethingElseNow: functions with return values " +
- "should be suspending functions."
+ "should be suspending functions.",
+ "Error in com.mysdk.MySdkInterface.returnSomethingInInterface: " +
+ "functions with return values should be suspending functions.",
+ "Error in com.mysdk.MySdkCallback.returnSomethingInCallback: " +
+ "functions with return values should be suspending functions."
)
}
@@ -330,42 +360,6 @@
}
@Test
- fun callbackWithNonFireAndForgetMethod_throws() {
- val api = ParsedApi(
- services = setOf(
- AnnotatedInterface(type = Type(packageName = "com.mysdk", simpleName = "MySdk")),
- ),
- callbacks = setOf(
- AnnotatedInterface(
- type = Type(packageName = "com.mysdk", simpleName = "MySdkCallback"),
- methods = listOf(
- Method(
- name = "suspendMethod",
- parameters = listOf(),
- returnType = Types.unit,
- isSuspend = true,
- ),
- Method(
- name = "methodWithReturnValue",
- parameters = listOf(),
- returnType = Types.int,
- isSuspend = false,
- ),
- )
- )
- )
- )
- val validationResult = ModelValidator.validate(api)
- assertThat(validationResult.isFailure).isTrue()
- assertThat(validationResult.errors).containsExactly(
- "Error in com.mysdk.MySdkCallback.suspendMethod: callback methods should be " +
- "non-suspending and have no return values.",
- "Error in com.mysdk.MySdkCallback.methodWithReturnValue: callback methods should be " +
- "non-suspending and have no return values.",
- )
- }
-
- @Test
fun callbackReceivingCallbacks_throws() {
val api = ParsedApi(
services = setOf(
diff --git a/privacysandbox/tools/tools/src/main/java/androidx/privacysandbox/tools/PrivacySandboxCallback.kt b/privacysandbox/tools/tools/src/main/java/androidx/privacysandbox/tools/PrivacySandboxCallback.kt
index 606914e..d4a3dd0 100644
--- a/privacysandbox/tools/tools/src/main/java/androidx/privacysandbox/tools/PrivacySandboxCallback.kt
+++ b/privacysandbox/tools/tools/src/main/java/androidx/privacysandbox/tools/PrivacySandboxCallback.kt
@@ -23,8 +23,7 @@
* should be public interfaces that only declare functions without implementation, and they may not
* extend any other interface. Callbacks run in the main thread by default.
*
- * The allowed types are the same as for [PrivacySandboxInterface], except that functions may not
- * return a value.
+ * The allowed types and return types are the same as for [PrivacySandboxInterface].
*
* Usage example:
* ```
@@ -33,6 +32,7 @@
* fun onComplete(response: Response)
* fun onClick(x: Int, y: Int)
* fun onCompleteInterface(myInterface: MyInterface)
+ * suspend fun getCallbackId(): String
* }
* ```
*/
diff --git a/privacysandbox/ui/integration-tests/mediateesdkprovider/build.gradle b/privacysandbox/ui/integration-tests/mediateesdkprovider/build.gradle
index 6879e7d..ba9815e 100644
--- a/privacysandbox/ui/integration-tests/mediateesdkprovider/build.gradle
+++ b/privacysandbox/ui/integration-tests/mediateesdkprovider/build.gradle
@@ -15,22 +15,17 @@
*/
plugins {
- id 'AndroidXPlugin'
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
}
android {
- namespace 'androidx.privacysandbox.ui.integration.mediateesdkprovider'
+ namespace "androidx.privacysandbox.ui.integration.mediateesdkprovider"
defaultConfig {
applicationId "androidx.privacysandbox.ui.integration.mediateesdkprovider"
minSdk 33
- targetSdk 33
- versionCode 1
- versionName "1.0"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -38,13 +33,6 @@
minifyEnabled false
}
}
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
}
dependencies {
diff --git a/privacysandbox/ui/integration-tests/testaidl/.gitignore b/privacysandbox/ui/integration-tests/testaidl/.gitignore
deleted file mode 100644
index 42afabf..0000000
--- a/privacysandbox/ui/integration-tests/testaidl/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/privacysandbox/ui/integration-tests/testaidl/build.gradle b/privacysandbox/ui/integration-tests/testaidl/build.gradle
index 0375687..f51a34a 100644
--- a/privacysandbox/ui/integration-tests/testaidl/build.gradle
+++ b/privacysandbox/ui/integration-tests/testaidl/build.gradle
@@ -15,19 +15,16 @@
*/
plugins {
- id 'AndroidXPlugin'
- id 'com.android.library'
- id 'org.jetbrains.kotlin.android'
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
}
android {
- namespace 'androidx.privacysandbox.ui.integration.testaidl'
+ namespace "androidx.privacysandbox.ui.integration.testaidl"
defaultConfig {
minSdk 33
- targetSdk 33
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -35,13 +32,6 @@
minifyEnabled false
}
}
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
buildFeatures {
aidl = true
}
diff --git a/privacysandbox/ui/integration-tests/testapp/.gitignore b/privacysandbox/ui/integration-tests/testapp/.gitignore
deleted file mode 100644
index 42afabf..0000000
--- a/privacysandbox/ui/integration-tests/testapp/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/privacysandbox/ui/integration-tests/testapp/build.gradle b/privacysandbox/ui/integration-tests/testapp/build.gradle
index a3d4c86..5d1c185 100644
--- a/privacysandbox/ui/integration-tests/testapp/build.gradle
+++ b/privacysandbox/ui/integration-tests/testapp/build.gradle
@@ -15,22 +15,17 @@
*/
plugins {
- id 'AndroidXPlugin'
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
}
android {
- namespace 'androidx.privacysandbox.ui.integration.testapp'
+ namespace "androidx.privacysandbox.ui.integration.testapp"
defaultConfig {
applicationId "androidx.privacysandbox.ui.integration.testapp"
minSdk 33
- targetSdk 33
- versionCode 1
- versionName "1.0"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -38,13 +33,6 @@
minifyEnabled false
}
}
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
}
dependencies {
diff --git a/privacysandbox/ui/integration-tests/testsdkprovider/.gitignore b/privacysandbox/ui/integration-tests/testsdkprovider/.gitignore
deleted file mode 100644
index 42afabf..0000000
--- a/privacysandbox/ui/integration-tests/testsdkprovider/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/privacysandbox/ui/integration-tests/testsdkprovider/build.gradle b/privacysandbox/ui/integration-tests/testsdkprovider/build.gradle
index 248162d..f542e52 100644
--- a/privacysandbox/ui/integration-tests/testsdkprovider/build.gradle
+++ b/privacysandbox/ui/integration-tests/testsdkprovider/build.gradle
@@ -15,9 +15,9 @@
*/
plugins {
- id 'AndroidXPlugin'
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
}
android {
@@ -26,11 +26,6 @@
defaultConfig {
applicationId "androidx.privacysandbox.ui.integration.testsdkprovider"
minSdk 33
- targetSdk 33
- versionCode 1
- versionName "1.0"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -38,13 +33,6 @@
minifyEnabled false
}
}
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
}
dependencies {
diff --git a/room/integration-tests/multiplatformtestapp/build.gradle b/room/integration-tests/multiplatformtestapp/build.gradle
index 4848221..9b66b89 100644
--- a/room/integration-tests/multiplatformtestapp/build.gradle
+++ b/room/integration-tests/multiplatformtestapp/build.gradle
@@ -18,29 +18,47 @@
plugins {
id("AndroidXPlugin")
+ id("com.android.library")
id("com.google.devtools.ksp")
}
-def nativeEnabled = KmpPlatformsKt.enableNative(project)
+// Disabled due to https://youtrack.jetbrains.com/issue/KT-65761
+ext["kotlin.native.disableCompilerDaemon"] = 'true'
androidXMultiplatform {
- mac()
- linux()
+ android()
ios()
+ jvm()
+ linux()
+ mac()
sourceSets {
commonTest {
dependencies {
implementation(libs.kotlinStdlib)
implementation(project(":room:room-runtime"))
+ implementation(project(":sqlite:sqlite-bundled"))
implementation(project(":kruth:kruth"))
implementation(libs.kotlinTest)
+ implementation(libs.kotlinCoroutinesTest)
}
}
- if (nativeEnabled) {
- nativeTest {
- dependsOn(commonTest)
+ androidInstrumentedTest {
+ dependsOn(commonTest)
+ dependencies {
+ implementation(libs.kotlinTestJunit)
+ implementation(libs.testRunner)
+ implementation(libs.testCore)
}
}
+ jvmTest {
+ dependsOn(commonTest)
+ dependencies {
+ implementation(libs.kotlinTestJunit)
+ }
+ }
+ nativeTest {
+ dependsOn(commonTest)
+ }
targets.all { target ->
if (target.platformType == KotlinPlatformType.native) {
target.compilations["test"].defaultSourceSet {
@@ -51,6 +69,24 @@
}
}
+// TODO(b/325111583): Create a helper function to configure KSP with KMP targets
+dependencies {
+ def roomCompilerDependency = project(path: ":room:room-compiler", configuration: "shadowAndImplementation")
+ add("kspAndroidAndroidTest", roomCompilerDependency)
+ add("kspJvmTest", roomCompilerDependency)
+ add("kspLinuxX64Test", roomCompilerDependency)
+ if (KmpPlatformsKt.enableMac(project)) {
+ add("kspIosSimulatorArm64Test", roomCompilerDependency)
+ add("kspIosX64Test", roomCompilerDependency)
+ add("kspMacosX64Test", roomCompilerDependency)
+ add("kspMacosArm64Test", roomCompilerDependency)
+ }
+}
+
ksp {
arg("room.generateKotlin", "true")
}
+
+android {
+ namespace "androidx.room.integration.multiplatformtestapp"
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
new file mode 100644
index 0000000..9b81e8a
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.kruth.assertThat
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+
+class BuilderTest : BaseBuilderTest() {
+
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val file = instrumentation.targetContext.getDatabasePath("test.db")
+
+ override fun getRoomDatabaseBuilder(): RoomDatabase.Builder<SampleDatabase> {
+ return Room.databaseBuilder(
+ context = instrumentation.targetContext,
+ name = file.path,
+ factory = { SampleDatabase::class.instantiateImpl() }
+ ).setDriver(BundledSQLiteDriver(file.path))
+ }
+
+ @BeforeTest
+ fun before() {
+ assertThat(file).isNotNull()
+ file.parentFile?.mkdirs()
+ deleteDatabaseFile()
+ }
+
+ @AfterTest
+ fun after() {
+ deleteDatabaseFile()
+ }
+
+ private fun deleteDatabaseFile() {
+ instrumentation.targetContext.deleteDatabase(file.name)
+ }
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
new file mode 100644
index 0000000..7b015ad
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.room.Room
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import androidx.test.platform.app.InstrumentationRegistry
+
+class SimpleQueryTest : BaseSimpleQueryTest() {
+
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ override fun getRoomDatabase(): SampleDatabase {
+ return Room.inMemoryDatabaseBuilder(
+ context = instrumentation.targetContext,
+ factory = { SampleDatabase::class.instantiateImpl() }
+ ).setDriver(BundledSQLiteDriver(":memory:"))
+ .build()
+ }
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseBuilderTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseBuilderTest.kt
new file mode 100644
index 0000000..7b346386
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseBuilderTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.kruth.assertThat
+import androidx.room.RoomDatabase
+import androidx.sqlite.SQLiteConnection
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+
+abstract class BaseBuilderTest {
+ abstract fun getRoomDatabaseBuilder(): RoomDatabase.Builder<SampleDatabase>
+
+ @Test
+ fun createOpenCallback() = runTest {
+ var onCreateInvoked = 0
+ var onOpenInvoked = 0
+ val testCallback = object : RoomDatabase.Callback() {
+ override fun onCreate(connection: SQLiteConnection) {
+ onCreateInvoked++
+ }
+
+ override fun onOpen(connection: SQLiteConnection) {
+ onOpenInvoked++
+ }
+ }
+
+ val builder = getRoomDatabaseBuilder()
+ .addCallback(testCallback)
+
+ val db1 = builder.build()
+
+ // No callback invoked, Room opens the database lazily
+ assertThat(onCreateInvoked).isEqualTo(0)
+ assertThat(onOpenInvoked).isEqualTo(0)
+
+ db1.dao().insertItem(1)
+
+ // Database is created and opened
+ assertThat(onCreateInvoked).isEqualTo(1)
+ assertThat(onOpenInvoked).isEqualTo(1)
+
+ db1.close()
+
+ val db2 = builder.build()
+
+ db2.dao().insertItem(2)
+
+ // Database was already created, it is now only opened
+ assertThat(onCreateInvoked).isEqualTo(1)
+ assertThat(onOpenInvoked).isEqualTo(2)
+
+ db2.close()
+ }
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt
new file mode 100644
index 0000000..a453e7a
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.kruth.assertThat
+import androidx.kruth.assertThrows
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+
+abstract class BaseSimpleQueryTest {
+
+ abstract fun getRoomDatabase(): SampleDatabase
+
+ @Test
+ fun preparedInsertAndDelete() = runTest {
+ val dao = getRoomDatabase().dao()
+ assertThat(dao.insertItem(1)).isEqualTo(1)
+ assertThat(dao.getSingleItem().pk).isEqualTo(1)
+ assertThat(dao.deleteItem(1)).isEqualTo(1)
+ assertThat(dao.deleteItem(1)).isEqualTo(0) // Nothing deleted
+ assertThrows<IllegalStateException> {
+ dao.getSingleItem()
+ }.hasMessageThat().contains("The query result was empty")
+ }
+
+ @Test
+ fun emptyResult() = runTest {
+ val db = getRoomDatabase()
+ assertThrows<IllegalStateException> {
+ db.dao().getSingleItem()
+ }.hasMessageThat().contains("The query result was empty")
+ }
+
+ @Test
+ fun queryList() = runTest {
+ val dao = getRoomDatabase().dao()
+ dao.insertItem(1)
+ dao.insertItem(2)
+ dao.insertItem(3)
+ val result = dao.getItemList()
+ assertThat(result.map { it.pk }).containsExactly(1L, 2L, 3L)
+ }
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
index cb400c0e5..936435b 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
@@ -19,8 +19,8 @@
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Entity
-import androidx.room.Insert
import androidx.room.PrimaryKey
+import androidx.room.Query
import androidx.room.RoomDatabase
@Entity
@@ -31,8 +31,18 @@
@Dao
interface SampleDao {
- @Insert
- fun insert(item: SampleEntity)
+
+ @Query("INSERT INTO SampleEntity (pk) VALUES (:pk)")
+ suspend fun insertItem(pk: Long): Long
+
+ @Query("DELETE FROM SampleEntity WHERE pk = :pk")
+ suspend fun deleteItem(pk: Long): Int
+
+ @Query("SELECT * FROM SampleEntity")
+ suspend fun getSingleItem(): SampleEntity
+
+ @Query("SELECT * FROM SampleEntity")
+ suspend fun getItemList(): List<SampleEntity>
}
@Database(
diff --git a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
new file mode 100644
index 0000000..d36069c
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlin.io.path.createTempFile
+
+class BuilderTest : BaseBuilderTest() {
+ override fun getRoomDatabaseBuilder(): RoomDatabase.Builder<SampleDatabase> {
+ val tempFile = createTempFile("test.db").also { it.toFile().deleteOnExit() }
+ return Room.databaseBuilder(tempFile.toString()) { SampleDatabase::class.instantiateImpl() }
+ .setDriver(BundledSQLiteDriver(tempFile.toString()))
+ }
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
new file mode 100644
index 0000000..6257320
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.room.Room
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+
+class SimpleQueryTest : BaseSimpleQueryTest() {
+
+ override fun getRoomDatabase(): SampleDatabase {
+ return Room.inMemoryDatabaseBuilder { SampleDatabase::class.instantiateImpl() }
+ .setDriver(BundledSQLiteDriver(":memory:"))
+ .build()
+ }
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
new file mode 100644
index 0000000..2411bc0
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlin.random.Random
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+import platform.posix.remove
+
+class BuilderTest : BaseBuilderTest() {
+
+ private val filename = "/tmp/test-${Random.nextInt()}.db"
+
+ override fun getRoomDatabaseBuilder(): RoomDatabase.Builder<SampleDatabase> {
+ return Room.databaseBuilder(filename) { SampleDatabase::class.instantiateImpl() }
+ .setDriver(BundledSQLiteDriver(filename))
+ }
+
+ @BeforeTest
+ fun before() {
+ deleteDatabaseFile()
+ }
+
+ @AfterTest
+ fun after() {
+ deleteDatabaseFile()
+ }
+
+ private fun deleteDatabaseFile() {
+ remove(filename)
+ remove("$filename-wal")
+ remove("$filename-shm")
+ }
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
new file mode 100644
index 0000000..6257320
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import androidx.room.Room
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+
+class SimpleQueryTest : BaseSimpleQueryTest() {
+
+ override fun getRoomDatabase(): SampleDatabase {
+ return Room.inMemoryDatabaseBuilder { SampleDatabase::class.instantiateImpl() }
+ .setDriver(BundledSQLiteDriver(":memory:"))
+ .build()
+ }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/QueryTransactionTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/QueryTransactionTest.java
index e60ac96..cb7238a 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/QueryTransactionTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/QueryTransactionTest.java
@@ -20,7 +20,15 @@
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteTransactionListener;
+import android.os.CancellationSignal;
+import android.util.Pair;
+
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.arch.core.executor.ArchTaskExecutor;
import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
import androidx.lifecycle.Lifecycle;
@@ -43,6 +51,11 @@
import androidx.room.RoomWarnings;
import androidx.room.Transaction;
import androidx.room.paging.LimitOffsetDataSource;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.SupportSQLiteQuery;
+import androidx.sqlite.db.SupportSQLiteStatement;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
@@ -53,8 +66,10 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
@@ -93,8 +108,11 @@
@Before
public void initDb() {
resetTransactionCount();
- mDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(),
- TransactionDb.class).build();
+ mDb = Room.inMemoryDatabaseBuilder(
+ ApplicationProvider.getApplicationContext(),
+ TransactionDb.class)
+ .openHelperFactory(new TransactionOpenHelperFactory())
+ .build();
mDao = mUseTransactionDao ? mDb.transactionDao() : mDb.dao();
drain();
}
@@ -279,6 +297,19 @@
}
}
+ private static void incrementTransactionCount() {
+ // When incrementing the transaction count, ignore those coming from the refresh runnable
+ // in the invalidation tracker.
+ StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+ for (StackTraceElement element : stack) {
+ String fileName = element.getFileName();
+ if (fileName != null && fileName.equals("InvalidationTracker.android.kt")) {
+ return;
+ }
+ }
+ sStartedTransactionCount.incrementAndGet();
+ }
+
private void resetTransactionCount() {
sStartedTransactionCount.set(0);
}
@@ -486,11 +517,298 @@
abstract EntityDao dao();
abstract TransactionDao transactionDao();
+ }
+
+ static class TransactionOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
+
+ private final SupportSQLiteOpenHelper.Factory mDelegate =
+ new FrameworkSQLiteOpenHelperFactory();
+
+ @NonNull
+ @Override
+ public SupportSQLiteOpenHelper create(
+ @NonNull SupportSQLiteOpenHelper.Configuration configuration) {
+ return new TransactionSupportSQLiteOpenHelper(mDelegate.create(configuration));
+ }
+ }
+
+ static class TransactionSupportSQLiteOpenHelper implements SupportSQLiteOpenHelper {
+ private final SupportSQLiteOpenHelper mDelegate;
+
+ TransactionSupportSQLiteOpenHelper(SupportSQLiteOpenHelper delegate) {
+ this.mDelegate = delegate;
+ }
+
+ @Nullable
+ @Override
+ public String getDatabaseName() {
+ return mDelegate.getDatabaseName();
+ }
+
+ @Override
+ public void setWriteAheadLoggingEnabled(boolean enabled) {
+ mDelegate.setWriteAheadLoggingEnabled(enabled);
+ }
+
+ @NonNull
+ @Override
+ public SupportSQLiteDatabase getWritableDatabase() {
+ return new TransactionSupportSQLiteDatabase(mDelegate.getWritableDatabase());
+ }
+
+ @NonNull
+ @Override
+ public SupportSQLiteDatabase getReadableDatabase() {
+ return new TransactionSupportSQLiteDatabase(mDelegate.getReadableDatabase());
+ }
+
+ @Override
+ public void close() {
+ mDelegate.close();
+ }
+ }
+
+ static class TransactionSupportSQLiteDatabase implements SupportSQLiteDatabase {
+ private final SupportSQLiteDatabase mDelegate;
+
+ TransactionSupportSQLiteDatabase(SupportSQLiteDatabase delegate) {
+ this.mDelegate = delegate;
+ }
+
+ @NonNull
+ @Override
+ public SupportSQLiteStatement compileStatement(@NonNull String sql) {
+ return mDelegate.compileStatement(sql);
+ }
@Override
public void beginTransaction() {
- super.beginTransaction();
+ mDelegate.beginTransaction();
+ incrementTransactionCount();
+ }
+
+ @Override
+ public void beginTransactionNonExclusive() {
+ mDelegate.beginTransactionNonExclusive();
+ incrementTransactionCount();
+ }
+
+ @Override
+ public void beginTransactionReadOnly() {
+ mDelegate.beginTransactionReadOnly();
+ incrementTransactionCount();
+ }
+
+ @Override
+ public void beginTransactionWithListener(
+ @NonNull SQLiteTransactionListener transactionListener) {
+ mDelegate.beginTransactionWithListener(transactionListener);
sStartedTransactionCount.incrementAndGet();
}
+
+ @Override
+ public void beginTransactionWithListenerNonExclusive(
+ @NonNull SQLiteTransactionListener transactionListener) {
+ mDelegate.beginTransactionWithListenerNonExclusive(transactionListener);
+ sStartedTransactionCount.incrementAndGet();
+ }
+
+ @Override
+ public void beginTransactionWithListenerReadOnly(
+ @NonNull SQLiteTransactionListener transactionListener) {
+ mDelegate.beginTransactionWithListenerReadOnly(transactionListener);
+ sStartedTransactionCount.incrementAndGet();
+ }
+
+ @Override
+ public void endTransaction() {
+ mDelegate.endTransaction();
+ }
+
+ @Override
+ public void setTransactionSuccessful() {
+ mDelegate.setTransactionSuccessful();
+ }
+
+ @Override
+ public boolean inTransaction() {
+ return mDelegate.inTransaction();
+ }
+
+ @Override
+ public boolean isDbLockedByCurrentThread() {
+ return mDelegate.isDbLockedByCurrentThread();
+ }
+
+ @Override
+ public boolean yieldIfContendedSafely() {
+ return mDelegate.yieldIfContendedSafely();
+ }
+
+ @Override
+ public boolean yieldIfContendedSafely(long sleepAfterYieldDelayMillis) {
+ return mDelegate.yieldIfContendedSafely(sleepAfterYieldDelayMillis);
+ }
+
+ @Override
+ public boolean isExecPerConnectionSQLSupported() {
+ return mDelegate.isExecPerConnectionSQLSupported();
+ }
+
+ @Override
+ public void execPerConnectionSQL(@NonNull String sql, @Nullable Object[] bindArgs) {
+ mDelegate.execPerConnectionSQL(sql, bindArgs);
+ }
+
+ @Override
+ public int getVersion() {
+ return mDelegate.getVersion();
+ }
+
+ @Override
+ public void setVersion(int i) {
+ mDelegate.setVersion(i);
+ }
+
+ @Override
+ public long getMaximumSize() {
+ return mDelegate.getMaximumSize();
+ }
+
+ @Override
+ public long setMaximumSize(long numBytes) {
+ return mDelegate.setMaximumSize(numBytes);
+ }
+
+ @Override
+ public long getPageSize() {
+ return mDelegate.getPageSize();
+ }
+
+ @Override
+ public void setPageSize(long l) {
+ mDelegate.setPageSize(l);
+ }
+
+ @NonNull
+ @Override
+ public Cursor query(@NonNull String query) {
+ return mDelegate.query(query);
+ }
+
+ @NonNull
+ @Override
+ public Cursor query(@NonNull String query, @NonNull Object[] bindArgs) {
+ return mDelegate.query(query, bindArgs);
+ }
+
+ @NonNull
+ @Override
+ public Cursor query(@NonNull SupportSQLiteQuery query) {
+ return mDelegate.query(query);
+ }
+
+ @NonNull
+ @Override
+ public Cursor query(@NonNull SupportSQLiteQuery query,
+ @Nullable CancellationSignal cancellationSignal) {
+ return mDelegate.query(query, cancellationSignal);
+ }
+
+ @Override
+ public long insert(@NonNull String table, int conflictAlgorithm,
+ @NonNull ContentValues values) throws SQLException {
+ return mDelegate.insert(table, conflictAlgorithm, values);
+ }
+
+ @Override
+ public int delete(@NonNull String table, @Nullable String whereClause,
+ @Nullable Object[] whereArgs) {
+ return mDelegate.delete(table, whereClause, whereArgs);
+ }
+
+ @Override
+ public int update(@NonNull String table, int conflictAlgorithm,
+ @NonNull ContentValues values, @Nullable String whereClause,
+ @Nullable Object[] whereArgs) {
+ return mDelegate.update(table, conflictAlgorithm, values, whereClause, whereArgs);
+ }
+
+ @Override
+ public void execSQL(@NonNull String sql) throws SQLException {
+ mDelegate.execSQL(sql);
+ }
+
+ @Override
+ public void execSQL(@NonNull String sql, @NonNull Object[] bindArgs) throws SQLException {
+ mDelegate.execSQL(sql, bindArgs);
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return mDelegate.isReadOnly();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return mDelegate.isOpen();
+ }
+
+ @Override
+ public boolean needUpgrade(int newVersion) {
+ return mDelegate.needUpgrade(newVersion);
+ }
+
+ @Nullable
+ @Override
+ public String getPath() {
+ return mDelegate.getPath();
+ }
+
+ @Override
+ public void setLocale(@NonNull Locale locale) {
+ mDelegate.setLocale(locale);
+ }
+
+ @Override
+ public void setMaxSqlCacheSize(int cacheSize) {
+ mDelegate.setMaxSqlCacheSize(cacheSize);
+ }
+
+ @Override
+ public void setForeignKeyConstraintsEnabled(boolean enabled) {
+ mDelegate.setForeignKeyConstraintsEnabled(enabled);
+ }
+
+ @Override
+ public boolean enableWriteAheadLogging() {
+ return mDelegate.enableWriteAheadLogging();
+ }
+
+ @Override
+ public void disableWriteAheadLogging() {
+ mDelegate.disableWriteAheadLogging();
+ }
+
+ @Override
+ public boolean isWriteAheadLoggingEnabled() {
+ return mDelegate.isWriteAheadLoggingEnabled();
+ }
+
+ @Nullable
+ @Override
+ public List<Pair<String, String>> getAttachedDbs() {
+ return mDelegate.getAttachedDbs();
+ }
+
+ @Override
+ public boolean isDatabaseIntegrityOk() {
+ return mDelegate.isDatabaseIntegrityOk();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mDelegate.close();
+ }
}
}
diff --git a/room/room-common/api/current.ignore b/room/room-common/api/current.ignore
deleted file mode 100644
index 59bc8f7..0000000
--- a/room/room-common/api/current.ignore
+++ /dev/null
@@ -1,27 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.room.AutoMigration#spec():
- Method androidx.room.AutoMigration.spec has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.Database#entities():
- Method androidx.room.Database.entities has changed return type from kotlin.reflect.KClass<?>[] to Class<?>[]
-ChangedType: androidx.room.Database#views():
- Method androidx.room.Database.views has changed return type from kotlin.reflect.KClass<?>[] to Class<?>[]
-ChangedType: androidx.room.Delete#entity():
- Method androidx.room.Delete.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.ForeignKey#entity():
- Method androidx.room.ForeignKey.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.Fts4#contentEntity():
- Method androidx.room.Fts4.contentEntity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.Insert#entity():
- Method androidx.room.Insert.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.Junction#value():
- Method androidx.room.Junction.value has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.RawQuery#observedEntities():
- Method androidx.room.RawQuery.observedEntities has changed return type from kotlin.reflect.KClass<?>[] to Class<?>[]
-ChangedType: androidx.room.Relation#entity():
- Method androidx.room.Relation.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.TypeConverters#value():
- Method androidx.room.TypeConverters.value has changed return type from kotlin.reflect.KClass<?>[] to Class<?>[]
-ChangedType: androidx.room.Update#entity():
- Method androidx.room.Update.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.Upsert#entity():
- Method androidx.room.Upsert.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
diff --git a/room/room-common/api/current.txt b/room/room-common/api/current.txt
index f4e229c..15d620b 100644
--- a/room/room-common/api/current.txt
+++ b/room/room-common/api/current.txt
@@ -20,8 +20,6 @@
}
public enum BuiltInTypeConverters.State {
- method public static androidx.room.BuiltInTypeConverters.State valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.BuiltInTypeConverters.State[] values();
enum_constant public static final androidx.room.BuiltInTypeConverters.State DISABLED;
enum_constant public static final androidx.room.BuiltInTypeConverters.State ENABLED;
enum_constant public static final androidx.room.BuiltInTypeConverters.State INHERITED;
@@ -213,15 +211,11 @@
}
public enum FtsOptions.MatchInfo {
- method public static androidx.room.FtsOptions.MatchInfo valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.FtsOptions.MatchInfo[] values();
enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
}
public enum FtsOptions.Order {
- method public static androidx.room.FtsOptions.Order valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.FtsOptions.Order[] values();
enum_constant public static final androidx.room.FtsOptions.Order ASC;
enum_constant public static final androidx.room.FtsOptions.Order DESC;
}
@@ -241,8 +235,6 @@
}
public enum Index.Order {
- method public static androidx.room.Index.Order valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.Index.Order[] values();
enum_constant public static final androidx.room.Index.Order ASC;
enum_constant public static final androidx.room.Index.Order DESC;
}
diff --git a/room/room-common/api/restricted_current.ignore b/room/room-common/api/restricted_current.ignore
deleted file mode 100644
index 59bc8f7..0000000
--- a/room/room-common/api/restricted_current.ignore
+++ /dev/null
@@ -1,27 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.room.AutoMigration#spec():
- Method androidx.room.AutoMigration.spec has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.Database#entities():
- Method androidx.room.Database.entities has changed return type from kotlin.reflect.KClass<?>[] to Class<?>[]
-ChangedType: androidx.room.Database#views():
- Method androidx.room.Database.views has changed return type from kotlin.reflect.KClass<?>[] to Class<?>[]
-ChangedType: androidx.room.Delete#entity():
- Method androidx.room.Delete.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.ForeignKey#entity():
- Method androidx.room.ForeignKey.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.Fts4#contentEntity():
- Method androidx.room.Fts4.contentEntity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.Insert#entity():
- Method androidx.room.Insert.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.Junction#value():
- Method androidx.room.Junction.value has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.RawQuery#observedEntities():
- Method androidx.room.RawQuery.observedEntities has changed return type from kotlin.reflect.KClass<?>[] to Class<?>[]
-ChangedType: androidx.room.Relation#entity():
- Method androidx.room.Relation.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.TypeConverters#value():
- Method androidx.room.TypeConverters.value has changed return type from kotlin.reflect.KClass<?>[] to Class<?>[]
-ChangedType: androidx.room.Update#entity():
- Method androidx.room.Update.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
-ChangedType: androidx.room.Upsert#entity():
- Method androidx.room.Upsert.entity has changed return type from kotlin.reflect.KClass<?> to Class<?>
diff --git a/room/room-common/api/restricted_current.txt b/room/room-common/api/restricted_current.txt
index 4accc75d2..a618b5c 100644
--- a/room/room-common/api/restricted_current.txt
+++ b/room/room-common/api/restricted_current.txt
@@ -20,8 +20,6 @@
}
public enum BuiltInTypeConverters.State {
- method public static androidx.room.BuiltInTypeConverters.State valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.BuiltInTypeConverters.State[] values();
enum_constant public static final androidx.room.BuiltInTypeConverters.State DISABLED;
enum_constant public static final androidx.room.BuiltInTypeConverters.State ENABLED;
enum_constant public static final androidx.room.BuiltInTypeConverters.State INHERITED;
@@ -213,15 +211,11 @@
}
public enum FtsOptions.MatchInfo {
- method public static androidx.room.FtsOptions.MatchInfo valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.FtsOptions.MatchInfo[] values();
enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
}
public enum FtsOptions.Order {
- method public static androidx.room.FtsOptions.Order valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.FtsOptions.Order[] values();
enum_constant public static final androidx.room.FtsOptions.Order ASC;
enum_constant public static final androidx.room.FtsOptions.Order DESC;
}
@@ -241,8 +235,6 @@
}
public enum Index.Order {
- method public static androidx.room.Index.Order valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.Index.Order[] values();
enum_constant public static final androidx.room.Index.Order ASC;
enum_constant public static final androidx.room.Index.Order DESC;
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XRoundEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XRoundEnv.kt
index f25ad04..baf25c7a1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XRoundEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XRoundEnv.kt
@@ -29,10 +29,6 @@
* @see javax.annotation.processing.RoundEnvironment
*/
interface XRoundEnv {
- /**
- * The root elements in the round.
- */
- val rootElements: Set<XElement>
/**
* Returns true if no further rounds of processing will be done.
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacRoundEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacRoundEnv.kt
index 16c191d..b83e87e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacRoundEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacRoundEnv.kt
@@ -18,23 +18,14 @@
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.XRoundEnv
-import com.google.auto.common.MoreElements
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.Element
import kotlin.reflect.KClass
-@Suppress("UnstableApiUsage")
internal class JavacRoundEnv(
private val env: JavacProcessingEnv,
val delegate: RoundEnvironment
) : XRoundEnv {
- override val rootElements: Set<XElement> by lazy {
- delegate.rootElements.map {
- check(MoreElements.isType(it))
- env.wrapTypeElement(MoreElements.asType(it))
- }.toSet()
- }
-
override val isProcessingOver: Boolean
get() = delegate.processingOver()
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAsMemberOf.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAsMemberOf.kt
index 53cd24a..5b64b7a 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAsMemberOf.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAsMemberOf.kt
@@ -47,9 +47,9 @@
internal fun KSValueParameter.typeAsMemberOf(
functionDeclaration: KSFunctionDeclaration,
- ksType: KSType?
+ ksType: KSType?,
+ resolved: KSType = type.resolve()
): KSType {
- val resolved = type.resolve()
if (functionDeclaration.isStatic()) {
// calling as member with a static would throw as it might be a member of the companion
// object
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
index a10b052..15a9043 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
@@ -73,12 +73,15 @@
private fun createAsMemberOf(container: XType?): KspType {
check(container is KspType?)
+ val resolvedType = parameter.type.resolve()
return env.wrap(
- originatingReference = parameter.type,
+ originalAnnotations = parameter.type.annotations,
ksType = parameter.typeAsMemberOf(
functionDeclaration = enclosingElement.declaration,
- ksType = container?.ksType
- )
+ ksType = container?.ksType,
+ resolved = resolvedType
+ ),
+ allowPrimitives = !resolvedType.isTypeParameter()
).copyWithScope(
KSTypeVarianceResolverScope.MethodParameter(
kspExecutableElement = enclosingElement,
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspRoundEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspRoundEnv.kt
index faa7a55..272b5a5 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspRoundEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspRoundEnv.kt
@@ -34,9 +34,6 @@
override val isProcessingOver: Boolean
get() = env == null
- override val rootElements: Set<XElement>
- get() = TODO("not supported")
-
override fun getElementsAnnotatedWith(klass: KClass<out Annotation>): Set<XElement> {
return getElementsAnnotatedWith(
annotationQualifiedName = klass.qualifiedName ?: error("No qualified name for $klass")
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
index dc9a2b9..c83a2c9 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
@@ -55,8 +55,8 @@
val ROOM_DB_KT = XClassName.get(ROOM_PACKAGE, "RoomDatabaseKt")
val ROOM_DB_CALLBACK = XClassName.get(ROOM_PACKAGE, "RoomDatabase", "Callback")
val ROOM_DB_CONFIG = XClassName.get(ROOM_PACKAGE, "DatabaseConfiguration")
- val INSERTION_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityInsertionAdapter")
- val UPSERTION_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityUpsertionAdapter")
+ val INSERT_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityInsertionAdapter")
+ val UPSERT_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityUpsertionAdapter")
val DELETE_OR_UPDATE_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityDeletionOrUpdateAdapter")
val SHARED_SQLITE_STMT = XClassName.get(ROOM_PACKAGE, "SharedSQLiteStatement")
val INVALIDATION_TRACKER = XClassName.get(ROOM_PACKAGE, "InvalidationTracker")
@@ -80,6 +80,7 @@
val ROOM_OPEN_DELEGATE_VALIDATION_RESULT =
XClassName.get(ROOM_PACKAGE, "RoomOpenDelegate", "ValidationResult")
val STATEMENT_UTIL = XClassName.get("$ROOM_PACKAGE.util", "SQLiteStatementUtil")
+ val CONNECTION_UTIL = XClassName.get("$ROOM_PACKAGE.util", "SQLiteConnectionUtil")
}
object RoomAnnotationTypeNames {
@@ -161,8 +162,10 @@
}
object ExceptionTypeNames {
- val ILLEGAL_STATE_EXCEPTION = IllegalStateException::class.asClassName()
- val ILLEGAL_ARG_EXCEPTION = IllegalArgumentException::class.asClassName()
+ val JAVA_ILLEGAL_STATE_EXCEPTION = XClassName.get("java.lang", "IllegalStateException")
+ val JAVA_ILLEGAL_ARG_EXCEPTION = XClassName.get("java.lang", "IllegalArgumentException")
+ val KOTLIN_ILLEGAL_STATE_EXCEPTION = XClassName.get("kotlin", "IllegalStateException")
+ val KOTLIN_ILLEGAL_ARG_EXCEPTION = XClassName.get("kotlin", "IllegalArgumentException")
}
object GuavaTypeNames {
@@ -285,6 +288,7 @@
val COLLECTIONS_KT = XClassName.get("kotlin.collections", "CollectionsKt")
val SETS_KT = XClassName.get("kotlin.collections", "SetsKt")
val MAPS_KT = XClassName.get("kotlin.collections", "MapsKt")
+ val STRING_BUILDER = XClassName.get("kotlin.text", "StringBuilder")
}
object RoomMemberNames {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
index cd1602c..962c54a 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
@@ -58,9 +58,9 @@
type = element.type,
queryMethods = emptyList(),
rawQueryMethods = emptyList(),
- insertionMethods = emptyList(),
- upsertionMethods = emptyList(),
- deletionMethods = emptyList(),
+ insertMethods = emptyList(),
+ upsertMethods = emptyList(),
+ deleteMethods = emptyList(),
updateMethods = emptyList(),
transactionMethods = emptyList(),
kotlinBoxedPrimitiveMethodDelegates = emptyList(),
@@ -142,16 +142,16 @@
).process()
} ?: emptyList()
- val insertionMethods = methods[Insert::class]?.map {
- InsertionMethodProcessor(
+ val insertMethods = methods[Insert::class]?.map {
+ InsertMethodProcessor(
baseContext = context,
containing = declaredType,
executableElement = it
).process()
} ?: emptyList()
- val deletionMethods = methods[Delete::class]?.map {
- DeletionMethodProcessor(
+ val deleteMethods = methods[Delete::class]?.map {
+ DeleteMethodProcessor(
baseContext = context,
containing = declaredType,
executableElement = it
@@ -166,8 +166,8 @@
).process()
} ?: emptyList()
- val upsertionMethods = methods[Upsert::class]?.map {
- UpsertionMethodProcessor(
+ val upsertMethods = methods[Upsert::class]?.map {
+ UpsertMethodProcessor(
baseContext = context,
containing = declaredType,
executableElement = it
@@ -246,10 +246,10 @@
type = declaredType,
queryMethods = queryMethods,
rawQueryMethods = rawQueryMethods,
- insertionMethods = insertionMethods,
- deletionMethods = deletionMethods,
+ insertMethods = insertMethods,
+ deleteMethods = deleteMethods,
updateMethods = updateMethods,
- upsertionMethods = upsertionMethods,
+ upsertMethods = upsertMethods,
transactionMethods = transactionMethods.toList(),
kotlinBoxedPrimitiveMethodDelegates = kotlinBoxedPrimitiveBridgeMethods,
kotlinDefaultMethodDelegates = kotlinDefaultMethodDelegates.toList(),
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DeletionMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DeleteMethodProcessor.kt
similarity index 89%
rename from room/room-compiler/src/main/kotlin/androidx/room/processor/DeletionMethodProcessor.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/processor/DeleteMethodProcessor.kt
index 2bb3d9f..c84f23e8 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DeletionMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DeleteMethodProcessor.kt
@@ -18,16 +18,16 @@
import androidx.room.Delete
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XType
-import androidx.room.vo.DeletionMethod
+import androidx.room.vo.DeleteMethod
-class DeletionMethodProcessor(
+class DeleteMethodProcessor(
baseContext: Context,
val containing: XType,
val executableElement: XMethodElement
) {
val context = baseContext.fork(executableElement)
- fun process(): DeletionMethod {
+ fun process(): DeleteMethod {
val delegate = ShortcutMethodProcessor(context, containing, executableElement)
val annotation = delegate
.extractAnnotation(Delete::class, ProcessorErrors.MISSING_DELETE_ANNOTATION)
@@ -44,11 +44,11 @@
val (entities, params) = delegate.extractParams(
targetEntityType = annotation?.getAsType("entity"),
- missingParamError = ProcessorErrors.DELETION_MISSING_PARAMS,
+ missingParamError = ProcessorErrors.DELETE_MISSING_PARAMS,
onValidatePartialEntity = { _, _ -> }
)
- return DeletionMethod(
+ return DeleteMethod(
element = delegate.executableElement,
entities = entities,
parameters = params,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/InsertionMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/InsertMethodProcessor.kt
similarity index 93%
rename from room/room-compiler/src/main/kotlin/androidx/room/processor/InsertionMethodProcessor.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/processor/InsertMethodProcessor.kt
index 592abec..ec75c3b 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/InsertionMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/InsertMethodProcessor.kt
@@ -22,17 +22,17 @@
import androidx.room.OnConflictStrategy
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XType
-import androidx.room.vo.InsertionMethod
+import androidx.room.vo.InsertMethod
import androidx.room.vo.findFieldByColumnName
-class InsertionMethodProcessor(
+class InsertMethodProcessor(
baseContext: Context,
val containing: XType,
val executableElement: XMethodElement
) {
val context = baseContext.fork(executableElement)
- fun process(): InsertionMethod {
+ fun process(): InsertMethod {
val delegate = ShortcutMethodProcessor(context, containing, executableElement)
val annotation = delegate.extractAnnotation(
Insert::class,
@@ -48,12 +48,12 @@
val returnType = delegate.extractReturnType()
context.checker.notUnbound(
returnType, executableElement,
- ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS
+ ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_INSERT_METHODS
)
val (entities, params) = delegate.extractParams(
targetEntityType = annotation?.getAsType("entity"),
- missingParamError = ProcessorErrors.INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT,
+ missingParamError = ProcessorErrors.INSERT_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT,
onValidatePartialEntity = { entity, pojo ->
val missingPrimaryKeys = entity.primaryKey.fields.any {
pojo.findFieldByColumnName(it.columnName) == null
@@ -92,7 +92,7 @@
ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
)
- return InsertionMethod(
+ return InsertMethod(
element = executableElement,
returnType = returnType,
entities = entities,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
index 5942d89..e524dec 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
@@ -32,7 +32,7 @@
import androidx.room.ext.RoomCoroutinesTypeNames.COROUTINES_ROOM
import androidx.room.parser.ParsedQuery
import androidx.room.solver.TypeAdapterExtras
-import androidx.room.solver.prepared.binder.CallablePreparedQueryResultBinder.Companion.createPreparedBinder
+import androidx.room.solver.prepared.binder.CoroutinePreparedQueryResultBinder
import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
import androidx.room.solver.query.result.CoroutineResultBinder
import androidx.room.solver.query.result.QueryResultBinder
@@ -233,12 +233,10 @@
override fun findPreparedResultBinder(
returnType: XType,
query: ParsedQuery
- ) = createPreparedBinder(
- returnType = returnType,
- adapter = context.typeAdapterStore.findPreparedQueryResultAdapter(returnType, query)
- ) { callableImpl, dbProperty ->
- addCoroutineExecuteStatement(callableImpl, dbProperty)
- }
+ ) = CoroutinePreparedQueryResultBinder(
+ adapter = context.typeAdapterStore.findPreparedQueryResultAdapter(returnType, query),
+ continuationParamName = continuationParam.name
+ )
override fun findInsertMethodBinder(
returnType: XType,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index d4cbabc..a293e29 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -39,10 +39,10 @@
val ISSUE_TRACKER_LINK = "https://issuetracker.google.com/issues/new?component=413107"
val MISSING_QUERY_ANNOTATION = "Query methods must be annotated with ${Query::class.java}"
- val MISSING_INSERT_ANNOTATION = "Insertion methods must be annotated with ${Insert::class.java}"
- val MISSING_DELETE_ANNOTATION = "Deletion methods must be annotated with ${Delete::class.java}"
+ val MISSING_INSERT_ANNOTATION = "Insert methods must be annotated with ${Insert::class.java}"
+ val MISSING_DELETE_ANNOTATION = "Delete methods must be annotated with ${Delete::class.java}"
val MISSING_UPDATE_ANNOTATION = "Update methods must be annotated with ${Update::class.java}"
- val MISSING_UPSERT_ANNOTATION = "Upsertion methods must be annotated with ${Upsert::class.java}"
+ val MISSING_UPSERT_ANNOTATION = "Upsert methods must be annotated with ${Upsert::class.java}"
val MISSING_RAWQUERY_ANNOTATION = "RawQuery methods must be annotated with" +
" ${RawQuery::class.java}"
val INVALID_ON_CONFLICT_VALUE = "On conflict value must be one of @OnConflictStrategy values."
@@ -56,10 +56,10 @@
val CANNOT_RESOLVE_RETURN_TYPE = "Cannot resolve return type for %s"
val CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS = "Cannot use unbound generics in query" +
" methods. It must be bound to a type through base Dao class."
- val CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS = "Cannot use unbound generics in" +
- " insertion methods. It must be bound to a type through base Dao class."
- val CANNOT_USE_UNBOUND_GENERICS_IN_UPSERTION_METHODS = "Cannot use unbound generics in" +
- " upsertion methods. It must be bound to a type through base Dao class."
+ val CANNOT_USE_UNBOUND_GENERICS_IN_INSERT_METHODS = "Cannot use unbound generics in" +
+ " insert methods. It must be bound to a type through base Dao class."
+ val CANNOT_USE_UNBOUND_GENERICS_IN_UPSERT_METHODS = "Cannot use unbound generics in" +
+ " upsert methods. It must be bound to a type through base Dao class."
val CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS = "Cannot use unbound fields in entities."
val CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES = "Cannot use unbound generics in Dao classes." +
" If you are trying to create a base DAO, create a normal class, extend it with type" +
@@ -145,13 +145,13 @@
" of the provided method's multimap return type must implement equals() and " +
"hashCode(). Key type is: $keyType."
- val INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT = "Method annotated with" +
+ val INSERT_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT = "Method annotated with" +
" @Insert but does not have any parameters to insert."
- val UPSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT = "Method annotated with" +
+ val UPSERT_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT = "Method annotated with" +
" @Upsert but does not have any parameters to insert or update."
- val DELETION_MISSING_PARAMS = "Method annotated with" +
+ val DELETE_MISSING_PARAMS = "Method annotated with" +
" @Delete but does not have any parameters to delete."
fun cannotMapSpecifiedColumn(column: String, columnsInQuery: List<String>, annotation: String) =
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertMethodProcessor.kt
similarity index 93%
rename from room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertMethodProcessor.kt
index eabff2f..5e6a126 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertMethodProcessor.kt
@@ -19,17 +19,17 @@
import androidx.room.Upsert
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XType
-import androidx.room.vo.UpsertionMethod
+import androidx.room.vo.UpsertMethod
import androidx.room.vo.findFieldByColumnName
-class UpsertionMethodProcessor(
+class UpsertMethodProcessor(
baseContext: Context,
val containing: XType,
val executableElement: XMethodElement
) {
val context = baseContext.fork(executableElement)
- fun process(): UpsertionMethod {
+ fun process(): UpsertMethod {
val delegate = ShortcutMethodProcessor(context, containing, executableElement)
val annotation = delegate.extractAnnotation(
@@ -40,12 +40,12 @@
val returnType = delegate.extractReturnType()
context.checker.notUnbound(
returnType, executableElement,
- ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_UPSERTION_METHODS
+ ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_UPSERT_METHODS
)
val (entities, params) = delegate.extractParams(
targetEntityType = annotation?.getAsType("entity"),
- missingParamError = ProcessorErrors.UPSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT,
+ missingParamError = ProcessorErrors.UPSERT_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT,
onValidatePartialEntity = { entity, pojo ->
val missingPrimaryKeys = entity.primaryKey.fields.any {
pojo.findFieldByColumnName(it.columnName) == null
@@ -84,7 +84,7 @@
ProcessorErrors.CANNOT_FIND_UPSERT_RESULT_ADAPTER
)
- return UpsertionMethod(
+ return UpsertMethod(
element = executableElement,
returnType = returnType,
entities = entities,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CoroutinePreparedQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CoroutinePreparedQueryResultBinder.kt
new file mode 100644
index 0000000..5172cacb
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CoroutinePreparedQueryResultBinder.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.prepared.binder
+
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
+import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.codegen.box
+import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
+import androidx.room.solver.CodeGenScope
+import androidx.room.solver.prepared.result.PreparedQueryResultAdapter
+
+/**
+ * Binder of prepared queries of a Kotlin coroutine suspend function.
+ */
+class CoroutinePreparedQueryResultBinder(
+ adapter: PreparedQueryResultAdapter?,
+ private val continuationParamName: String,
+) : PreparedQueryResultBinder(adapter) {
+
+ override fun executeAndReturn(
+ prepareQueryStmtBlock: CodeGenScope.() -> String,
+ preparedStmtProperty: XPropertySpec?,
+ dbProperty: XPropertySpec,
+ scope: CodeGenScope
+ ) {
+ error("Wrong executeAndReturn invoked")
+ }
+
+ override fun isMigratedToDriver(): Boolean = true
+
+ override fun executeAndReturn(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: CodeGenScope.(String) -> Unit,
+ returnTypeName: XTypeName,
+ scope: CodeGenScope
+ ) {
+ when (scope.language) {
+ CodeLanguage.JAVA -> executeAndReturnJava(
+ sqlQueryVar, dbProperty, bindStatement, returnTypeName, scope
+ )
+ CodeLanguage.KOTLIN -> executeAndReturnKotlin(
+ sqlQueryVar, dbProperty, bindStatement, scope
+ )
+ }
+ }
+
+ private fun executeAndReturnJava(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: CodeGenScope.(String) -> Unit,
+ returnTypeName: XTypeName,
+ scope: CodeGenScope
+ ) {
+ val connectionVar = scope.getTmpVar("_connection")
+ val statementVar = scope.getTmpVar("_stmt")
+ scope.builder.addStatement(
+ "return %M(%N, %L, %L, %L, %L)",
+ RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+ dbProperty,
+ false, // isReadOnly
+ true, // inTransaction
+ // TODO(b/322387497): Generate lambda syntax if possible
+ Function1TypeSpec(
+ language = scope.language,
+ parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+ parameterName = connectionVar,
+ returnTypeName = returnTypeName.box()
+ ) {
+ val functionScope = scope.fork()
+ val functionCode = functionScope.builder.apply {
+ addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
+ bindStatement(functionScope, statementVar)
+ adapter?.executeAndReturn(connectionVar, statementVar, functionScope)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
+ }.build()
+ this.addCode(functionCode)
+ },
+ continuationParamName
+ )
+ }
+
+ private fun executeAndReturnKotlin(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: CodeGenScope.(String) -> Unit,
+ scope: CodeGenScope
+ ) {
+ val connectionVar = scope.getTmpVar("_connection")
+ val statementVar = scope.getTmpVar("_stmt")
+ scope.builder.apply {
+ beginControlFlow(
+ "return %M(%N, %L, %L) { %L ->",
+ RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
+ dbProperty,
+ false, // isReadOnly
+ true, // inTransaction
+ connectionVar
+ )
+ addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
+ bindStatement(scope, statementVar)
+ adapter?.executeAndReturn(connectionVar, statementVar, scope)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
+ endControlFlow()
+ }
+ }
+}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/InstantPreparedQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/InstantPreparedQueryResultBinder.kt
index 7cf78d5..a7a3591 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/InstantPreparedQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/InstantPreparedQueryResultBinder.kt
@@ -16,15 +16,24 @@
package androidx.room.solver.prepared.binder
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.codegen.box
+import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
import androidx.room.solver.CodeGenScope
import androidx.room.solver.prepared.result.PreparedQueryResultAdapter
/**
* Default binder for prepared queries.
*/
-class InstantPreparedQueryResultBinder(adapter: PreparedQueryResultAdapter?) :
- PreparedQueryResultBinder(adapter) {
+class InstantPreparedQueryResultBinder(
+ adapter: PreparedQueryResultAdapter?
+) : PreparedQueryResultBinder(adapter) {
override fun executeAndReturn(
prepareQueryStmtBlock: CodeGenScope.() -> String,
@@ -42,4 +51,101 @@
scope = scope
)
}
+
+ override fun isMigratedToDriver(): Boolean = true
+
+ override fun executeAndReturn(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: CodeGenScope.(String) -> Unit,
+ returnTypeName: XTypeName,
+ scope: CodeGenScope
+ ) {
+ when (scope.language) {
+ CodeLanguage.JAVA -> executeAndReturnJava(
+ sqlQueryVar, dbProperty, bindStatement, returnTypeName, scope
+ )
+ CodeLanguage.KOTLIN -> executeAndReturnKotlin(
+ sqlQueryVar, dbProperty, bindStatement, scope
+ )
+ }
+ }
+
+ private fun executeAndReturnJava(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: CodeGenScope.(String) -> Unit,
+ returnTypeName: XTypeName,
+ scope: CodeGenScope
+ ) {
+ val connectionVar = scope.getTmpVar("_connection")
+ val statementVar = scope.getTmpVar("_stmt")
+ val returnPrefix = if (returnTypeName == XTypeName.UNIT_VOID) "" else "return "
+ scope.builder.addStatement(
+ "$returnPrefix%M(%N, %L, %L, %L)",
+ RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+ dbProperty,
+ false, // isReadOnly
+ true, // inTransaction
+ // TODO(b/322387497): Generate lambda syntax if possible
+ Function1TypeSpec(
+ language = scope.language,
+ parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+ parameterName = connectionVar,
+ returnTypeName = returnTypeName.box()
+ ) {
+ val functionScope = scope.fork()
+ val functionCode = functionScope.builder.apply {
+ addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
+ bindStatement(functionScope, statementVar)
+ adapter?.executeAndReturn(connectionVar, statementVar, functionScope)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
+ }.build()
+ this.addCode(functionCode)
+ }
+ )
+ }
+
+ private fun executeAndReturnKotlin(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: CodeGenScope.(String) -> Unit,
+ scope: CodeGenScope
+ ) {
+ val connectionVar = scope.getTmpVar("_connection")
+ val statementVar = scope.getTmpVar("_stmt")
+ scope.builder.apply {
+ beginControlFlow(
+ "return %M(%N, %L, %L) { %L ->",
+ RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
+ dbProperty,
+ false, // isReadOnly
+ true, // inTransaction
+ connectionVar
+ )
+ addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
+ bindStatement(scope, statementVar)
+ adapter?.executeAndReturn(connectionVar, statementVar, scope)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
+ endControlFlow()
+ }
+ }
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/PreparedQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/PreparedQueryResultBinder.kt
index 0f32876..2e736a5 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/PreparedQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/PreparedQueryResultBinder.kt
@@ -17,6 +17,7 @@
package androidx.room.solver.prepared.binder
import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeName
import androidx.room.solver.CodeGenScope
import androidx.room.solver.prepared.result.PreparedQueryResultAdapter
@@ -38,4 +39,21 @@
dbProperty: XPropertySpec,
scope: CodeGenScope
)
+
+ // TODO(b/319660042): Remove once migration to driver API is done.
+ open fun isMigratedToDriver(): Boolean = false
+
+ /**
+ * Receives the SQL and a function to bind args into a statement, it must then generate the
+ * code that steps on the query and if applicable returns the result of the write operation.
+ */
+ open fun executeAndReturn(
+ sqlQueryVar: String,
+ dbProperty: XPropertySpec,
+ bindStatement: CodeGenScope.(String) -> Unit,
+ returnTypeName: XTypeName,
+ scope: CodeGenScope
+ ) {
+ error("Result binder has not been migrated to use driver API.")
+ }
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/result/PreparedQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/result/PreparedQueryResultAdapter.kt
index 89be9b7..ef4e2d1 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/result/PreparedQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/result/PreparedQueryResultAdapter.kt
@@ -18,6 +18,7 @@
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
import androidx.room.compiler.codegen.XPropertySpec
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.isInt
@@ -26,6 +27,7 @@
import androidx.room.compiler.processing.isVoid
import androidx.room.compiler.processing.isVoidObject
import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.RoomTypeNames
import androidx.room.parser.QueryType
import androidx.room.solver.CodeGenScope
import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
@@ -109,4 +111,38 @@
}
}
}
+
+ fun executeAndReturn(
+ connectionVar: String,
+ statementVar: String,
+ scope: CodeGenScope
+ ) {
+ scope.builder.apply {
+ addStatement("%L.step()", statementVar)
+ if (returnType.isVoid() || returnType.isVoidObject() || returnType.isKotlinUnit()) {
+ if (returnType.isVoidObject()) {
+ addStatement("return null")
+ } else if (returnType.isVoid() && language == CodeLanguage.JAVA) {
+ addStatement("return null")
+ } else if (returnType.isKotlinUnit() && language == CodeLanguage.JAVA) {
+ addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
+ }
+ } else {
+ val returnPrefix = when (language) {
+ CodeLanguage.JAVA -> "return "
+ CodeLanguage.KOTLIN -> ""
+ }
+ val returnFunctionName = when (queryType) {
+ QueryType.INSERT -> "getLastInsertedRowId"
+ QueryType.UPDATE, QueryType.DELETE -> "getTotalChangedRows"
+ else -> error("No return function name for query type $queryType")
+ }
+ addStatement(
+ "$returnPrefix%M(%L)",
+ RoomTypeNames.CONNECTION_UTIL.packageMember(returnFunctionName),
+ connectionVar
+ )
+ }
+ }
+ }
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
index a930e05..e376b18 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
@@ -18,7 +18,7 @@
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
import androidx.room.compiler.codegen.XMemberName.Companion.companionMember
import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
import androidx.room.compiler.codegen.XPropertySpec
@@ -170,26 +170,40 @@
inTransaction: Boolean,
scope: CodeGenScope
) {
- val performFunctionName = getPerformFunctionName(inTransaction)
+ val connectionVar = scope.getTmpVar("_connection")
val statementVar = scope.getTmpVar("_stmt")
scope.builder.addStatement(
- "return %M(%N, %L, %L, %L)",
- RoomTypeNames.DB_UTIL.packageMember(performFunctionName),
+ "return %M(%N, %L, %L, %L, %L)",
+ RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
dbProperty,
- sqlQueryVar,
+ true, // isReadOnly
+ inTransaction,
// TODO(b/322387497): Generate lambda syntax if possible
Function1TypeSpec(
language = scope.language,
- parameterTypeName = SQLiteDriverTypeNames.STATEMENT,
- parameterName = statementVar,
+ parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+ parameterName = connectionVar,
returnTypeName = returnTypeName.box()
) {
val functionScope = scope.fork()
- bindStatement(functionScope, statementVar)
val outVar = functionScope.getTmpVar("_result")
- adapter?.convert(outVar, statementVar, functionScope)
- this.addCode(functionScope.generate())
- this.addStatement("return %L", outVar)
+ val functionCode = functionScope.builder.apply {
+ addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
+ bindStatement(functionScope, statementVar)
+ adapter?.convert(outVar, statementVar, functionScope)
+ addStatement("return %L", outVar)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
+ }.build()
+ this.addCode(functionCode)
},
continuationParamName
)
@@ -202,28 +216,33 @@
inTransaction: Boolean,
scope: CodeGenScope
) {
+ val connectionVar = scope.getTmpVar("_connection")
val statementVar = scope.getTmpVar("_stmt")
- val performFunctionName = getPerformFunctionName(inTransaction)
scope.builder.apply {
beginControlFlow(
- "return %M(%N, %L) { %L ->",
- RoomTypeNames.DB_UTIL.packageMember(performFunctionName),
+ "return %M(%N, %L, %L) { %L ->",
+ RoomTypeNames.DB_UTIL.packageMember("performSuspending"),
dbProperty,
- sqlQueryVar,
- statementVar
+ true, // isReadOnly
+ inTransaction,
+ connectionVar
)
+ scope.builder.addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
bindStatement(scope, statementVar)
val outVar = scope.getTmpVar("_result")
adapter?.convert(outVar, statementVar, scope)
addStatement("%L", outVar)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
endControlFlow()
}
}
-
- private fun getPerformFunctionName(inTransaction: Boolean) =
- if (inTransaction) {
- "performReadTransactionSuspending"
- } else {
- "performReadSuspending"
- }
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
index 67e2337..d947e00 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
@@ -17,7 +17,7 @@
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
import androidx.room.compiler.codegen.XPropertySpec
import androidx.room.compiler.codegen.XTypeName
@@ -109,26 +109,40 @@
inTransaction: Boolean,
scope: CodeGenScope
) {
- val performFunctionName = getPerformFunctionName(inTransaction)
+ val connectionVar = scope.getTmpVar("_connection")
val statementVar = scope.getTmpVar("_stmt")
scope.builder.addStatement(
- "return %M(%N, %L, %L)",
- RoomTypeNames.DB_UTIL.packageMember(performFunctionName),
+ "return %M(%N, %L, %L, %L)",
+ RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
dbProperty,
- sqlQueryVar,
+ true, // isReadOnly
+ inTransaction,
// TODO(b/322387497): Generate lambda syntax if possible
Function1TypeSpec(
language = scope.language,
- parameterTypeName = SQLiteDriverTypeNames.STATEMENT,
- parameterName = statementVar,
+ parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+ parameterName = connectionVar,
returnTypeName = returnTypeName.box()
) {
val functionScope = scope.fork()
- bindStatement(functionScope, statementVar)
val outVar = functionScope.getTmpVar("_result")
- adapter?.convert(outVar, statementVar, functionScope)
- this.addCode(functionScope.generate())
- this.addStatement("return %L", outVar)
+ val functionCode = functionScope.builder.apply {
+ addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
+ bindStatement(functionScope, statementVar)
+ adapter?.convert(outVar, statementVar, functionScope)
+ addStatement("return %L", outVar)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
+ }.build()
+ this.addCode(functionCode)
}
)
}
@@ -140,28 +154,33 @@
inTransaction: Boolean,
scope: CodeGenScope
) {
+ val connectionVar = scope.getTmpVar("_connection")
val statementVar = scope.getTmpVar("_stmt")
- val performFunctionName = getPerformFunctionName(inTransaction)
scope.builder.apply {
beginControlFlow(
- "return %M(%N, %L) { %L ->",
- RoomTypeNames.DB_UTIL.packageMember(performFunctionName),
+ "return %M(%N, %L, %L) { %L ->",
+ RoomTypeNames.DB_UTIL.packageMember("performBlocking"),
dbProperty,
- sqlQueryVar,
- statementVar
+ true, // isReadOnly
+ inTransaction,
+ connectionVar
)
+ addLocalVal(
+ statementVar,
+ SQLiteDriverTypeNames.STATEMENT,
+ "%L.prepare(%L)",
+ connectionVar,
+ sqlQueryVar
+ )
+ beginControlFlow("try")
bindStatement(scope, statementVar)
val outVar = scope.getTmpVar("_result")
adapter?.convert(outVar, statementVar, scope)
addStatement("%L", outVar)
+ nextControlFlow("finally")
+ addStatement("%L.close()", statementVar)
+ endControlFlow()
endControlFlow()
}
}
-
- private fun getPerformFunctionName(inTransaction: Boolean) =
- if (inTransaction) {
- "performReadTransactionBlocking"
- } else {
- "performReadBlocking"
- }
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt
index df6d058..1644252 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt
@@ -16,10 +16,13 @@
package androidx.room.solver.query.result
+import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
import androidx.room.compiler.processing.XType
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.CommonTypeNames.ARRAY_LIST
+import androidx.room.ext.KotlinCollectionMemberNames
import androidx.room.solver.CodeGenScope
class ListQueryResultAdapter(
@@ -30,18 +33,25 @@
scope.builder.apply {
rowAdapter.onCursorReady(cursorVarName = cursorVarName, scope = scope)
val listTypeName = CommonTypeNames.MUTABLE_LIST.parametrizedBy(typeArg.asTypeName())
- addLocalVariable(
- name = outVarName,
- typeName = listTypeName,
- assignExpr = XCodeBlock.ofNewInstance(
- language,
- ARRAY_LIST.parametrizedBy(typeArg.asTypeName()),
- "%L.getCount()",
- cursorVarName
+ when (language) {
+ CodeLanguage.JAVA -> addLocalVariable(
+ name = outVarName,
+ typeName = listTypeName,
+ assignExpr = XCodeBlock.ofNewInstance(
+ language,
+ ARRAY_LIST.parametrizedBy(typeArg.asTypeName())
+ )
)
- )
+ CodeLanguage.KOTLIN -> addLocalVal(
+ outVarName,
+ listTypeName,
+ "%M()",
+ KotlinCollectionMemberNames.MUTABLE_LIST_OF
+ )
+ }
val tmpVarName = scope.getTmpVar("_item")
- beginControlFlow("while (%L.moveToNext())", cursorVarName).apply {
+ val stepName = if (scope.useDriverApi) "step" else "moveToNext"
+ beginControlFlow("while (%L.$stepName())", cursorVarName).apply {
addLocalVariable(
name = tmpVarName,
typeName = typeArg.asTypeName()
@@ -52,4 +62,6 @@
endControlFlow()
}
}
+
+ override fun isMigratedToDriver(): Boolean = rowAdapter.isMigratedToDriver()
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
index 7ed2495..bbc6585 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
@@ -121,7 +121,7 @@
}
addStatement(
"default: throw new %T(%S + %L)",
- ExceptionTypeNames.ILLEGAL_ARG_EXCEPTION,
+ ExceptionTypeNames.JAVA_ILLEGAL_ARG_EXCEPTION,
ENUM_TO_STRING_ERROR_MSG,
paramName
)
@@ -186,7 +186,7 @@
}
addStatement(
"default: throw new %T(%S + %L)",
- ExceptionTypeNames.ILLEGAL_ARG_EXCEPTION,
+ ExceptionTypeNames.JAVA_ILLEGAL_ARG_EXCEPTION,
STRING_TO_ENUM_ERROR_MSG,
paramName
)
@@ -205,7 +205,7 @@
}
addStatement(
"else -> throw %T(%S + %L)",
- ExceptionTypeNames.ILLEGAL_ARG_EXCEPTION,
+ ExceptionTypeNames.KOTLIN_ILLEGAL_ARG_EXCEPTION,
STRING_TO_ENUM_ERROR_MSG,
paramName
)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/NullAwareTypeConverters.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/NullAwareTypeConverters.kt
index 03c641c..a3e4f91 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/NullAwareTypeConverters.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/NullAwareTypeConverters.kt
@@ -93,6 +93,10 @@
private fun XCodeBlock.Builder.addIllegalStateException() {
val typeName = from.asTypeName().copy(nullable = false).toString(language)
+ val exceptionClassName = when (language) {
+ CodeLanguage.JAVA -> ExceptionTypeNames.JAVA_ILLEGAL_STATE_EXCEPTION
+ CodeLanguage.KOTLIN -> ExceptionTypeNames.KOTLIN_ILLEGAL_STATE_EXCEPTION
+ }
val message = "Expected NON-NULL '$typeName', but it was NULL."
when (language) {
CodeLanguage.JAVA -> {
@@ -100,7 +104,7 @@
"throw %L",
XCodeBlock.ofNewInstance(
language,
- ExceptionTypeNames.ILLEGAL_STATE_EXCEPTION,
+ exceptionClassName,
"%S",
message
)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/Dao.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/Dao.kt
index d35a3a1..0f0d8b6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/Dao.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/Dao.kt
@@ -26,10 +26,10 @@
val type: XType,
val queryMethods: List<QueryMethod>,
val rawQueryMethods: List<RawQueryMethod>,
- val insertionMethods: List<InsertionMethod>,
- val deletionMethods: List<DeletionMethod>,
+ val insertMethods: List<InsertMethod>,
+ val deleteMethods: List<DeleteMethod>,
val updateMethods: List<UpdateMethod>,
- val upsertionMethods: List<UpsertionMethod>,
+ val upsertMethods: List<UpsertMethod>,
val transactionMethods: List<TransactionMethod>,
val kotlinBoxedPrimitiveMethodDelegates: List<KotlinBoxedPrimitiveMethodDelegate>,
val kotlinDefaultMethodDelegates: List<KotlinDefaultMethodDelegate>,
@@ -47,11 +47,11 @@
val typeName: XClassName by lazy { element.asClassName() }
val deleteOrUpdateShortcutMethods: List<DeleteOrUpdateShortcutMethod> by lazy {
- deletionMethods + updateMethods
+ deleteMethods + updateMethods
}
val insertOrUpsertShortcutMethods: List<InsertOrUpsertShortcutMethod> by lazy {
- insertionMethods + upsertionMethods
+ insertMethods + upsertMethods
}
val implTypeName: XClassName by lazy {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/DeletionMethod.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/DeleteMethod.kt
similarity index 97%
rename from room/room-compiler/src/main/kotlin/androidx/room/vo/DeletionMethod.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/vo/DeleteMethod.kt
index 1973273..7e69f5f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/DeletionMethod.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/DeleteMethod.kt
@@ -18,7 +18,7 @@
import androidx.room.compiler.processing.XMethodElement
import androidx.room.solver.shortcut.binder.DeleteOrUpdateMethodBinder
-class DeletionMethod(
+class DeleteMethod(
element: XMethodElement,
entities: Map<String, ShortcutEntity>,
parameters: List<ShortcutQueryParameter>,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/InsertionMethod.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/InsertMethod.kt
similarity index 97%
rename from room/room-compiler/src/main/kotlin/androidx/room/vo/InsertionMethod.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/vo/InsertMethod.kt
index 9e5cbca3..c97af3d 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/InsertionMethod.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/InsertMethod.kt
@@ -21,7 +21,7 @@
import androidx.room.compiler.processing.XType
import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
-class InsertionMethod(
+class InsertMethod(
element: XMethodElement,
@OnConflictStrategy val onConflict: Int,
entities: Map<String, ShortcutEntity>,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/UpsertionMethod.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/UpsertMethod.kt
similarity index 97%
rename from room/room-compiler/src/main/kotlin/androidx/room/vo/UpsertionMethod.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/vo/UpsertMethod.kt
index 4ba44f3..3aa49b1 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/UpsertionMethod.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/UpsertMethod.kt
@@ -20,7 +20,7 @@
import androidx.room.compiler.processing.XType
import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
-class UpsertionMethod(
+class UpsertMethod(
element: XMethodElement,
entities: Map<String, ShortcutEntity>,
returnType: XType,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
index 2f9c7e5..ec6e71e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -34,12 +34,10 @@
import androidx.room.ext.RoomMemberNames
import androidx.room.ext.RoomTypeNames
import androidx.room.ext.RoomTypeNames.DELETE_OR_UPDATE_ADAPTER
-import androidx.room.ext.RoomTypeNames.INSERTION_ADAPTER
+import androidx.room.ext.RoomTypeNames.INSERT_ADAPTER
import androidx.room.ext.RoomTypeNames.ROOM_DB
-import androidx.room.ext.RoomTypeNames.SHARED_SQLITE_STMT
-import androidx.room.ext.RoomTypeNames.UPSERTION_ADAPTER
+import androidx.room.ext.RoomTypeNames.UPSERT_ADAPTER
import androidx.room.ext.SupportDbTypeNames
-import androidx.room.ext.capitalize
import androidx.room.processor.OnConflictProcessor
import androidx.room.solver.CodeGenScope
import androidx.room.solver.KotlinBoxedPrimitiveMethodDelegateBinder
@@ -47,18 +45,16 @@
import androidx.room.solver.types.getRequiredTypeConverters
import androidx.room.vo.Dao
import androidx.room.vo.DeleteOrUpdateShortcutMethod
-import androidx.room.vo.InsertionMethod
+import androidx.room.vo.InsertMethod
import androidx.room.vo.KotlinBoxedPrimitiveMethodDelegate
import androidx.room.vo.KotlinDefaultMethodDelegate
-import androidx.room.vo.QueryMethod
import androidx.room.vo.RawQueryMethod
import androidx.room.vo.ReadQueryMethod
import androidx.room.vo.ShortcutEntity
import androidx.room.vo.TransactionMethod
import androidx.room.vo.UpdateMethod
-import androidx.room.vo.UpsertionMethod
+import androidx.room.vo.UpsertMethod
import androidx.room.vo.WriteQueryMethod
-import java.util.Locale
/**
* Creates the implementation for a class annotated with Dao.
@@ -100,25 +96,13 @@
override fun createTypeSpecBuilder(): XTypeSpec.Builder {
val builder = XTypeSpec.classBuilder(codeLanguage, dao.implTypeName)
- /**
- * For prepared statements that perform insert/update/delete/upsert,
- * we check if there are any arguments of variable length (e.g. "IN (:var)").
- * If not, we should re-use the statement.
- * This requires more work but creates good performance.
- */
- val groupedPreparedQueries = dao.queryMethods
- .filterIsInstance<WriteQueryMethod>()
- .groupBy { it.parameters.any { it.queryParamAdapter?.isMultiple ?: true } }
- // queries that can be prepared ahead of time
- val preparedQueries = groupedPreparedQueries[false] ?: emptyList()
- // queries that must be rebuilt every single time
- val oneOffPreparedQueries = groupedPreparedQueries[true] ?: emptyList()
+ val preparedQueries = dao.queryMethods.filterIsInstance<WriteQueryMethod>()
+
val shortcutMethods = buildList {
- addAll(createInsertionMethods())
- addAll(createDeletionMethods())
+ addAll(createInsertMethods())
+ addAll(createDeleteMethods())
addAll(createUpdateMethods())
addAll(createTransactionMethods())
- addAll(createPreparedQueries(preparedQueries))
addAll(createUpsertMethods())
}
@@ -148,11 +132,10 @@
shortcutMethods.forEach {
addFunction(it.functionImpl)
}
-
dao.queryMethods.filterIsInstance<ReadQueryMethod>().forEach { method ->
addFunction(createSelectMethod(method))
}
- oneOffPreparedQueries.forEach {
+ preparedQueries.forEach {
addFunction(createPreparedQueryMethod(it))
}
dao.rawQueryMethods.forEach {
@@ -243,50 +226,6 @@
}.build()
}
- private fun createPreparedQueries(
- preparedQueries: List<WriteQueryMethod>
- ): List<PreparedStmtQuery> {
- return preparedQueries.map { method ->
- val fieldSpec = getOrCreateProperty(PreparedStatementProperty(method))
- val queryWriter = QueryWriter(method)
- val fieldImpl = PreparedStatementWriter(queryWriter)
- .createAnonymous(this@DaoWriter, dbProperty)
- val methodBody =
- createPreparedQueryMethodBody(method, fieldSpec, queryWriter)
- PreparedStmtQuery(
- mapOf(PreparedStmtQuery.NO_PARAM_FIELD to (fieldSpec to fieldImpl)),
- methodBody
- )
- }
- }
-
- private fun createPreparedQueryMethodBody(
- method: WriteQueryMethod,
- preparedStmtField: XPropertySpec,
- queryWriter: QueryWriter
- ): XFunSpec {
- val scope = CodeGenScope(this)
- method.preparedQueryResultBinder.executeAndReturn(
- prepareQueryStmtBlock = {
- val stmtName = getTmpVar("_stmt")
- builder.addLocalVal(
- stmtName,
- SupportDbTypeNames.SQLITE_STMT,
- "%N.acquire()",
- preparedStmtField
- )
- queryWriter.bindArgs(stmtName, emptyList(), this)
- stmtName
- },
- preparedStmtProperty = preparedStmtField,
- dbProperty = dbProperty,
- scope = scope
- )
- return overrideWithoutAnnotations(method.element, declaredDao)
- .addCode(scope.generate())
- .build()
- }
-
private fun createTransactionMethods(): List<PreparedStmtQuery> {
return dao.transactionMethods.map {
PreparedStmtQuery(emptyMap(), createTransactionMethodBody(it))
@@ -406,42 +345,42 @@
}
/**
- * Groups all insertion methods based on the insert statement they will use then creates all
- * field specs, EntityInsertionAdapterWriter and actual insert methods.
+ * Groups all insert methods based on the insert statement they will use then creates all
+ * field specs, EntityInsertAdapterWriter and actual insert methods.
*/
- private fun createInsertionMethods(): List<PreparedStmtQuery> {
- return dao.insertionMethods
- .map { insertionMethod ->
- val onConflict = OnConflictProcessor.onConflictText(insertionMethod.onConflict)
- val entities = insertionMethod.entities
+ private fun createInsertMethods(): List<PreparedStmtQuery> {
+ return dao.insertMethods
+ .map { insertMethod ->
+ val onConflict = OnConflictProcessor.onConflictText(insertMethod.onConflict)
+ val entities = insertMethod.entities
val fields = entities.mapValues {
- val spec = getOrCreateProperty(InsertionMethodProperty(it.value, onConflict))
- val impl = EntityInsertionAdapterWriter.create(it.value, onConflict)
+ val spec = getOrCreateProperty(InsertMethodProperty(it.value, onConflict))
+ val impl = EntityInsertAdapterWriter.create(it.value, onConflict)
.createAnonymous(this@DaoWriter, dbProperty)
spec to impl
}
val methodImpl = overrideWithoutAnnotations(
- insertionMethod.element,
+ insertMethod.element,
declaredDao
).apply {
- addCode(createInsertionMethodBody(insertionMethod, fields))
+ addCode(createInsertMethodBody(insertMethod, fields))
}.build()
PreparedStmtQuery(fields, methodImpl)
}
}
- private fun createInsertionMethodBody(
- method: InsertionMethod,
- insertionAdapters: Map<String, Pair<XPropertySpec, XTypeSpec>>
+ private fun createInsertMethodBody(
+ method: InsertMethod,
+ insertAdapters: Map<String, Pair<XPropertySpec, XTypeSpec>>
): XCodeBlock {
- if (insertionAdapters.isEmpty() || method.methodBinder == null) {
+ if (insertAdapters.isEmpty() || method.methodBinder == null) {
return XCodeBlock.builder(codeLanguage).build()
}
val scope = CodeGenScope(this)
method.methodBinder.convertAndReturn(
parameters = method.parameters,
- adapters = insertionAdapters,
+ adapters = insertAdapters,
dbProperty = dbProperty,
scope = scope
)
@@ -449,11 +388,11 @@
}
/**
- * Creates EntityUpdateAdapter for each deletion method.
+ * Creates EntityUpdateAdapter for each delete method.
*/
- private fun createDeletionMethods(): List<PreparedStmtQuery> {
- return createShortcutMethods(dao.deletionMethods, "deletion") { _, entity ->
- EntityDeletionAdapterWriter.create(entity)
+ private fun createDeleteMethods(): List<PreparedStmtQuery> {
+ return createShortcutMethods(dao.deleteMethods, "delete") { _, entity ->
+ EntityDeleteAdapterWriter.create(entity)
.createAnonymous(this@DaoWriter, dbProperty.name)
}
}
@@ -518,41 +457,41 @@
}
/**
- * Groups all upsertion methods based on the upsert statement they will use then creates all
- * field specs, EntityIUpsertionAdapterWriter and actual upsert methods.
+ * Groups all upsert methods based on the upsert statement they will use then creates all
+ * field specs, EntityUpsertAdapterWriter and actual upsert methods.
*/
private fun createUpsertMethods(): List<PreparedStmtQuery> {
- return dao.upsertionMethods
- .map { upsertionMethod ->
- val entities = upsertionMethod.entities
+ return dao.upsertMethods
+ .map { upsertMethod ->
+ val entities = upsertMethod.entities
val fields = entities.mapValues {
- val spec = getOrCreateProperty(UpsertionAdapterProperty(it.value))
- val impl = EntityUpsertionAdapterWriter.create(it.value)
+ val spec = getOrCreateProperty(UpsertAdapterProperty(it.value))
+ val impl = EntityUpsertAdapterWriter.create(it.value)
.createConcrete(it.value, this@DaoWriter, dbProperty)
spec to impl
}
val methodImpl = overrideWithoutAnnotations(
- upsertionMethod.element,
+ upsertMethod.element,
declaredDao
).apply {
- addCode(createUpsertionMethodBody(upsertionMethod, fields))
+ addCode(createUpsertMethodBody(upsertMethod, fields))
}.build()
PreparedStmtQuery(fields, methodImpl)
}
}
- private fun createUpsertionMethodBody(
- method: UpsertionMethod,
- upsertionAdapters: Map<String, Pair<XPropertySpec, XCodeBlock>>
+ private fun createUpsertMethodBody(
+ method: UpsertMethod,
+ upsertAdapters: Map<String, Pair<XPropertySpec, XCodeBlock>>
): XCodeBlock {
- if (upsertionAdapters.isEmpty() || method.methodBinder == null) {
+ if (upsertAdapters.isEmpty() || method.methodBinder == null) {
return XCodeBlock.builder(codeLanguage).build()
}
val scope = CodeGenScope(this)
method.methodBinder.convertAndReturn(
parameters = method.parameters,
- adapters = upsertionAdapters,
+ adapters = upsertAdapters,
dbProperty = dbProperty,
scope = scope
)
@@ -560,6 +499,25 @@
}
private fun createPreparedQueryMethodBody(method: WriteQueryMethod): XCodeBlock {
+ if (!method.preparedQueryResultBinder.isMigratedToDriver()) {
+ return compatCreatePreparedQueryMethodBody(method)
+ }
+
+ val scope = CodeGenScope(this, useDriverApi = true)
+ val queryWriter = QueryWriter(method)
+ val sqlVar = scope.getTmpVar("_sql")
+ val listSizeArgs = queryWriter.prepareQuery(sqlVar, scope)
+ method.preparedQueryResultBinder.executeAndReturn(
+ sqlQueryVar = sqlVar,
+ dbProperty = dbProperty,
+ bindStatement = { stmtVar -> queryWriter.bindArgs(stmtVar, listSizeArgs, this) },
+ returnTypeName = method.returnType.asTypeName(),
+ scope = scope
+ )
+ return scope.generate()
+ }
+
+ private fun compatCreatePreparedQueryMethodBody(method: WriteQueryMethod): XCodeBlock {
val scope = CodeGenScope(this)
method.preparedQueryResultBinder.executeAndReturn(
prepareQueryStmtBlock = {
@@ -681,12 +639,12 @@
}
}
- private class InsertionMethodProperty(
+ private class InsertMethodProperty(
val shortcutEntity: ShortcutEntity,
val onConflictText: String
) : SharedPropertySpec(
- baseName = "insertionAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}",
- type = INSERTION_ADAPTER.parametrizedBy(shortcutEntity.pojo.typeName)
+ baseName = "insertAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}",
+ type = INSERT_ADAPTER.parametrizedBy(shortcutEntity.pojo.typeName)
) {
override fun getUniqueKey(): String {
return "${shortcutEntity.pojo.typeName}-${shortcutEntity.entityTypeName}$onConflictText"
@@ -713,11 +671,11 @@
}
}
- class UpsertionAdapterProperty(
+ class UpsertAdapterProperty(
val shortcutEntity: ShortcutEntity
) : SharedPropertySpec(
- baseName = "upsertionAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}",
- type = UPSERTION_ADAPTER.parametrizedBy(shortcutEntity.pojo.typeName)
+ baseName = "upsertAdapterOf${shortcutEntityFieldNamePart(shortcutEntity)}",
+ type = UPSERT_ADAPTER.parametrizedBy(shortcutEntity.pojo.typeName)
) {
override fun getUniqueKey(): String {
return "${shortcutEntity.pojo.typeName}-${shortcutEntity.entityTypeName}"
@@ -726,16 +684,4 @@
override fun prepare(writer: TypeWriter, builder: XPropertySpec.Builder) {
}
}
-
- class PreparedStatementProperty(val method: QueryMethod) : SharedPropertySpec(
- baseName = "preparedStmtOf${method.element.name.capitalize(Locale.US)}",
- type = SHARED_SQLITE_STMT
- ) {
- override fun prepare(writer: TypeWriter, builder: XPropertySpec.Builder) {
- }
-
- override fun getUniqueKey(): String {
- return method.query.original
- }
- }
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeletionAdapterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeleteAdapterWriter.kt
similarity index 95%
rename from room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeletionAdapterWriter.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeleteAdapterWriter.kt
index 204bc48..33eacfa 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeletionAdapterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeleteAdapterWriter.kt
@@ -29,13 +29,13 @@
import androidx.room.vo.Fields
import androidx.room.vo.ShortcutEntity
-class EntityDeletionAdapterWriter private constructor(
+class EntityDeleteAdapterWriter private constructor(
val tableName: String,
val pojoTypeName: XTypeName,
val fields: Fields
) {
companion object {
- fun create(entity: ShortcutEntity): EntityDeletionAdapterWriter {
+ fun create(entity: ShortcutEntity): EntityDeleteAdapterWriter {
val fieldsToUse = if (entity.isPartialEntity) {
// When using partial entity, delete by values in pojo
entity.pojo.fields
@@ -43,7 +43,7 @@
// When using entity, delete by primary key
entity.primaryKey.fields
}
- return EntityDeletionAdapterWriter(
+ return EntityDeleteAdapterWriter(
tableName = entity.tableName,
pojoTypeName = entity.pojo.typeName,
fields = fieldsToUse
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertAdapterWriter.kt
similarity index 95%
rename from room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertAdapterWriter.kt
index 3a6d684..77c59b9 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertAdapterWriter.kt
@@ -32,14 +32,14 @@
import androidx.room.vo.ShortcutEntity
import androidx.room.vo.columnNames
-class EntityInsertionAdapterWriter private constructor(
+class EntityInsertAdapterWriter private constructor(
val tableName: String,
val pojo: Pojo,
val primitiveAutoGenerateColumn: String?,
val onConflict: String
) {
companion object {
- fun create(entity: ShortcutEntity, onConflict: String): EntityInsertionAdapterWriter {
+ fun create(entity: ShortcutEntity, onConflict: String): EntityInsertAdapterWriter {
// If there is an auto-increment primary key with primitive type, we consider 0 as
// not set. For such fields, we must generate a slightly different insertion SQL.
val primitiveAutoGenerateField = if (entity.primaryKey.autoGenerateId) {
@@ -55,7 +55,7 @@
} else {
null
}
- return EntityInsertionAdapterWriter(
+ return EntityInsertAdapterWriter(
tableName = entity.tableName,
pojo = entity.pojo,
primitiveAutoGenerateColumn = primitiveAutoGenerateField?.columnName,
@@ -69,7 +69,7 @@
typeWriter.codeLanguage, "%N", dbProperty
).apply {
superclass(
- RoomTypeNames.INSERTION_ADAPTER.parametrizedBy(pojo.typeName)
+ RoomTypeNames.INSERT_ADAPTER.parametrizedBy(pojo.typeName)
)
addFunction(
XFunSpec.builder(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpsertionAdapterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpsertAdapterWriter.kt
similarity index 76%
rename from room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpsertionAdapterWriter.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpsertAdapterWriter.kt
index d650a63..4c5609c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpsertionAdapterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpsertAdapterWriter.kt
@@ -22,13 +22,13 @@
import androidx.room.vo.Pojo
import androidx.room.vo.ShortcutEntity
-class EntityUpsertionAdapterWriter private constructor(
+class EntityUpsertAdapterWriter private constructor(
val tableName: String,
val pojo: Pojo
) {
companion object {
- fun create(entity: ShortcutEntity): EntityUpsertionAdapterWriter {
- return EntityUpsertionAdapterWriter(
+ fun create(entity: ShortcutEntity): EntityUpsertAdapterWriter {
+ return EntityUpsertAdapterWriter(
tableName = entity.tableName,
pojo = entity.pojo
)
@@ -40,16 +40,16 @@
typeWriter: TypeWriter,
dbProperty: XPropertySpec
): XCodeBlock {
- val upsertionAdapter = RoomTypeNames.UPSERTION_ADAPTER.parametrizedBy(pojo.typeName)
- val insertionHelper = EntityInsertionAdapterWriter.create(entity, "")
+ val upsertAdapter = RoomTypeNames.UPSERT_ADAPTER.parametrizedBy(pojo.typeName)
+ val insertHelper = EntityInsertAdapterWriter.create(entity, "")
.createAnonymous(typeWriter, dbProperty)
val updateHelper = EntityUpdateAdapterWriter.create(entity, "")
.createAnonymous(typeWriter, dbProperty.name)
return XCodeBlock.ofNewInstance(
language = typeWriter.codeLanguage,
- typeName = upsertionAdapter,
+ typeName = upsertAdapter,
argsFormat = "%L, %L",
- args = arrayOf(insertionHelper, updateHelper)
+ args = arrayOf(insertHelper, updateHelper)
)
}
}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt
index bfc0817..4cbdba5 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt
@@ -16,11 +16,13 @@
package androidx.room.writer
+import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
import androidx.room.compiler.codegen.XTypeName
import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.KotlinTypeNames
import androidx.room.ext.RoomMemberNames
import androidx.room.ext.RoomTypeNames
import androidx.room.parser.ParsedQuery
@@ -74,11 +76,14 @@
scope.builder.apply {
if (varargParams.isNotEmpty()) {
val stringBuilderVar = scope.getTmpVar("_stringBuilder")
- addLocalVal(
- stringBuilderVar,
- CommonTypeNames.STRING_BUILDER,
- "%M()",
- RoomTypeNames.STRING_UTIL.packageMember("newStringBuilder")
+ val stringBuilderTypeName = when (language) {
+ CodeLanguage.JAVA -> CommonTypeNames.STRING_BUILDER
+ CodeLanguage.KOTLIN -> KotlinTypeNames.STRING_BUILDER
+ }
+ addLocalVariable(
+ name = stringBuilderVar,
+ typeName = stringBuilderTypeName,
+ assignExpr = XCodeBlock.ofNewInstance(language, stringBuilderTypeName)
)
query.sections.forEach { section ->
when (section) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
index 50cacb8..2f432e3 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
@@ -26,7 +26,7 @@
void insertMe(T t);
"""
) { dao ->
- assertThat(dao.insertionMethods.size, `is`(1))
+ assertThat(dao.insertMethods.size, `is`(1))
}
}
@@ -38,7 +38,7 @@
void insertMe(T[] t);
"""
) { dao ->
- assertThat(dao.insertionMethods.size, `is`(1))
+ assertThat(dao.insertMethods.size, `is`(1))
}
}
@@ -50,7 +50,7 @@
void insertMe(T... t);
"""
) { dao ->
- assertThat(dao.insertionMethods.size, `is`(1))
+ assertThat(dao.insertMethods.size, `is`(1))
}
}
@@ -62,7 +62,7 @@
void insertMe(List<T> t);
"""
) { dao ->
- assertThat(dao.insertionMethods.size, `is`(1))
+ assertThat(dao.insertMethods.size, `is`(1))
}
}
@@ -74,7 +74,7 @@
void deleteMe(T t);
"""
) { dao ->
- assertThat(dao.deletionMethods.size, `is`(1))
+ assertThat(dao.deleteMethods.size, `is`(1))
}
}
@@ -86,7 +86,7 @@
void deleteMe(T[] t);
"""
) { dao ->
- assertThat(dao.deletionMethods.size, `is`(1))
+ assertThat(dao.deleteMethods.size, `is`(1))
}
}
@@ -98,7 +98,7 @@
void deleteMe(T... t);
"""
) { dao ->
- assertThat(dao.deletionMethods.size, `is`(1))
+ assertThat(dao.deleteMethods.size, `is`(1))
}
}
@@ -110,7 +110,7 @@
void deleteMe(List<T> t);
"""
) { dao ->
- assertThat(dao.deletionMethods.size, `is`(1))
+ assertThat(dao.deleteMethods.size, `is`(1))
}
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
index dd5133d..2758547 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
@@ -192,8 +192,8 @@
assertThat(dao.queryMethods.size, `is`(1))
val method = dao.queryMethods.first()
assertThat(method.element.jvmName, `is`("getIds"))
- assertThat(dao.insertionMethods.size, `is`(1))
- val insertMethod = dao.insertionMethods.first()
+ assertThat(dao.insertMethods.size, `is`(1))
+ val insertMethod = dao.insertMethods.first()
assertThat(insertMethod.element.jvmName, `is`("insert"))
}
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index e49d439..d8fde6c 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -880,7 +880,7 @@
USER, USER_DAO
) { db, _ ->
val userDao = db.daoMethods.first().dao
- val insertionMethod = userDao.insertionMethods.find { it.element.jvmName == "insert" }
+ val insertionMethod = userDao.insertMethods.find { it.element.jvmName == "insert" }
assertThat(insertionMethod, notNullValue())
val loadOne = userDao.queryMethods
.filterIsInstance<ReadQueryMethod>()
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeletionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteMethodProcessorTest.kt
similarity index 75%
rename from room/room-compiler/src/test/kotlin/androidx/room/processor/DeletionMethodProcessorTest.kt
rename to room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteMethodProcessorTest.kt
index f7184dd..0a78781 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeletionMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteMethodProcessorTest.kt
@@ -19,24 +19,24 @@
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XType
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_DELETE_RESULT_ADAPTER
-import androidx.room.processor.ProcessorErrors.DELETION_MISSING_PARAMS
-import androidx.room.vo.DeletionMethod
+import androidx.room.processor.ProcessorErrors.DELETE_MISSING_PARAMS
+import androidx.room.vo.DeleteMethod
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@RunWith(JUnit4::class)
-class DeletionMethodProcessorTest :
- DeleteOrUpdateShortcutMethodProcessorTest<DeletionMethod>(Delete::class) {
+class DeleteMethodProcessorTest :
+ DeleteOrUpdateShortcutMethodProcessorTest<DeleteMethod>(Delete::class) {
override fun invalidReturnTypeError(): String = CANNOT_FIND_DELETE_RESULT_ADAPTER
- override fun noParamsError(): String = DELETION_MISSING_PARAMS
+ override fun noParamsError(): String = DELETE_MISSING_PARAMS
override fun process(
baseContext: Context,
containing: XType,
executableElement: XMethodElement
- ): DeletionMethod {
- return DeletionMethodProcessor(baseContext, containing, executableElement).process()
+ ): DeleteMethod {
+ return DeleteMethodProcessor(baseContext, containing, executableElement).process()
}
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertMethodProcessorTest.kt
similarity index 87%
rename from room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt
rename to room/room-compiler/src/test/kotlin/androidx/room/processor/InsertMethodProcessorTest.kt
index 3550ce5..25e81d81 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertMethodProcessorTest.kt
@@ -22,19 +22,19 @@
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XType
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_INSERT_RESULT_ADAPTER
-import androidx.room.processor.ProcessorErrors.INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
+import androidx.room.processor.ProcessorErrors.INSERT_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
import androidx.room.processor.ProcessorErrors.INSERT_MULTI_PARAM_SINGLE_RETURN_MISMATCH
import androidx.room.processor.ProcessorErrors.INSERT_SINGLE_PARAM_MULTI_RETURN_MISMATCH
-import androidx.room.vo.InsertionMethod
+import androidx.room.vo.InsertMethod
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@RunWith(JUnit4::class)
-class InsertionMethodProcessorTest :
- InsertOrUpsertShortcutMethodProcessorTest<InsertionMethod>(Insert::class) {
- override fun noParamsError(): String = INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
+class InsertMethodProcessorTest :
+ InsertOrUpsertShortcutMethodProcessorTest<InsertMethod>(Insert::class) {
+ override fun noParamsError(): String = INSERT_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
override fun missingPrimaryKey(partialEntityName: String, primaryKeyName: List<String>):
String {
@@ -105,7 +105,7 @@
baseContext: Context,
containing: XType,
executableElement: XMethodElement
- ): InsertionMethod {
- return InsertionMethodProcessor(baseContext, containing, executableElement).process()
+ ): InsertMethod {
+ return InsertMethodProcessor(baseContext, containing, executableElement).process()
}
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertMethodProcessorTest.kt
similarity index 80%
rename from room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertionMethodProcessorTest.kt
rename to room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertMethodProcessorTest.kt
index 0dea532..a69e144 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertionMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/UpsertMethodProcessorTest.kt
@@ -20,18 +20,18 @@
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XType
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_UPSERT_RESULT_ADAPTER
-import androidx.room.processor.ProcessorErrors.UPSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT
+import androidx.room.processor.ProcessorErrors.UPSERT_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT
import androidx.room.processor.ProcessorErrors.UPSERT_MULTI_PARAM_SINGLE_RETURN_MISMATCH
import androidx.room.processor.ProcessorErrors.UPSERT_SINGLE_PARAM_MULTI_RETURN_MISMATCH
-import androidx.room.vo.UpsertionMethod
+import androidx.room.vo.UpsertMethod
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@RunWith(JUnit4::class)
-class UpsertionMethodProcessorTest :
- InsertOrUpsertShortcutMethodProcessorTest<UpsertionMethod>(Upsert::class) {
- override fun noParamsError(): String = UPSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT
+class UpsertMethodProcessorTest :
+ InsertOrUpsertShortcutMethodProcessorTest<UpsertMethod>(Upsert::class) {
+ override fun noParamsError(): String = UPSERT_DOES_NOT_HAVE_ANY_PARAMETERS_TO_UPSERT
override fun missingPrimaryKey(partialEntityName: String, primaryKeyName: List<String>):
String {
@@ -53,7 +53,7 @@
baseContext: Context,
containing: XType,
executableElement: XMethodElement
- ): UpsertionMethod {
- return UpsertionMethodProcessor(baseContext, containing, executableElement).process()
+ ): UpsertMethod {
+ return UpsertMethodProcessor(baseContext, containing, executableElement).process()
}
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
index 650b393..6ee375a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
@@ -136,7 +136,7 @@
writer.prepareReadAndBind("_sql", "_stmt", scope)
assertThat(scope.generate().toString().trim()).isEqualTo(
"""
- final java.lang.StringBuilder _stringBuilder = ${STRING_UTIL.canonicalName}.newStringBuilder();
+ final java.lang.StringBuilder _stringBuilder = new java.lang.StringBuilder();
_stringBuilder.append("SELECT id FROM users WHERE id IN(");
final int _inputSize = ids == null ? 1 : ids.length;
${STRING_UTIL.canonicalName}.appendPlaceholders(_stringBuilder, _inputSize);
@@ -162,7 +162,7 @@
}
val collectionOut = """
- final java.lang.StringBuilder _stringBuilder = ${STRING_UTIL.canonicalName}.newStringBuilder();
+ final java.lang.StringBuilder _stringBuilder = new java.lang.StringBuilder();
_stringBuilder.append("SELECT id FROM users WHERE id IN(");
final int _inputSize = ids == null ? 1 : ids.size();
${STRING_UTIL.canonicalName}.appendPlaceholders(_stringBuilder, _inputSize);
@@ -265,7 +265,7 @@
writer.prepareReadAndBind("_sql", "_stmt", scope)
assertThat(scope.generate().toString().trim()).isEqualTo(
"""
- final java.lang.StringBuilder _stringBuilder = ${STRING_UTIL.canonicalName}.newStringBuilder();
+ final java.lang.StringBuilder _stringBuilder = new java.lang.StringBuilder();
_stringBuilder.append("SELECT id FROM users WHERE age > ");
_stringBuilder.append("?");
_stringBuilder.append(" OR bage > ");
@@ -307,7 +307,7 @@
writer.prepareReadAndBind("_sql", "_stmt", scope)
assertThat(scope.generate().toString().trim()).isEqualTo(
"""
- final java.lang.StringBuilder _stringBuilder = ${STRING_UTIL.canonicalName}.newStringBuilder();
+ final java.lang.StringBuilder _stringBuilder = new java.lang.StringBuilder();
_stringBuilder.append("SELECT id FROM users WHERE age IN (");
final int _inputSize = ages == null ? 1 : ages.length;
${STRING_UTIL.canonicalName}.appendPlaceholders(_stringBuilder, _inputSize);
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
index 0950112..afdf910 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
@@ -908,6 +908,9 @@
@Query("DELETE FROM MyEntity")
fun deleteEntityReturnInt(): Int
+
+ @Query("DELETE FROM MyEntity WHERE id IN (:ids)")
+ fun deleteEntitiesIn(ids: List<Long>)
}
@Entity
@@ -2044,6 +2047,27 @@
@Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
suspend fun getSuspendList(vararg arg: String?): List<MyEntity>
+
+ @Query("INSERT INTO MyEntity (pk) VALUES (:pk)")
+ suspend fun insertEntity(pk: Long)
+
+ @Query("INSERT INTO MyEntity (pk) VALUES (:pk)")
+ suspend fun insertEntityReturnLong(pk: Long): Long
+
+ @Query("UPDATE MyEntity SET other = :text")
+ suspend fun updateEntity(text: String)
+
+ @Query("UPDATE MyEntity SET other = :text WHERE pk = :pk")
+ suspend fun updateEntityReturnInt(pk: Long, text: String): Int
+
+ @Query("DELETE FROM MyEntity")
+ suspend fun deleteEntity()
+
+ @Query("DELETE FROM MyEntity")
+ suspend fun deleteEntityReturnInt(): Int
+
+ @Query("DELETE FROM MyEntity WHERE pk IN (:pks)")
+ suspend fun deleteEntitiesIn(pks: List<Long>)
}
@Entity
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
index 6edd091..3cf7503 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
@@ -12,6 +12,7 @@
import androidx.room.util.DBUtil;
import androidx.room.util.SQLiteStatementUtil;
import androidx.room.util.StringUtil;
+import androidx.sqlite.SQLiteConnection;
import androidx.sqlite.SQLiteStatement;
import androidx.sqlite.db.SupportSQLiteQuery;
import com.google.common.util.concurrent.ListenableFuture;
@@ -55,67 +56,75 @@
@Override
public List<ComplexDao.FullName> fullNames(final int id) {
final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
- int _argIndex = 1;
- _statement.bindLong(_argIndex, id);
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfFullName = 0;
- final int _cursorIndexOfId = 1;
- final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>(_cursor.getCount());
- while (_cursor.moveToNext()) {
- final ComplexDao.FullName _item;
- _item = new ComplexDao.FullName();
- if (_cursor.isNull(_cursorIndexOfFullName)) {
- _item.fullName = null;
- } else {
- _item.fullName = _cursor.getString(_cursorIndexOfFullName);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<ComplexDao.FullName>>() {
+ @Override
+ @NonNull
+ public List<ComplexDao.FullName> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, id);
+ final int _cursorIndexOfFullName = 0;
+ final int _cursorIndexOfId = 1;
+ final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>();
+ while (_stmt.step()) {
+ final ComplexDao.FullName _item;
+ _item = new ComplexDao.FullName();
+ if (_stmt.isNull(_cursorIndexOfFullName)) {
+ _item.fullName = null;
+ } else {
+ _item.fullName = _stmt.getText(_cursorIndexOfFullName);
+ }
+ _item.id = (int) (_stmt.getLong(_cursorIndexOfId));
+ _result.add(_item);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- _item.id = _cursor.getInt(_cursorIndexOfId);
- _result.add(_item);
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
public User getById(final int id) {
final String _sql = "SELECT * FROM user where uid = ?";
- return DBUtil.performReadBlocking(__db, _sql, new Function1<SQLiteStatement, User>() {
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, User>() {
@Override
@NonNull
- public User invoke(@NonNull final SQLiteStatement _stmt) {
- int _argIndex = 1;
- _stmt.bindLong(_argIndex, id);
- final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
- final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
- final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
- final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
- final User _result;
- if (_stmt.step()) {
- _result = new User();
- _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
- if (_stmt.isNull(_cursorIndexOfName)) {
- _result.name = null;
+ public User invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, id);
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+ final User _result;
+ if (_stmt.step()) {
+ _result = new User();
+ _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ if (_stmt.isNull(_cursorIndexOfName)) {
+ _result.name = null;
+ } else {
+ _result.name = _stmt.getText(_cursorIndexOfName);
+ }
+ final String _tmpLastName;
+ if (_stmt.isNull(_cursorIndexOfLastName)) {
+ _tmpLastName = null;
+ } else {
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ }
+ _result.setLastName(_tmpLastName);
+ _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
} else {
- _result.name = _stmt.getText(_cursorIndexOfName);
+ _result = null;
}
- final String _tmpLastName;
- if (_stmt.isNull(_cursorIndexOfLastName)) {
- _tmpLastName = null;
- } else {
- _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
- }
- _result.setLastName(_tmpLastName);
- _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
- } else {
- _result = null;
+ return _result;
+ } finally {
+ _stmt.close();
}
- return _result;
}
});
}
@@ -123,102 +132,109 @@
@Override
public User findByName(final String name, final String lastName) {
final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
- return DBUtil.performReadBlocking(__db, _sql, new Function1<SQLiteStatement, User>() {
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, User>() {
@Override
@NonNull
- public User invoke(@NonNull final SQLiteStatement _stmt) {
- int _argIndex = 1;
- if (name == null) {
- _stmt.bindNull(_argIndex);
- } else {
- _stmt.bindText(_argIndex, name);
- }
- _argIndex = 2;
- if (lastName == null) {
- _stmt.bindNull(_argIndex);
- } else {
- _stmt.bindText(_argIndex, lastName);
- }
- final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
- final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
- final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
- final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
- final User _result;
- if (_stmt.step()) {
- _result = new User();
- _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
- if (_stmt.isNull(_cursorIndexOfName)) {
- _result.name = null;
+ public User invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (name == null) {
+ _stmt.bindNull(_argIndex);
} else {
- _result.name = _stmt.getText(_cursorIndexOfName);
+ _stmt.bindText(_argIndex, name);
}
- final String _tmpLastName;
- if (_stmt.isNull(_cursorIndexOfLastName)) {
- _tmpLastName = null;
+ _argIndex = 2;
+ if (lastName == null) {
+ _stmt.bindNull(_argIndex);
} else {
- _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ _stmt.bindText(_argIndex, lastName);
}
- _result.setLastName(_tmpLastName);
- _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
- } else {
- _result = null;
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+ final User _result;
+ if (_stmt.step()) {
+ _result = new User();
+ _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ if (_stmt.isNull(_cursorIndexOfName)) {
+ _result.name = null;
+ } else {
+ _result.name = _stmt.getText(_cursorIndexOfName);
+ }
+ final String _tmpLastName;
+ if (_stmt.isNull(_cursorIndexOfLastName)) {
+ _tmpLastName = null;
+ } else {
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ }
+ _result.setLastName(_tmpLastName);
+ _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+ } else {
+ _result = null;
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- return _result;
}
});
}
@Override
public List<User> loadAllByIds(final int... ids) {
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ final StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("SELECT * FROM user where uid IN (");
final int _inputSize = ids == null ? 1 : ids.length;
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
_stringBuilder.append(")");
final String _sql = _stringBuilder.toString();
- final int _argCount = 0 + _inputSize;
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
- int _argIndex = 1;
- if (ids == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (int _item : ids) {
- _statement.bindLong(_argIndex, _item);
- _argIndex++;
- }
- }
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
- final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
- final List<User> _result = new ArrayList<User>(_cursor.getCount());
- while (_cursor.moveToNext()) {
- final User _item_1;
- _item_1 = new User();
- _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
- if (_cursor.isNull(_cursorIndexOfName)) {
- _item_1.name = null;
- } else {
- _item_1.name = _cursor.getString(_cursorIndexOfName);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<User>>() {
+ @Override
+ @NonNull
+ public List<User> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (ids == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item : ids) {
+ _stmt.bindLong(_argIndex, _item);
+ _argIndex++;
+ }
+ }
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+ final List<User> _result = new ArrayList<User>();
+ while (_stmt.step()) {
+ final User _item_1;
+ _item_1 = new User();
+ _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ if (_stmt.isNull(_cursorIndexOfName)) {
+ _item_1.name = null;
+ } else {
+ _item_1.name = _stmt.getText(_cursorIndexOfName);
+ }
+ final String _tmpLastName;
+ if (_stmt.isNull(_cursorIndexOfLastName)) {
+ _tmpLastName = null;
+ } else {
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ }
+ _item_1.setLastName(_tmpLastName);
+ _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+ _result.add(_item_1);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- final String _tmpLastName;
- if (_cursor.isNull(_cursorIndexOfLastName)) {
- _tmpLastName = null;
- } else {
- _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
- }
- _item_1.setLastName(_tmpLastName);
- _item_1.age = _cursor.getInt(_cursorIndexOfAge);
- _result.add(_item_1);
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
@@ -245,7 +261,7 @@
@Override
public int[] getAllAges(final int... ids) {
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ final StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
final int _inputSize = ids == null ? 1 : ids.length;
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
@@ -283,7 +299,7 @@
@Override
public List<Integer> getAllAgesAsList(final List<Integer> ids) {
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ final StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
final int _inputSize = ids == null ? 1 : ids.size();
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
@@ -307,7 +323,7 @@
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
- final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
+ final List<Integer> _result = new ArrayList<Integer>();
while (_cursor.moveToNext()) {
final Integer _item_1;
if (_cursor.isNull(0)) {
@@ -327,7 +343,7 @@
@Override
public List<Integer> getAllAgesAsList(final List<Integer> ids1, final int[] ids2,
final int... ids3) {
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ final StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
final int _inputSize = ids1 == null ? 1 : ids1.size();
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
@@ -375,7 +391,7 @@
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
- final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
+ final List<Integer> _result = new ArrayList<Integer>();
while (_cursor.moveToNext()) {
final Integer _item_3;
if (_cursor.isNull(0)) {
@@ -443,7 +459,7 @@
@Override
public LiveData<List<User>> loadUsersByIdsLive(final int... ids) {
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ final StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("SELECT * FROM user where uid IN (");
final int _inputSize = ids == null ? 1 : ids.length;
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
@@ -470,7 +486,7 @@
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
- final List<User> _result = new ArrayList<User>(_cursor.getCount());
+ final List<User> _result = new ArrayList<User>();
while (_cursor.moveToNext()) {
final User _item_1;
_item_1 = new User();
@@ -506,89 +522,95 @@
@Override
public List<Child1> getChild1List() {
final String _sql = "SELECT * FROM Child1";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
- final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
- final List<Child1> _result = new ArrayList<Child1>(_cursor.getCount());
- while (_cursor.moveToNext()) {
- final Child1 _item;
- final int _tmpId;
- _tmpId = _cursor.getInt(_cursorIndexOfId);
- final String _tmpName;
- if (_cursor.isNull(_cursorIndexOfName)) {
- _tmpName = null;
- } else {
- _tmpName = _cursor.getString(_cursorIndexOfName);
- }
- final Info _tmpInfo;
- if (!(_cursor.isNull(_cursorIndexOfSerial) && _cursor.isNull(_cursorIndexOfCode))) {
- _tmpInfo = new Info();
- _tmpInfo.serial = _cursor.getInt(_cursorIndexOfSerial);
- if (_cursor.isNull(_cursorIndexOfCode)) {
- _tmpInfo.code = null;
- } else {
- _tmpInfo.code = _cursor.getString(_cursorIndexOfCode);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Child1>>() {
+ @Override
+ @NonNull
+ public List<Child1> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+ final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+ final List<Child1> _result = new ArrayList<Child1>();
+ while (_stmt.step()) {
+ final Child1 _item;
+ final int _tmpId;
+ _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+ final String _tmpName;
+ if (_stmt.isNull(_cursorIndexOfName)) {
+ _tmpName = null;
+ } else {
+ _tmpName = _stmt.getText(_cursorIndexOfName);
+ }
+ final Info _tmpInfo;
+ if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+ _tmpInfo = new Info();
+ _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+ if (_stmt.isNull(_cursorIndexOfCode)) {
+ _tmpInfo.code = null;
+ } else {
+ _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+ }
+ } else {
+ _tmpInfo = null;
+ }
+ _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+ _result.add(_item);
}
- } else {
- _tmpInfo = null;
+ return _result;
+ } finally {
+ _stmt.close();
}
- _item = new Child1(_tmpId,_tmpName,_tmpInfo);
- _result.add(_item);
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
public List<Child2> getChild2List() {
final String _sql = "SELECT * FROM Child2";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
- final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
- final List<Child2> _result = new ArrayList<Child2>(_cursor.getCount());
- while (_cursor.moveToNext()) {
- final Child2 _item;
- final int _tmpId;
- _tmpId = _cursor.getInt(_cursorIndexOfId);
- final String _tmpName;
- if (_cursor.isNull(_cursorIndexOfName)) {
- _tmpName = null;
- } else {
- _tmpName = _cursor.getString(_cursorIndexOfName);
- }
- final Info _tmpInfo;
- if (!(_cursor.isNull(_cursorIndexOfSerial) && _cursor.isNull(_cursorIndexOfCode))) {
- _tmpInfo = new Info();
- _tmpInfo.serial = _cursor.getInt(_cursorIndexOfSerial);
- if (_cursor.isNull(_cursorIndexOfCode)) {
- _tmpInfo.code = null;
- } else {
- _tmpInfo.code = _cursor.getString(_cursorIndexOfCode);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Child2>>() {
+ @Override
+ @NonNull
+ public List<Child2> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+ final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+ final List<Child2> _result = new ArrayList<Child2>();
+ while (_stmt.step()) {
+ final Child2 _item;
+ final int _tmpId;
+ _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+ final String _tmpName;
+ if (_stmt.isNull(_cursorIndexOfName)) {
+ _tmpName = null;
+ } else {
+ _tmpName = _stmt.getText(_cursorIndexOfName);
+ }
+ final Info _tmpInfo;
+ if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+ _tmpInfo = new Info();
+ _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+ if (_stmt.isNull(_cursorIndexOfCode)) {
+ _tmpInfo.code = null;
+ } else {
+ _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+ }
+ } else {
+ _tmpInfo = null;
+ }
+ _item = new Child2(_tmpId,_tmpName,_tmpInfo);
+ _result.add(_item);
}
- } else {
- _tmpInfo = null;
+ return _result;
+ } finally {
+ _stmt.close();
}
- _item = new Child2(_tmpId,_tmpName,_tmpInfo);
- _result.add(_item);
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
@@ -605,7 +627,7 @@
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
- final List<Child1> _result = new ArrayList<Child1>(_cursor.getCount());
+ final List<Child1> _result = new ArrayList<Child1>();
while (_cursor.moveToNext()) {
final Child1 _item;
final int _tmpId;
@@ -642,29 +664,32 @@
@Override
public List<UserSummary> getUserNames() {
final String _sql = "SELECT `uid`, `name` FROM (SELECT * FROM User)";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfUid = 0;
- final int _cursorIndexOfName = 1;
- final List<UserSummary> _result = new ArrayList<UserSummary>(_cursor.getCount());
- while (_cursor.moveToNext()) {
- final UserSummary _item;
- _item = new UserSummary();
- _item.uid = _cursor.getInt(_cursorIndexOfUid);
- if (_cursor.isNull(_cursorIndexOfName)) {
- _item.name = null;
- } else {
- _item.name = _cursor.getString(_cursorIndexOfName);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<UserSummary>>() {
+ @Override
+ @NonNull
+ public List<UserSummary> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ final int _cursorIndexOfUid = 0;
+ final int _cursorIndexOfName = 1;
+ final List<UserSummary> _result = new ArrayList<UserSummary>();
+ while (_stmt.step()) {
+ final UserSummary _item;
+ _item = new UserSummary();
+ _item.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ if (_stmt.isNull(_cursorIndexOfName)) {
+ _item.name = null;
+ } else {
+ _item.name = _stmt.getText(_cursorIndexOfName);
+ }
+ _result.add(_item);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- _result.add(_item);
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java
index 31272dc..ffacaa6 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/DeletionDao.java
@@ -4,8 +4,11 @@
import androidx.annotation.Nullable;
import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.RoomDatabase;
-import androidx.room.SharedSQLiteStatement;
+import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteConnectionUtil;
import androidx.room.util.StringUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
import androidx.sqlite.db.SupportSQLiteStatement;
import io.reactivex.Completable;
import io.reactivex.Maybe;
@@ -22,25 +25,22 @@
import java.util.List;
import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
@Generated("androidx.room.RoomProcessor")
@SuppressWarnings({"unchecked", "deprecation"})
public final class DeletionDao_Impl implements DeletionDao {
private final RoomDatabase __db;
- private final EntityDeletionOrUpdateAdapter<User> __deletionAdapterOfUser;
+ private final EntityDeletionOrUpdateAdapter<User> __deleteAdapterOfUser;
- private final EntityDeletionOrUpdateAdapter<MultiPKeyEntity> __deletionAdapterOfMultiPKeyEntity;
+ private final EntityDeletionOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
- private final EntityDeletionOrUpdateAdapter<Book> __deletionAdapterOfBook;
-
- private final SharedSQLiteStatement __preparedStmtOfDeleteByUid;
-
- private final SharedSQLiteStatement __preparedStmtOfDeleteEverything;
+ private final EntityDeletionOrUpdateAdapter<Book> __deleteAdapterOfBook;
public DeletionDao_Impl(@NonNull final RoomDatabase __db) {
this.__db = __db;
- this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+ this.__deleteAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -52,7 +52,7 @@
statement.bindLong(1, entity.uid);
}
};
- this.__deletionAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+ this.__deleteAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -74,7 +74,7 @@
}
}
};
- this.__deletionAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+ this.__deleteAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -86,22 +86,6 @@
statement.bindLong(1, entity.bookId);
}
};
- this.__preparedStmtOfDeleteByUid = new SharedSQLiteStatement(__db) {
- @Override
- @NonNull
- public String createQuery() {
- final String _query = "DELETE FROM user where uid = ?";
- return _query;
- }
- };
- this.__preparedStmtOfDeleteEverything = new SharedSQLiteStatement(__db) {
- @Override
- @NonNull
- public String createQuery() {
- final String _query = "DELETE FROM user";
- return _query;
- }
- };
}
@Override
@@ -109,7 +93,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __deletionAdapterOfUser.handle(user);
+ __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -121,8 +105,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __deletionAdapterOfUser.handle(user1);
- __deletionAdapterOfUser.handleMultiple(others);
+ __deleteAdapterOfUser.handle(user1);
+ __deleteAdapterOfUser.handleMultiple(others);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -134,7 +118,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __deletionAdapterOfUser.handleMultiple(users);
+ __deleteAdapterOfUser.handleMultiple(users);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -147,7 +131,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handle(user);
+ _total += __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -161,7 +145,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handle(user);
+ _total += __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -175,8 +159,8 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handle(user1);
- _total += __deletionAdapterOfUser.handleMultiple(others);
+ _total += __deleteAdapterOfUser.handle(user1);
+ _total += __deleteAdapterOfUser.handleMultiple(others);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -190,7 +174,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handleMultiple(users);
+ _total += __deleteAdapterOfUser.handleMultiple(users);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -206,7 +190,7 @@
public Void call() throws Exception {
__db.beginTransaction();
try {
- __deletionAdapterOfUser.handle(user);
+ __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
return null;
} finally {
@@ -225,7 +209,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handle(user);
+ _total += __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -244,7 +228,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handle(user);
+ _total += __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -260,7 +244,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfMultiPKeyEntity.handle(entity);
+ _total += __deleteAdapterOfMultiPKeyEntity.handle(entity);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -273,8 +257,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __deletionAdapterOfUser.handle(user);
- __deletionAdapterOfBook.handle(book);
+ __deleteAdapterOfUser.handle(user);
+ __deleteAdapterOfBook.handle(book);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -283,22 +267,22 @@
@Override
public int deleteByUid(final int uid) {
- __db.assertNotSuspendingTransaction();
- final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
- int _argIndex = 1;
- _stmt.bindLong(_argIndex, uid);
- try {
- __db.beginTransaction();
- try {
- final int _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
+ final String _sql = "DELETE FROM user where uid = ?";
+ return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+ @Override
+ @NonNull
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, uid);
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
+ }
}
- } finally {
- __preparedStmtOfDeleteByUid.release(_stmt);
- }
+ });
}
@Override
@@ -307,20 +291,17 @@
@Override
@Nullable
public Void call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
+ final String _sql = "DELETE FROM user where uid = ?";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
+ _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return null;
} finally {
- __preparedStmtOfDeleteByUid.release(_stmt);
+ __db.endTransaction();
}
}
});
@@ -332,20 +313,17 @@
@Override
@Nullable
public Integer call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
+ final String _sql = "DELETE FROM user where uid = ?";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final Integer _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
} finally {
- __preparedStmtOfDeleteByUid.release(_stmt);
+ __db.endTransaction();
}
}
});
@@ -357,20 +335,49 @@
@Override
@Nullable
public Integer call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
+ final String _sql = "DELETE FROM user where uid = ?";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final Integer _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
} finally {
- __preparedStmtOfDeleteByUid.release(_stmt);
+ __db.endTransaction();
+ }
+ }
+ });
+ }
+
+ @Override
+ public int deleteByUidList(final int... uid) {
+ final StringBuilder _stringBuilder = new StringBuilder();
+ _stringBuilder.append("DELETE FROM user where uid IN(");
+ final int _inputSize = uid == null ? 1 : uid.length;
+ StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+ _stringBuilder.append(")");
+ final String _sql = _stringBuilder.toString();
+ return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+ @Override
+ @NonNull
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (uid == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item : uid) {
+ _stmt.bindLong(_argIndex, _item);
+ _argIndex++;
+ }
+ }
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
}
}
});
@@ -378,49 +385,20 @@
@Override
public int deleteEverything() {
- __db.assertNotSuspendingTransaction();
- final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteEverything.acquire();
- try {
- __db.beginTransaction();
- try {
- final int _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
+ final String _sql = "DELETE FROM user";
+ return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+ @Override
+ @NonNull
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
+ }
}
- } finally {
- __preparedStmtOfDeleteEverything.release(_stmt);
- }
- }
-
- @Override
- public int deleteByUidList(final int... uid) {
- __db.assertNotSuspendingTransaction();
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
- _stringBuilder.append("DELETE FROM user where uid IN(");
- final int _inputSize = uid == null ? 1 : uid.length;
- StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
- _stringBuilder.append(")");
- final String _sql = _stringBuilder.toString();
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- int _argIndex = 1;
- if (uid == null) {
- _stmt.bindNull(_argIndex);
- } else {
- for (int _item : uid) {
- _stmt.bindLong(_argIndex, _item);
- _argIndex++;
- }
- }
- __db.beginTransaction();
- try {
- final int _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ });
}
@NonNull
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java
index 5dbb7eb..5d60d43 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpdateDao.java
@@ -4,7 +4,9 @@
import androidx.annotation.Nullable;
import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.RoomDatabase;
-import androidx.room.SharedSQLiteStatement;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
import androidx.sqlite.db.SupportSQLiteStatement;
import io.reactivex.Completable;
import io.reactivex.Maybe;
@@ -20,6 +22,7 @@
import java.util.List;
import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
@Generated("androidx.room.RoomProcessor")
@SuppressWarnings({"unchecked", "deprecation"})
@@ -34,10 +37,6 @@
private final EntityDeletionOrUpdateAdapter<Book> __updateAdapterOfBook;
- private final SharedSQLiteStatement __preparedStmtOfAgeUserByUid;
-
- private final SharedSQLiteStatement __preparedStmtOfAgeUserAll;
-
public UpdateDao_Impl(@NonNull final RoomDatabase __db) {
this.__db = __db;
this.__updateAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
@@ -134,22 +133,6 @@
statement.bindLong(3, entity.bookId);
}
};
- this.__preparedStmtOfAgeUserByUid = new SharedSQLiteStatement(__db) {
- @Override
- @NonNull
- public String createQuery() {
- final String _query = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
- return _query;
- }
- };
- this.__preparedStmtOfAgeUserAll = new SharedSQLiteStatement(__db) {
- @Override
- @NonNull
- public String createQuery() {
- final String _query = "UPDATE User SET ageColumn = ageColumn + 1";
- return _query;
- }
- };
}
@Override
@@ -355,42 +338,44 @@
@Override
public void ageUserByUid(final String uid) {
- __db.assertNotSuspendingTransaction();
- final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserByUid.acquire();
- int _argIndex = 1;
- if (uid == null) {
- _stmt.bindNull(_argIndex);
- } else {
- _stmt.bindString(_argIndex, uid);
- }
- try {
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- } finally {
- __db.endTransaction();
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
+ DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+ @Override
+ @NonNull
+ public Void invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (uid == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ _stmt.bindText(_argIndex, uid);
+ }
+ _stmt.step();
+ return null;
+ } finally {
+ _stmt.close();
+ }
}
- } finally {
- __preparedStmtOfAgeUserByUid.release(_stmt);
- }
+ });
}
@Override
public void ageUserAll() {
- __db.assertNotSuspendingTransaction();
- final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
- try {
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- } finally {
- __db.endTransaction();
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+ @Override
+ @NonNull
+ public Void invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ _stmt.step();
+ return null;
+ } finally {
+ _stmt.close();
+ }
}
- } finally {
- __preparedStmtOfAgeUserAll.release(_stmt);
- }
+ });
}
@Override
@@ -399,18 +384,15 @@
@Override
@Nullable
public Void call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
+ _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return null;
} finally {
- __preparedStmtOfAgeUserAll.release(_stmt);
+ __db.endTransaction();
}
}
});
@@ -422,18 +404,15 @@
@Override
@Nullable
public Integer call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final Integer _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
} finally {
- __preparedStmtOfAgeUserAll.release(_stmt);
+ __db.endTransaction();
}
}
});
@@ -445,18 +424,15 @@
@Override
@Nullable
public Integer call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final Integer _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
} finally {
- __preparedStmtOfAgeUserAll.release(_stmt);
+ __db.endTransaction();
}
}
});
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java
index 4d4875a..a380ba3 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/UpsertDao.java
@@ -19,13 +19,13 @@
public final class UpsertDao_Impl implements UpsertDao {
private final RoomDatabase __db;
- private final EntityUpsertionAdapter<User> __upsertionAdapterOfUser;
+ private final EntityUpsertionAdapter<User> __upsertAdapterOfUser;
- private final EntityUpsertionAdapter<Book> __upsertionAdapterOfBook;
+ private final EntityUpsertionAdapter<Book> __upsertAdapterOfBook;
public UpsertDao_Impl(@NonNull final RoomDatabase __db) {
this.__db = __db;
- this.__upsertionAdapterOfUser = new EntityUpsertionAdapter<User>(new EntityInsertionAdapter<User>(__db) {
+ this.__upsertAdapterOfUser = new EntityUpsertionAdapter<User>(new EntityInsertionAdapter<User>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -71,7 +71,7 @@
statement.bindLong(5, entity.uid);
}
});
- this.__upsertionAdapterOfBook = new EntityUpsertionAdapter<Book>(new EntityInsertionAdapter<Book>(__db) {
+ this.__upsertAdapterOfBook = new EntityUpsertionAdapter<Book>(new EntityInsertionAdapter<Book>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -104,7 +104,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __upsertionAdapterOfUser.upsert(user);
+ __upsertAdapterOfUser.upsert(user);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -116,8 +116,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __upsertionAdapterOfUser.upsert(user1);
- __upsertionAdapterOfUser.upsert(others);
+ __upsertAdapterOfUser.upsert(user1);
+ __upsertAdapterOfUser.upsert(others);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -129,7 +129,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __upsertionAdapterOfUser.upsert(users);
+ __upsertAdapterOfUser.upsert(users);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -141,8 +141,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __upsertionAdapterOfUser.upsert(userOne);
- __upsertionAdapterOfUser.upsert(userTwo);
+ __upsertAdapterOfUser.upsert(userOne);
+ __upsertAdapterOfUser.upsert(userTwo);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -154,8 +154,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __upsertionAdapterOfUser.upsert(user);
- __upsertionAdapterOfBook.upsert(book);
+ __upsertAdapterOfUser.upsert(user);
+ __upsertAdapterOfBook.upsert(book);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -167,7 +167,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- final long _result = __upsertionAdapterOfUser.upsertAndReturnId(user);
+ final long _result = __upsertAdapterOfUser.upsertAndReturnId(user);
__db.setTransactionSuccessful();
return _result;
} finally {
@@ -180,7 +180,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- final long[] _result = __upsertionAdapterOfUser.upsertAndReturnIdsArray(users);
+ final long[] _result = __upsertAdapterOfUser.upsertAndReturnIdsArray(users);
__db.setTransactionSuccessful();
return _result;
} finally {
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java
index 3287dd2..7a67ff7 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/WriterDao.java
@@ -17,17 +17,17 @@
public final class WriterDao_Impl implements WriterDao {
private final RoomDatabase __db;
- private final EntityInsertionAdapter<User> __insertionAdapterOfUser;
+ private final EntityInsertionAdapter<User> __insertAdapterOfUser;
- private final EntityInsertionAdapter<User> __insertionAdapterOfUser_1;
+ private final EntityInsertionAdapter<User> __insertAdapterOfUser_1;
- private final EntityInsertionAdapter<User> __insertionAdapterOfUser_2;
+ private final EntityInsertionAdapter<User> __insertAdapterOfUser_2;
- private final EntityInsertionAdapter<Book> __insertionAdapterOfBook;
+ private final EntityInsertionAdapter<Book> __insertAdapterOfBook;
public WriterDao_Impl(@NonNull final RoomDatabase __db) {
this.__db = __db;
- this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
+ this.__insertAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -50,7 +50,7 @@
statement.bindLong(4, entity.age);
}
};
- this.__insertionAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) {
+ this.__insertAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -73,7 +73,7 @@
statement.bindLong(4, entity.age);
}
};
- this.__insertionAdapterOfUser_2 = new EntityInsertionAdapter<User>(__db) {
+ this.__insertAdapterOfUser_2 = new EntityInsertionAdapter<User>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -96,7 +96,7 @@
statement.bindLong(4, entity.age);
}
};
- this.__insertionAdapterOfBook = new EntityInsertionAdapter<Book>(__db) {
+ this.__insertAdapterOfBook = new EntityInsertionAdapter<Book>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -116,7 +116,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __insertionAdapterOfUser.insert(user);
+ __insertAdapterOfUser.insert(user);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -128,8 +128,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __insertionAdapterOfUser.insert(user1);
- __insertionAdapterOfUser.insert(others);
+ __insertAdapterOfUser.insert(user1);
+ __insertAdapterOfUser.insert(others);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -141,7 +141,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __insertionAdapterOfUser_1.insert(users);
+ __insertAdapterOfUser_1.insert(users);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -153,8 +153,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __insertionAdapterOfUser_2.insert(userOne);
- __insertionAdapterOfUser_2.insert(userTwo);
+ __insertAdapterOfUser_2.insert(userOne);
+ __insertAdapterOfUser_2.insert(userTwo);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -166,8 +166,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __insertionAdapterOfUser.insert(user);
- __insertionAdapterOfBook.insert(book);
+ __insertAdapterOfUser.insert(user);
+ __insertAdapterOfBook.insert(book);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
index 5241793..8cc7acf 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
@@ -12,6 +12,7 @@
import androidx.room.util.DBUtil;
import androidx.room.util.SQLiteStatementUtil;
import androidx.room.util.StringUtil;
+import androidx.sqlite.SQLiteConnection;
import androidx.sqlite.SQLiteStatement;
import androidx.sqlite.db.SupportSQLiteQuery;
import com.google.common.util.concurrent.ListenableFuture;
@@ -55,55 +56,63 @@
@Override
public List<ComplexDao.FullName> fullNames(final int id) {
final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
- int _argIndex = 1;
- _statement.bindLong(_argIndex, id);
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfFullName = 0;
- final int _cursorIndexOfId = 1;
- final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>(_cursor.getCount());
- while (_cursor.moveToNext()) {
- final ComplexDao.FullName _item;
- _item = new ComplexDao.FullName();
- _item.fullName = _cursor.getString(_cursorIndexOfFullName);
- _item.id = _cursor.getInt(_cursorIndexOfId);
- _result.add(_item);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<ComplexDao.FullName>>() {
+ @Override
+ @NonNull
+ public List<ComplexDao.FullName> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, id);
+ final int _cursorIndexOfFullName = 0;
+ final int _cursorIndexOfId = 1;
+ final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>();
+ while (_stmt.step()) {
+ final ComplexDao.FullName _item;
+ _item = new ComplexDao.FullName();
+ _item.fullName = _stmt.getText(_cursorIndexOfFullName);
+ _item.id = (int) (_stmt.getLong(_cursorIndexOfId));
+ _result.add(_item);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
+ }
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
public User getById(final int id) {
final String _sql = "SELECT * FROM user where uid = ?";
- return DBUtil.performReadBlocking(__db, _sql, new Function1<SQLiteStatement, User>() {
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, User>() {
@Override
@NonNull
- public User invoke(@NonNull final SQLiteStatement _stmt) {
- int _argIndex = 1;
- _stmt.bindLong(_argIndex, id);
- final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
- final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
- final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
- final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
- final User _result;
- if (_stmt.step()) {
- _result = new User();
- _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
- _result.name = _stmt.getText(_cursorIndexOfName);
- final String _tmpLastName;
- _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
- _result.setLastName(_tmpLastName);
- _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
- } else {
- _result = null;
+ public User invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, id);
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+ final User _result;
+ if (_stmt.step()) {
+ _result = new User();
+ _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ _result.name = _stmt.getText(_cursorIndexOfName);
+ final String _tmpLastName;
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ _result.setLastName(_tmpLastName);
+ _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+ } else {
+ _result = null;
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- return _result;
}
});
}
@@ -111,78 +120,85 @@
@Override
public User findByName(final String name, final String lastName) {
final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
- return DBUtil.performReadBlocking(__db, _sql, new Function1<SQLiteStatement, User>() {
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, User>() {
@Override
@NonNull
- public User invoke(@NonNull final SQLiteStatement _stmt) {
- int _argIndex = 1;
- _stmt.bindText(_argIndex, name);
- _argIndex = 2;
- _stmt.bindText(_argIndex, lastName);
- final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
- final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
- final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
- final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
- final User _result;
- if (_stmt.step()) {
- _result = new User();
- _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
- _result.name = _stmt.getText(_cursorIndexOfName);
- final String _tmpLastName;
- _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
- _result.setLastName(_tmpLastName);
- _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
- } else {
- _result = null;
+ public User invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindText(_argIndex, name);
+ _argIndex = 2;
+ _stmt.bindText(_argIndex, lastName);
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+ final User _result;
+ if (_stmt.step()) {
+ _result = new User();
+ _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ _result.name = _stmt.getText(_cursorIndexOfName);
+ final String _tmpLastName;
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ _result.setLastName(_tmpLastName);
+ _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+ } else {
+ _result = null;
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- return _result;
}
});
}
@Override
public List<User> loadAllByIds(final int... ids) {
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ final StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("SELECT * FROM user where uid IN (");
final int _inputSize = ids == null ? 1 : ids.length;
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
_stringBuilder.append(")");
final String _sql = _stringBuilder.toString();
- final int _argCount = 0 + _inputSize;
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
- int _argIndex = 1;
- if (ids == null) {
- _statement.bindNull(_argIndex);
- } else {
- for (int _item : ids) {
- _statement.bindLong(_argIndex, _item);
- _argIndex++;
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<User>>() {
+ @Override
+ @NonNull
+ public List<User> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (ids == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item : ids) {
+ _stmt.bindLong(_argIndex, _item);
+ _argIndex++;
+ }
+ }
+ final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+ final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+ final List<User> _result = new ArrayList<User>();
+ while (_stmt.step()) {
+ final User _item_1;
+ _item_1 = new User();
+ _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ _item_1.name = _stmt.getText(_cursorIndexOfName);
+ final String _tmpLastName;
+ _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+ _item_1.setLastName(_tmpLastName);
+ _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+ _result.add(_item_1);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
+ }
}
- }
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
- final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
- final List<User> _result = new ArrayList<User>(_cursor.getCount());
- while (_cursor.moveToNext()) {
- final User _item_1;
- _item_1 = new User();
- _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
- _item_1.name = _cursor.getString(_cursorIndexOfName);
- final String _tmpLastName;
- _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
- _item_1.setLastName(_tmpLastName);
- _item_1.age = _cursor.getInt(_cursorIndexOfAge);
- _result.add(_item_1);
- }
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
@@ -209,7 +225,7 @@
@Override
public int[] getAllAges(final int... ids) {
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ final StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
final int _inputSize = ids == null ? 1 : ids.length;
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
@@ -247,7 +263,7 @@
@Override
public List<Integer> getAllAgesAsList(final List<Integer> ids) {
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ final StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
final int _inputSize = ids == null ? 1 : ids.size();
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
@@ -271,7 +287,7 @@
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
- final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
+ final List<Integer> _result = new ArrayList<Integer>();
while (_cursor.moveToNext()) {
final Integer _item_1;
if (_cursor.isNull(0)) {
@@ -291,7 +307,7 @@
@Override
public List<Integer> getAllAgesAsList(final List<Integer> ids1, final int[] ids2,
final int... ids3) {
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ final StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
final int _inputSize = ids1 == null ? 1 : ids1.size();
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
@@ -339,7 +355,7 @@
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
- final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
+ final List<Integer> _result = new ArrayList<Integer>();
while (_cursor.moveToNext()) {
final Integer _item_3;
if (_cursor.isNull(0)) {
@@ -399,7 +415,7 @@
@Override
public LiveData<List<User>> loadUsersByIdsLive(final int... ids) {
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ final StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("SELECT * FROM user where uid IN (");
final int _inputSize = ids == null ? 1 : ids.length;
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
@@ -426,7 +442,7 @@
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
- final List<User> _result = new ArrayList<User>(_cursor.getCount());
+ final List<User> _result = new ArrayList<User>();
while (_cursor.moveToNext()) {
final User _item_1;
_item_1 = new User();
@@ -454,73 +470,79 @@
@Override
public List<Child1> getChild1List() {
final String _sql = "SELECT * FROM Child1";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
- final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
- final List<Child1> _result = new ArrayList<Child1>(_cursor.getCount());
- while (_cursor.moveToNext()) {
- final Child1 _item;
- final int _tmpId;
- _tmpId = _cursor.getInt(_cursorIndexOfId);
- final String _tmpName;
- _tmpName = _cursor.getString(_cursorIndexOfName);
- final Info _tmpInfo;
- if (!(_cursor.isNull(_cursorIndexOfSerial) && _cursor.isNull(_cursorIndexOfCode))) {
- _tmpInfo = new Info();
- _tmpInfo.serial = _cursor.getInt(_cursorIndexOfSerial);
- _tmpInfo.code = _cursor.getString(_cursorIndexOfCode);
- } else {
- _tmpInfo = null;
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Child1>>() {
+ @Override
+ @NonNull
+ public List<Child1> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+ final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+ final List<Child1> _result = new ArrayList<Child1>();
+ while (_stmt.step()) {
+ final Child1 _item;
+ final int _tmpId;
+ _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+ final String _tmpName;
+ _tmpName = _stmt.getText(_cursorIndexOfName);
+ final Info _tmpInfo;
+ if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+ _tmpInfo = new Info();
+ _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+ _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+ } else {
+ _tmpInfo = null;
+ }
+ _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+ _result.add(_item);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- _item = new Child1(_tmpId,_tmpName,_tmpInfo);
- _result.add(_item);
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
public List<Child2> getChild2List() {
final String _sql = "SELECT * FROM Child2";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
- final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
- final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
- final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
- final List<Child2> _result = new ArrayList<Child2>(_cursor.getCount());
- while (_cursor.moveToNext()) {
- final Child2 _item;
- final int _tmpId;
- _tmpId = _cursor.getInt(_cursorIndexOfId);
- final String _tmpName;
- _tmpName = _cursor.getString(_cursorIndexOfName);
- final Info _tmpInfo;
- if (!(_cursor.isNull(_cursorIndexOfSerial) && _cursor.isNull(_cursorIndexOfCode))) {
- _tmpInfo = new Info();
- _tmpInfo.serial = _cursor.getInt(_cursorIndexOfSerial);
- _tmpInfo.code = _cursor.getString(_cursorIndexOfCode);
- } else {
- _tmpInfo = null;
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<Child2>>() {
+ @Override
+ @NonNull
+ public List<Child2> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+ final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+ final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+ final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+ final List<Child2> _result = new ArrayList<Child2>();
+ while (_stmt.step()) {
+ final Child2 _item;
+ final int _tmpId;
+ _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+ final String _tmpName;
+ _tmpName = _stmt.getText(_cursorIndexOfName);
+ final Info _tmpInfo;
+ if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+ _tmpInfo = new Info();
+ _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+ _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+ } else {
+ _tmpInfo = null;
+ }
+ _item = new Child2(_tmpId,_tmpName,_tmpInfo);
+ _result.add(_item);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
}
- _item = new Child2(_tmpId,_tmpName,_tmpInfo);
- _result.add(_item);
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
@@ -537,7 +559,7 @@
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
final int _cursorIndexOfSerial = CursorUtil.getColumnIndexOrThrow(_cursor, "serial");
final int _cursorIndexOfCode = CursorUtil.getColumnIndexOrThrow(_cursor, "code");
- final List<Child1> _result = new ArrayList<Child1>(_cursor.getCount());
+ final List<Child1> _result = new ArrayList<Child1>();
while (_cursor.moveToNext()) {
final Child1 _item;
final int _tmpId;
@@ -566,25 +588,28 @@
@Override
public List<UserSummary> getUserNames() {
final String _sql = "SELECT `uid`, `name` FROM (SELECT * FROM User)";
- final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
- __db.assertNotSuspendingTransaction();
- final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
- try {
- final int _cursorIndexOfUid = 0;
- final int _cursorIndexOfName = 1;
- final List<UserSummary> _result = new ArrayList<UserSummary>(_cursor.getCount());
- while (_cursor.moveToNext()) {
- final UserSummary _item;
- _item = new UserSummary();
- _item.uid = _cursor.getInt(_cursorIndexOfUid);
- _item.name = _cursor.getString(_cursorIndexOfName);
- _result.add(_item);
+ return DBUtil.performBlocking(__db, true, false, new Function1<SQLiteConnection, List<UserSummary>>() {
+ @Override
+ @NonNull
+ public List<UserSummary> invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ final int _cursorIndexOfUid = 0;
+ final int _cursorIndexOfName = 1;
+ final List<UserSummary> _result = new ArrayList<UserSummary>();
+ while (_stmt.step()) {
+ final UserSummary _item;
+ _item = new UserSummary();
+ _item.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+ _item.name = _stmt.getText(_cursorIndexOfName);
+ _result.add(_item);
+ }
+ return _result;
+ } finally {
+ _stmt.close();
+ }
}
- return _result;
- } finally {
- _cursor.close();
- _statement.release();
- }
+ });
}
@Override
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
index 45fee3b..2a085ab 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
@@ -4,8 +4,11 @@
import androidx.annotation.Nullable;
import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.RoomDatabase;
-import androidx.room.SharedSQLiteStatement;
+import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteConnectionUtil;
import androidx.room.util.StringUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
import androidx.sqlite.db.SupportSQLiteStatement;
import io.reactivex.Completable;
import io.reactivex.Maybe;
@@ -22,25 +25,22 @@
import java.util.List;
import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
@Generated("androidx.room.RoomProcessor")
@SuppressWarnings({"unchecked", "deprecation"})
public final class DeletionDao_Impl implements DeletionDao {
private final RoomDatabase __db;
- private final EntityDeletionOrUpdateAdapter<User> __deletionAdapterOfUser;
+ private final EntityDeletionOrUpdateAdapter<User> __deleteAdapterOfUser;
- private final EntityDeletionOrUpdateAdapter<MultiPKeyEntity> __deletionAdapterOfMultiPKeyEntity;
+ private final EntityDeletionOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
- private final EntityDeletionOrUpdateAdapter<Book> __deletionAdapterOfBook;
-
- private final SharedSQLiteStatement __preparedStmtOfDeleteByUid;
-
- private final SharedSQLiteStatement __preparedStmtOfDeleteEverything;
+ private final EntityDeletionOrUpdateAdapter<Book> __deleteAdapterOfBook;
public DeletionDao_Impl(@NonNull final RoomDatabase __db) {
this.__db = __db;
- this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+ this.__deleteAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -53,7 +53,7 @@
statement.bindLong(1, entity.uid);
}
};
- this.__deletionAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+ this.__deleteAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -67,7 +67,7 @@
statement.bindString(2, entity.lastName);
}
};
- this.__deletionAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+ this.__deleteAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -80,22 +80,6 @@
statement.bindLong(1, entity.bookId);
}
};
- this.__preparedStmtOfDeleteByUid = new SharedSQLiteStatement(__db) {
- @Override
- @NonNull
- public String createQuery() {
- final String _query = "DELETE FROM user where uid = ?";
- return _query;
- }
- };
- this.__preparedStmtOfDeleteEverything = new SharedSQLiteStatement(__db) {
- @Override
- @NonNull
- public String createQuery() {
- final String _query = "DELETE FROM user";
- return _query;
- }
- };
}
@Override
@@ -103,7 +87,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __deletionAdapterOfUser.handle(user);
+ __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -115,8 +99,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __deletionAdapterOfUser.handle(user1);
- __deletionAdapterOfUser.handleMultiple(others);
+ __deleteAdapterOfUser.handle(user1);
+ __deleteAdapterOfUser.handleMultiple(others);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -128,7 +112,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __deletionAdapterOfUser.handleMultiple(users);
+ __deleteAdapterOfUser.handleMultiple(users);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -141,7 +125,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handle(user);
+ _total += __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -155,7 +139,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handle(user);
+ _total += __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -169,8 +153,8 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handle(user1);
- _total += __deletionAdapterOfUser.handleMultiple(others);
+ _total += __deleteAdapterOfUser.handle(user1);
+ _total += __deleteAdapterOfUser.handleMultiple(others);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -184,7 +168,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handleMultiple(users);
+ _total += __deleteAdapterOfUser.handleMultiple(users);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -200,7 +184,7 @@
public Void call() throws Exception {
__db.beginTransaction();
try {
- __deletionAdapterOfUser.handle(user);
+ __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
return null;
} finally {
@@ -219,7 +203,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handle(user);
+ _total += __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -238,7 +222,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfUser.handle(user);
+ _total += __deleteAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -254,7 +238,7 @@
int _total = 0;
__db.beginTransaction();
try {
- _total += __deletionAdapterOfMultiPKeyEntity.handle(entity);
+ _total += __deleteAdapterOfMultiPKeyEntity.handle(entity);
__db.setTransactionSuccessful();
return _total;
} finally {
@@ -267,8 +251,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __deletionAdapterOfUser.handle(user);
- __deletionAdapterOfBook.handle(book);
+ __deleteAdapterOfUser.handle(user);
+ __deleteAdapterOfBook.handle(book);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -277,22 +261,22 @@
@Override
public int deleteByUid(final int uid) {
- __db.assertNotSuspendingTransaction();
- final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
- int _argIndex = 1;
- _stmt.bindLong(_argIndex, uid);
- try {
- __db.beginTransaction();
- try {
- final int _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
+ final String _sql = "DELETE FROM user where uid = ?";
+ return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+ @Override
+ @NonNull
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, uid);
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
+ }
}
- } finally {
- __preparedStmtOfDeleteByUid.release(_stmt);
- }
+ });
}
@Override
@@ -301,20 +285,17 @@
@Override
@Nullable
public Void call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
+ final String _sql = "DELETE FROM user where uid = ?";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
+ _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return null;
} finally {
- __preparedStmtOfDeleteByUid.release(_stmt);
+ __db.endTransaction();
}
}
});
@@ -326,20 +307,17 @@
@Override
@Nullable
public Integer call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
+ final String _sql = "DELETE FROM user where uid = ?";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final Integer _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
} finally {
- __preparedStmtOfDeleteByUid.release(_stmt);
+ __db.endTransaction();
}
}
});
@@ -351,20 +329,49 @@
@Override
@Nullable
public Integer call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
+ final String _sql = "DELETE FROM user where uid = ?";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
int _argIndex = 1;
_stmt.bindLong(_argIndex, uid);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final Integer _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
} finally {
- __preparedStmtOfDeleteByUid.release(_stmt);
+ __db.endTransaction();
+ }
+ }
+ });
+ }
+
+ @Override
+ public int deleteByUidList(final int... uid) {
+ final StringBuilder _stringBuilder = new StringBuilder();
+ _stringBuilder.append("DELETE FROM user where uid IN(");
+ final int _inputSize = uid == null ? 1 : uid.length;
+ StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+ _stringBuilder.append(")");
+ final String _sql = _stringBuilder.toString();
+ return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+ @Override
+ @NonNull
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ if (uid == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ for (int _item : uid) {
+ _stmt.bindLong(_argIndex, _item);
+ _argIndex++;
+ }
+ }
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
}
}
});
@@ -372,49 +379,20 @@
@Override
public int deleteEverything() {
- __db.assertNotSuspendingTransaction();
- final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteEverything.acquire();
- try {
- __db.beginTransaction();
- try {
- final int _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
+ final String _sql = "DELETE FROM user";
+ return DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Integer>() {
+ @Override
+ @NonNull
+ public Integer invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ _stmt.step();
+ return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+ } finally {
+ _stmt.close();
+ }
}
- } finally {
- __preparedStmtOfDeleteEverything.release(_stmt);
- }
- }
-
- @Override
- public int deleteByUidList(final int... uid) {
- __db.assertNotSuspendingTransaction();
- final StringBuilder _stringBuilder = StringUtil.newStringBuilder();
- _stringBuilder.append("DELETE FROM user where uid IN(");
- final int _inputSize = uid == null ? 1 : uid.length;
- StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
- _stringBuilder.append(")");
- final String _sql = _stringBuilder.toString();
- final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
- int _argIndex = 1;
- if (uid == null) {
- _stmt.bindNull(_argIndex);
- } else {
- for (int _item : uid) {
- _stmt.bindLong(_argIndex, _item);
- _argIndex++;
- }
- }
- __db.beginTransaction();
- try {
- final int _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ });
}
@NonNull
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
index 1fb23be..20f2c7d 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
@@ -4,7 +4,9 @@
import androidx.annotation.Nullable;
import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.RoomDatabase;
-import androidx.room.SharedSQLiteStatement;
+import androidx.room.util.DBUtil;
+import androidx.sqlite.SQLiteConnection;
+import androidx.sqlite.SQLiteStatement;
import androidx.sqlite.db.SupportSQLiteStatement;
import io.reactivex.Completable;
import io.reactivex.Maybe;
@@ -20,6 +22,7 @@
import java.util.List;
import java.util.concurrent.Callable;
import javax.annotation.processing.Generated;
+import kotlin.jvm.functions.Function1;
@Generated("androidx.room.RoomProcessor")
@SuppressWarnings({"unchecked", "deprecation"})
@@ -34,10 +37,6 @@
private final EntityDeletionOrUpdateAdapter<Book> __updateAdapterOfBook;
- private final SharedSQLiteStatement __preparedStmtOfAgeUserByUid;
-
- private final SharedSQLiteStatement __preparedStmtOfAgeUserAll;
-
public UpdateDao_Impl(@NonNull final RoomDatabase __db) {
this.__db = __db;
this.__updateAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
@@ -105,22 +104,6 @@
statement.bindLong(3, entity.bookId);
}
};
- this.__preparedStmtOfAgeUserByUid = new SharedSQLiteStatement(__db) {
- @Override
- @NonNull
- public String createQuery() {
- final String _query = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
- return _query;
- }
- };
- this.__preparedStmtOfAgeUserAll = new SharedSQLiteStatement(__db) {
- @Override
- @NonNull
- public String createQuery() {
- final String _query = "UPDATE User SET ageColumn = ageColumn + 1";
- return _query;
- }
- };
}
@Override
@@ -326,38 +309,40 @@
@Override
public void ageUserByUid(final String uid) {
- __db.assertNotSuspendingTransaction();
- final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserByUid.acquire();
- int _argIndex = 1;
- _stmt.bindString(_argIndex, uid);
- try {
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- } finally {
- __db.endTransaction();
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
+ DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+ @Override
+ @NonNull
+ public Void invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ int _argIndex = 1;
+ _stmt.bindText(_argIndex, uid);
+ _stmt.step();
+ return null;
+ } finally {
+ _stmt.close();
+ }
}
- } finally {
- __preparedStmtOfAgeUserByUid.release(_stmt);
- }
+ });
}
@Override
public void ageUserAll() {
- __db.assertNotSuspendingTransaction();
- final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
- try {
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- } finally {
- __db.endTransaction();
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ DBUtil.performBlocking(__db, false, true, new Function1<SQLiteConnection, Void>() {
+ @Override
+ @NonNull
+ public Void invoke(@NonNull final SQLiteConnection _connection) {
+ final SQLiteStatement _stmt = _connection.prepare(_sql);
+ try {
+ _stmt.step();
+ return null;
+ } finally {
+ _stmt.close();
+ }
}
- } finally {
- __preparedStmtOfAgeUserAll.release(_stmt);
- }
+ });
}
@Override
@@ -366,18 +351,15 @@
@Override
@Nullable
public Void call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return null;
- } finally {
- __db.endTransaction();
- }
+ _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return null;
} finally {
- __preparedStmtOfAgeUserAll.release(_stmt);
+ __db.endTransaction();
}
}
});
@@ -389,18 +371,15 @@
@Override
@Nullable
public Integer call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final Integer _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
} finally {
- __preparedStmtOfAgeUserAll.release(_stmt);
+ __db.endTransaction();
}
}
});
@@ -412,18 +391,15 @@
@Override
@Nullable
public Integer call() throws Exception {
- final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
+ final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+ final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ __db.beginTransaction();
try {
- __db.beginTransaction();
- try {
- final Integer _result = _stmt.executeUpdateDelete();
- __db.setTransactionSuccessful();
- return _result;
- } finally {
- __db.endTransaction();
- }
+ final Integer _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
} finally {
- __preparedStmtOfAgeUserAll.release(_stmt);
+ __db.endTransaction();
}
}
});
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java
index 80b1ba9..c169994 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpsertDao.java
@@ -19,13 +19,13 @@
public final class UpsertDao_Impl implements UpsertDao {
private final RoomDatabase __db;
- private final EntityUpsertionAdapter<User> __upsertionAdapterOfUser;
+ private final EntityUpsertionAdapter<User> __upsertAdapterOfUser;
- private final EntityUpsertionAdapter<Book> __upsertionAdapterOfBook;
+ private final EntityUpsertionAdapter<Book> __upsertAdapterOfBook;
public UpsertDao_Impl(@NonNull final RoomDatabase __db) {
this.__db = __db;
- this.__upsertionAdapterOfUser = new EntityUpsertionAdapter<User>(new EntityInsertionAdapter<User>(__db) {
+ this.__upsertAdapterOfUser = new EntityUpsertionAdapter<User>(new EntityInsertionAdapter<User>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -57,7 +57,7 @@
statement.bindLong(5, entity.uid);
}
});
- this.__upsertionAdapterOfBook = new EntityUpsertionAdapter<Book>(new EntityInsertionAdapter<Book>(__db) {
+ this.__upsertAdapterOfBook = new EntityUpsertionAdapter<Book>(new EntityInsertionAdapter<Book>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -92,7 +92,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __upsertionAdapterOfUser.upsert(user);
+ __upsertAdapterOfUser.upsert(user);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -104,8 +104,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __upsertionAdapterOfUser.upsert(user1);
- __upsertionAdapterOfUser.upsert(others);
+ __upsertAdapterOfUser.upsert(user1);
+ __upsertAdapterOfUser.upsert(others);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -117,7 +117,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __upsertionAdapterOfUser.upsert(users);
+ __upsertAdapterOfUser.upsert(users);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -129,8 +129,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __upsertionAdapterOfUser.upsert(userOne);
- __upsertionAdapterOfUser.upsert(userTwo);
+ __upsertAdapterOfUser.upsert(userOne);
+ __upsertAdapterOfUser.upsert(userTwo);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -142,8 +142,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __upsertionAdapterOfUser.upsert(user);
- __upsertionAdapterOfBook.upsert(book);
+ __upsertAdapterOfUser.upsert(user);
+ __upsertAdapterOfBook.upsert(book);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -155,7 +155,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- final long _result = __upsertionAdapterOfUser.upsertAndReturnId(user);
+ final long _result = __upsertAdapterOfUser.upsertAndReturnId(user);
__db.setTransactionSuccessful();
return _result;
} finally {
@@ -168,7 +168,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- final long[] _result = __upsertionAdapterOfUser.upsertAndReturnIdsArray(users);
+ final long[] _result = __upsertAdapterOfUser.upsertAndReturnIdsArray(users);
__db.setTransactionSuccessful();
return _result;
} finally {
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java
index a0601f1..83d3b7a 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/WriterDao.java
@@ -17,17 +17,17 @@
public final class WriterDao_Impl implements WriterDao {
private final RoomDatabase __db;
- private final EntityInsertionAdapter<User> __insertionAdapterOfUser;
+ private final EntityInsertionAdapter<User> __insertAdapterOfUser;
- private final EntityInsertionAdapter<User> __insertionAdapterOfUser_1;
+ private final EntityInsertionAdapter<User> __insertAdapterOfUser_1;
- private final EntityInsertionAdapter<User> __insertionAdapterOfUser_2;
+ private final EntityInsertionAdapter<User> __insertAdapterOfUser_2;
- private final EntityInsertionAdapter<Book> __insertionAdapterOfBook;
+ private final EntityInsertionAdapter<Book> __insertAdapterOfBook;
public WriterDao_Impl(@NonNull final RoomDatabase __db) {
this.__db = __db;
- this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
+ this.__insertAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -43,7 +43,7 @@
statement.bindLong(4, entity.age);
}
};
- this.__insertionAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) {
+ this.__insertAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -59,7 +59,7 @@
statement.bindLong(4, entity.age);
}
};
- this.__insertionAdapterOfUser_2 = new EntityInsertionAdapter<User>(__db) {
+ this.__insertAdapterOfUser_2 = new EntityInsertionAdapter<User>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -75,7 +75,7 @@
statement.bindLong(4, entity.age);
}
};
- this.__insertionAdapterOfBook = new EntityInsertionAdapter<Book>(__db) {
+ this.__insertAdapterOfBook = new EntityInsertionAdapter<Book>(__db) {
@Override
@NonNull
protected String createQuery() {
@@ -96,7 +96,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __insertionAdapterOfUser.insert(user);
+ __insertAdapterOfUser.insert(user);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -108,8 +108,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __insertionAdapterOfUser.insert(user1);
- __insertionAdapterOfUser.insert(others);
+ __insertAdapterOfUser.insert(user1);
+ __insertAdapterOfUser.insert(others);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -121,7 +121,7 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __insertionAdapterOfUser_1.insert(users);
+ __insertAdapterOfUser_1.insert(users);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -133,8 +133,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __insertionAdapterOfUser_2.insert(userOne);
- __insertionAdapterOfUser_2.insert(userTwo);
+ __insertAdapterOfUser_2.insert(userOne);
+ __insertAdapterOfUser_2.insert(userTwo);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
@@ -146,8 +146,8 @@
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
- __insertionAdapterOfUser.insert(user);
- __insertionAdapterOfBook.insert(book);
+ __insertAdapterOfUser.insert(user);
+ __insertAdapterOfBook.insert(book);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/abstractClassWithParam.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/abstractClassWithParam.kt
index 277577f..c8e48b8 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/abstractClassWithParam.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/abstractClassWithParam.kt
@@ -1,6 +1,7 @@
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.String
@@ -20,17 +21,22 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- _result = MyEntity(_tmpPk)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ _result = MyEntity(_tmpPk)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/arrayParameterAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/arrayParameterAdapter.kt
index be1206e..abb055b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/arrayParameterAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/arrayParameterAdapter.kt
@@ -1,9 +1,8 @@
import androidx.room.RoomDatabase
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
-import androidx.room.util.performReadBlocking
-import java.lang.StringBuilder
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Array
import kotlin.Int
@@ -12,6 +11,7 @@
import kotlin.Suppress
import kotlin.collections.List
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -24,200 +24,235 @@
}
public override fun arrayOfString(arg: Array<String>): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- for (_item: String in arg) {
- _stmt.bindText(_argIndex, _item)
- _argIndex++
- }
- val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpId: String
- _tmpId = _stmt.getText(_cursorIndexOfId)
- _result = MyEntity(_tmpId)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
- }
- }
-
- public override fun nullableArrayOfString(arg: Array<String>?): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
- val _inputSize: Int = if (arg == null) 1 else arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- if (arg == null) {
- _stmt.bindNull(_argIndex)
- } else {
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
for (_item: String in arg) {
_stmt.bindText(_argIndex, _item)
_argIndex++
}
- }
- val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpId: String
- _tmpId = _stmt.getText(_cursorIndexOfId)
- _result = MyEntity(_tmpId)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
- }
- }
-
- public override fun arrayOfNullableString(arg: Array<String?>): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _stmt.bindNull(_argIndex)
+ val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpId: String
+ _tmpId = _stmt.getText(_cursorIndexOfId)
+ _result = MyEntity(_tmpId)
} else {
- _stmt.bindText(_argIndex, _item)
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- _argIndex++
+ _result
+ } finally {
+ _stmt.close()
}
- val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpId: String
- _tmpId = _stmt.getText(_cursorIndexOfId)
- _result = MyEntity(_tmpId)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
}
}
- public override fun varargOfString(vararg arg: String): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- for (_item: String in arg) {
- _stmt.bindText(_argIndex, _item)
- _argIndex++
- }
- val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpId: String
- _tmpId = _stmt.getText(_cursorIndexOfId)
- _result = MyEntity(_tmpId)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
- }
- }
-
- public override fun varargOfNullableString(vararg arg: String?): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _stmt.bindNull(_argIndex)
- } else {
- _stmt.bindText(_argIndex, _item)
- }
- _argIndex++
- }
- val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpId: String
- _tmpId = _stmt.getText(_cursorIndexOfId)
- _result = MyEntity(_tmpId)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
- }
- }
-
- public override fun primitiveIntArray(arg: IntArray): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
- _stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
- val _inputSize: Int = arg.size
- appendPlaceholders(_stringBuilder, _inputSize)
- _stringBuilder.append(")")
- val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- for (_item: Int in arg) {
- _stmt.bindLong(_argIndex, _item.toLong())
- _argIndex++
- }
- val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpId: String
- _tmpId = _stmt.getText(_cursorIndexOfId)
- _result = MyEntity(_tmpId)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
- }
- }
-
- public override fun nullablePrimitiveIntArray(arg: IntArray?): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ public override fun nullableArrayOfString(arg: Array<String>?): MyEntity {
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
val _inputSize: Int = if (arg == null) 1 else arg.size
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- if (arg == null) {
- _stmt.bindNull(_argIndex)
- } else {
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ if (arg == null) {
+ _stmt.bindNull(_argIndex)
+ } else {
+ for (_item: String in arg) {
+ _stmt.bindText(_argIndex, _item)
+ _argIndex++
+ }
+ }
+ val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpId: String
+ _tmpId = _stmt.getText(_cursorIndexOfId)
+ _result = MyEntity(_tmpId)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override fun arrayOfNullableString(arg: Array<String?>): MyEntity {
+ val _stringBuilder: StringBuilder = StringBuilder()
+ _stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
+ val _inputSize: Int = arg.size
+ appendPlaceholders(_stringBuilder, _inputSize)
+ _stringBuilder.append(")")
+ val _sql: String = _stringBuilder.toString()
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
+ } else {
+ _stmt.bindText(_argIndex, _item)
+ }
+ _argIndex++
+ }
+ val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpId: String
+ _tmpId = _stmt.getText(_cursorIndexOfId)
+ _result = MyEntity(_tmpId)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override fun varargOfString(vararg arg: String): MyEntity {
+ val _stringBuilder: StringBuilder = StringBuilder()
+ _stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
+ val _inputSize: Int = arg.size
+ appendPlaceholders(_stringBuilder, _inputSize)
+ _stringBuilder.append(")")
+ val _sql: String = _stringBuilder.toString()
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String in arg) {
+ _stmt.bindText(_argIndex, _item)
+ _argIndex++
+ }
+ val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpId: String
+ _tmpId = _stmt.getText(_cursorIndexOfId)
+ _result = MyEntity(_tmpId)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override fun varargOfNullableString(vararg arg: String?): MyEntity {
+ val _stringBuilder: StringBuilder = StringBuilder()
+ _stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
+ val _inputSize: Int = arg.size
+ appendPlaceholders(_stringBuilder, _inputSize)
+ _stringBuilder.append(")")
+ val _sql: String = _stringBuilder.toString()
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
+ } else {
+ _stmt.bindText(_argIndex, _item)
+ }
+ _argIndex++
+ }
+ val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpId: String
+ _tmpId = _stmt.getText(_cursorIndexOfId)
+ _result = MyEntity(_tmpId)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override fun primitiveIntArray(arg: IntArray): MyEntity {
+ val _stringBuilder: StringBuilder = StringBuilder()
+ _stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
+ val _inputSize: Int = arg.size
+ appendPlaceholders(_stringBuilder, _inputSize)
+ _stringBuilder.append(")")
+ val _sql: String = _stringBuilder.toString()
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
for (_item: Int in arg) {
_stmt.bindLong(_argIndex, _item.toLong())
_argIndex++
}
+ val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpId: String
+ _tmpId = _stmt.getText(_cursorIndexOfId)
+ _result = MyEntity(_tmpId)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpId: String
- _tmpId = _stmt.getText(_cursorIndexOfId)
- _result = MyEntity(_tmpId)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ }
+
+ public override fun nullablePrimitiveIntArray(arg: IntArray?): MyEntity {
+ val _stringBuilder: StringBuilder = StringBuilder()
+ _stringBuilder.append("SELECT * FROM MyEntity WHERE id IN (")
+ val _inputSize: Int = if (arg == null) 1 else arg.size
+ appendPlaceholders(_stringBuilder, _inputSize)
+ _stringBuilder.append(")")
+ val _sql: String = _stringBuilder.toString()
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ if (arg == null) {
+ _stmt.bindNull(_argIndex)
+ } else {
+ for (_item: Int in arg) {
+ _stmt.bindLong(_argIndex, _item.toLong())
+ _argIndex++
+ }
+ }
+ val _cursorIndexOfId: Int = getColumnIndexOrThrow(_stmt, "id")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpId: String
+ _tmpId = _stmt.getText(_cursorIndexOfId)
+ _result = MyEntity(_tmpId)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/basicParameterAdapter_string.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/basicParameterAdapter_string.kt
index 6ecd81d..c68d7e37 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/basicParameterAdapter_string.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/basicParameterAdapter_string.kt
@@ -1,6 +1,7 @@
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.String
@@ -20,41 +21,51 @@
public override fun stringParam(arg: String): MyEntity {
val _sql: String = "SELECT * FROM MyEntity WHERE string = ?"
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- _stmt.bindText(_argIndex, arg)
- val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpString: String
- _tmpString = _stmt.getText(_cursorIndexOfString)
- _result = MyEntity(_tmpString)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, arg)
+ val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpString: String
+ _tmpString = _stmt.getText(_cursorIndexOfString)
+ _result = MyEntity(_tmpString)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
public override fun nullableStringParam(arg: String?): MyEntity {
val _sql: String = "SELECT * FROM MyEntity WHERE string = ?"
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- if (arg == null) {
- _stmt.bindNull(_argIndex)
- } else {
- _stmt.bindText(_argIndex, arg)
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ if (arg == null) {
+ _stmt.bindNull(_argIndex)
+ } else {
+ _stmt.bindText(_argIndex, arg)
+ }
+ val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpString: String
+ _tmpString = _stmt.getText(_cursorIndexOfString)
+ _result = MyEntity(_tmpString)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpString: String
- _tmpString = _stmt.getText(_cursorIndexOfString)
- _result = MyEntity(_tmpString)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
index 27d4d9d..ea1e6ab 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
@@ -6,13 +6,11 @@
import androidx.room.RxRoom
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
import androidx.room.util.query
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.Observable
import io.reactivex.Single
-import java.lang.StringBuilder
import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -20,6 +18,7 @@
import kotlin.Suppress
import kotlin.collections.List
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -32,7 +31,7 @@
}
public override fun getFlowable(vararg arg: String?): Flowable<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -78,7 +77,7 @@
}
public override fun getObservable(vararg arg: String?): Observable<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -124,7 +123,7 @@
}
public override fun getSingle(vararg arg: String?): Single<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -173,7 +172,7 @@
}
public override fun getMaybe(vararg arg: String?): Maybe<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -219,7 +218,7 @@
}
public override fun getFlowableNullable(vararg arg: String?): Flowable<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -265,7 +264,7 @@
}
public override fun getObservableNullable(vararg arg: String?): Observable<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -311,7 +310,7 @@
}
public override fun getSingleNullable(vararg arg: String?): Single<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -360,7 +359,7 @@
}
public override fun getMaybeNullable(vararg arg: String?): Maybe<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
index 9a67d69..90e23eb 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
@@ -6,13 +6,11 @@
import androidx.room.rxjava3.RxRoom
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
import androidx.room.util.query
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
-import java.lang.StringBuilder
import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -20,6 +18,7 @@
import kotlin.Suppress
import kotlin.collections.List
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -32,7 +31,7 @@
}
public override fun getFlowable(vararg arg: String?): Flowable<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -78,7 +77,7 @@
}
public override fun getObservable(vararg arg: String?): Observable<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -124,7 +123,7 @@
}
public override fun getSingle(vararg arg: String?): Single<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -173,7 +172,7 @@
}
public override fun getMaybe(vararg arg: String?): Maybe<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -219,7 +218,7 @@
}
public override fun getFlowableNullable(vararg arg: String?): Flowable<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -265,7 +264,7 @@
}
public override fun getObservableNullable(vararg arg: String?): Observable<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -311,7 +310,7 @@
}
public override fun getSingleNullable(vararg arg: String?): Single<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -360,7 +359,7 @@
}
public override fun getMaybeNullable(vararg arg: String?): Maybe<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/collectionParameterAdapter_string.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/collectionParameterAdapter_string.kt
index c50d243..919e66b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/collectionParameterAdapter_string.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/collectionParameterAdapter_string.kt
@@ -1,9 +1,8 @@
import androidx.room.RoomDatabase
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
-import androidx.room.util.performReadBlocking
-import java.lang.StringBuilder
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.String
@@ -11,6 +10,7 @@
import kotlin.collections.List
import kotlin.collections.Set
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -23,114 +23,134 @@
}
public override fun listOfString(arg: List<String>): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE string IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- for (_item: String in arg) {
- _stmt.bindText(_argIndex, _item)
- _argIndex++
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String in arg) {
+ _stmt.bindText(_argIndex, _item)
+ _argIndex++
+ }
+ val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpString: String
+ _tmpString = _stmt.getText(_cursorIndexOfString)
+ _result = MyEntity(_tmpString)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpString: String
- _tmpString = _stmt.getText(_cursorIndexOfString)
- _result = MyEntity(_tmpString)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
}
}
public override fun nullableListOfString(arg: List<String>?): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE string IN (")
val _inputSize: Int = if (arg == null) 1 else arg.size
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- if (arg == null) {
- _stmt.bindNull(_argIndex)
- } else {
- for (_item: String in arg) {
- _stmt.bindText(_argIndex, _item)
- _argIndex++
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ if (arg == null) {
+ _stmt.bindNull(_argIndex)
+ } else {
+ for (_item: String in arg) {
+ _stmt.bindText(_argIndex, _item)
+ _argIndex++
+ }
}
+ val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpString: String
+ _tmpString = _stmt.getText(_cursorIndexOfString)
+ _result = MyEntity(_tmpString)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpString: String
- _tmpString = _stmt.getText(_cursorIndexOfString)
- _result = MyEntity(_tmpString)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
}
}
public override fun listOfNullableString(arg: List<String?>): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE string IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _stmt.bindNull(_argIndex)
- } else {
- _stmt.bindText(_argIndex, _item)
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
+ } else {
+ _stmt.bindText(_argIndex, _item)
+ }
+ _argIndex++
}
- _argIndex++
+ val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpString: String
+ _tmpString = _stmt.getText(_cursorIndexOfString)
+ _result = MyEntity(_tmpString)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpString: String
- _tmpString = _stmt.getText(_cursorIndexOfString)
- _result = MyEntity(_tmpString)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
}
}
public override fun setOfString(arg: Set<String>): MyEntity {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE string IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- for (_item: String in arg) {
- _stmt.bindText(_argIndex, _item)
- _argIndex++
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String in arg) {
+ _stmt.bindText(_argIndex, _item)
+ _argIndex++
+ }
+ val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpString: String
+ _tmpString = _stmt.getText(_cursorIndexOfString)
+ _result = MyEntity(_tmpString)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpString: String
- _tmpString = _stmt.getText(_cursorIndexOfString)
- _result = MyEntity(_tmpString)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutineResultBinder.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutineResultBinder.kt
index c130c67..679a698 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutineResultBinder.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutineResultBinder.kt
@@ -1,6 +1,7 @@
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadSuspending
+import androidx.room.util.performSuspending
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.String
@@ -20,17 +21,22 @@
public override suspend fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadSuspending(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- _result = MyEntity(_tmpPk)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ return performSuspending(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ _result = MyEntity(_tmpPk)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
index 25aba5b..0abb567 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
@@ -1,24 +1,26 @@
import android.database.Cursor
-import android.os.CancellationSignal
import androidx.room.CoroutinesRoom
-import androidx.room.CoroutinesRoom.Companion.execute
import androidx.room.RoomDatabase
import androidx.room.RoomSQLiteQuery
import androidx.room.RoomSQLiteQuery.Companion.acquire
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
+import androidx.room.util.getLastInsertedRowId
+import androidx.room.util.getTotalChangedRows
+import androidx.room.util.performSuspending
import androidx.room.util.query
-import java.lang.StringBuilder
-import java.util.ArrayList
+import androidx.sqlite.SQLiteStatement
import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
+import kotlin.Long
import kotlin.String
import kotlin.Suppress
import kotlin.collections.List
import kotlin.collections.MutableList
+import kotlin.collections.mutableListOf
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
import kotlinx.coroutines.flow.Flow
@Generated(value = ["androidx.room.RoomProcessor"])
@@ -32,7 +34,7 @@
}
public override fun getFlow(vararg arg: String?): Flow<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -78,7 +80,7 @@
}
public override fun getFlowNullable(vararg arg: String?): Flow<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -125,47 +127,148 @@
}
public override suspend fun getSuspendList(vararg arg: String?): List<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
_stringBuilder.append(")")
val _sql: String = _stringBuilder.toString()
- val _argCount: Int = 0 + _inputSize
- val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
- var _argIndex: Int = 1
- for (_item: String? in arg) {
- if (_item == null) {
- _statement.bindNull(_argIndex)
- } else {
- _statement.bindString(_argIndex, _item)
- }
- _argIndex++
- }
- val _cancellationSignal: CancellationSignal = CancellationSignal()
- return execute(__db, false, _cancellationSignal, object : Callable<List<MyEntity>> {
- public override fun call(): List<MyEntity> {
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MutableList<MyEntity> = ArrayList<MyEntity>(_cursor.getCount())
- while (_cursor.moveToNext()) {
- val _item_1: MyEntity
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _item_1 = MyEntity(_tmpPk,_tmpOther)
- _result.add(_item_1)
+ return performSuspending(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: String? in arg) {
+ if (_item == null) {
+ _stmt.bindNull(_argIndex)
+ } else {
+ _stmt.bindText(_argIndex, _item)
}
- return _result
- } finally {
- _cursor.close()
- _statement.release()
+ _argIndex++
}
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MutableList<MyEntity> = mutableListOf()
+ while (_stmt.step()) {
+ val _item_1: MyEntity
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _item_1 = MyEntity(_tmpPk,_tmpOther)
+ _result.add(_item_1)
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- })
+ }
+ }
+
+ public override suspend fun insertEntity(pk: Long) {
+ val _sql: String = "INSERT INTO MyEntity (pk) VALUES (?)"
+ return performSuspending(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ _stmt.bindLong(_argIndex, pk)
+ _stmt.step()
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override suspend fun insertEntityReturnLong(pk: Long): Long {
+ val _sql: String = "INSERT INTO MyEntity (pk) VALUES (?)"
+ return performSuspending(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ _stmt.bindLong(_argIndex, pk)
+ _stmt.step()
+ getLastInsertedRowId(_connection)
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override suspend fun updateEntity(text: String) {
+ val _sql: String = "UPDATE MyEntity SET other = ?"
+ return performSuspending(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, text)
+ _stmt.step()
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override suspend fun updateEntityReturnInt(pk: Long, text: String): Int {
+ val _sql: String = "UPDATE MyEntity SET other = ? WHERE pk = ?"
+ return performSuspending(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, text)
+ _argIndex = 2
+ _stmt.bindLong(_argIndex, pk)
+ _stmt.step()
+ getTotalChangedRows(_connection)
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override suspend fun deleteEntity() {
+ val _sql: String = "DELETE FROM MyEntity"
+ return performSuspending(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ _stmt.step()
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override suspend fun deleteEntityReturnInt(): Int {
+ val _sql: String = "DELETE FROM MyEntity"
+ return performSuspending(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ _stmt.step()
+ getTotalChangedRows(_connection)
+ } finally {
+ _stmt.close()
+ }
+ }
+ }
+
+ public override suspend fun deleteEntitiesIn(pks: List<Long>) {
+ val _stringBuilder: StringBuilder = StringBuilder()
+ _stringBuilder.append("DELETE FROM MyEntity WHERE pk IN (")
+ val _inputSize: Int = pks.size
+ appendPlaceholders(_stringBuilder, _inputSize)
+ _stringBuilder.append(")")
+ val _sql: String = _stringBuilder.toString()
+ return performSuspending(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: Long in pks) {
+ _stmt.bindLong(_argIndex, _item)
+ _argIndex++
+ }
+ _stmt.step()
+ } finally {
+ _stmt.close()
+ }
+ }
}
public companion object {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_boxedPrimitiveBridge.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_boxedPrimitiveBridge.kt
index 0df1155..f4115a2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_boxedPrimitiveBridge.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_boxedPrimitiveBridge.kt
@@ -1,8 +1,8 @@
import androidx.room.RoomDatabase
-import androidx.room.SharedSQLiteStatement
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.util.getLastInsertedRowId
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.Long
@@ -17,52 +17,45 @@
__db: RoomDatabase,
) : MyDao {
private val __db: RoomDatabase
-
- private val __preparedStmtOfInsertEntity: SharedSQLiteStatement
init {
this.__db = __db
- this.__preparedStmtOfInsertEntity = object : SharedSQLiteStatement(__db) {
- public override fun createQuery(): String {
- val _query: String = "INSERT INTO MyEntity (pk) VALUES (?)"
- return _query
+ }
+
+ public override fun getEntity(id: Long): MyEntity {
+ val _sql: String = "SELECT * FROM MyEntity WHERE pk = ?"
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ _stmt.bindLong(_argIndex, id)
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Long
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk)
+ _result = MyEntity(_tmpPk)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
}
}
public override fun insertEntity(id: Long): Long {
- __db.assertNotSuspendingTransaction()
- val _stmt: SupportSQLiteStatement = __preparedStmtOfInsertEntity.acquire()
- var _argIndex: Int = 1
- _stmt.bindLong(_argIndex, id)
- try {
- __db.beginTransaction()
+ val _sql: String = "INSERT INTO MyEntity (pk) VALUES (?)"
+ return performBlocking(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- val _result: Long = _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return _result
+ var _argIndex: Int = 1
+ _stmt.bindLong(_argIndex, id)
+ _stmt.step()
+ getLastInsertedRowId(_connection)
} finally {
- __db.endTransaction()
+ _stmt.close()
}
- } finally {
- __preparedStmtOfInsertEntity.release(_stmt)
- }
- }
-
- public override fun getEntity(id: Long): MyEntity {
- val _sql: String = "SELECT * FROM MyEntity WHERE pk = ?"
- return performReadBlocking(__db, _sql) { _stmt ->
- var _argIndex: Int = 1
- _stmt.bindLong(_argIndex, id)
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Long
- _tmpPk = _stmt.getLong(_cursorIndexOfPk)
- _result = MyEntity(_tmpPk)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
- }
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_defaultImplBridge.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_defaultImplBridge.kt
index a7775fc..c91988d2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_defaultImplBridge.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_defaultImplBridge.kt
@@ -1,6 +1,7 @@
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.Long
@@ -21,17 +22,22 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Long
- _tmpPk = _stmt.getLong(_cursorIndexOfPk)
- _result = MyEntity(_tmpPk)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Long
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk)
+ _result = MyEntity(_tmpPk)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
index ebafaaf..d702c28 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
@@ -15,12 +15,12 @@
) : MyDao {
private val __db: RoomDatabase
- private val __deletionAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+ private val __deleteAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
private val __updateAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
init {
this.__db = __db
- this.__deletionAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ this.__deleteAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
@@ -43,7 +43,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __deletionAdapterOfMyEntity.handle(item)
+ __deleteAdapterOfMyEntity.handle(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -55,7 +55,7 @@
var _total: Int = 0
__db.beginTransaction()
try {
- _total += __deletionAdapterOfMyEntity.handle(item)
+ _total += __deleteAdapterOfMyEntity.handle(item)
__db.setTransactionSuccessful()
return _total
} finally {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
index 2ec199a..462d3d1 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
@@ -22,10 +22,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`valuePrimitive`,`valueBoolean`,`valueString`,`valueNullableString`,`variablePrimitive`,`variableNullableBoolean`,`variableString`,`variableNullableString`) VALUES (?,?,?,?,?,?,?,?)"
@@ -63,7 +63,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt
index c140a673..ae37dd9 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/guavaCallable.kt
@@ -9,11 +9,9 @@
import androidx.room.guava.GuavaRoom
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
import androidx.room.util.query
import androidx.sqlite.db.SupportSQLiteStatement
import com.google.common.util.concurrent.ListenableFuture
-import java.lang.StringBuilder
import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -22,6 +20,7 @@
import kotlin.Suppress
import kotlin.collections.List
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -30,16 +29,16 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
- private val __deletionAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+ private val __deleteAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
private val __updateAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
- private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+ private val __upsertAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -48,7 +47,7 @@
statement.bindString(2, entity.other)
}
}
- this.__deletionAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ this.__deleteAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
@@ -65,7 +64,7 @@
statement.bindLong(3, entity.pk.toLong())
}
}
- this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
+ this.__upsertAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -92,7 +91,7 @@
public override fun call(): List<Long> {
__db.beginTransaction()
try {
- val _result: List<Long> = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
+ val _result: List<Long> = __insertAdapterOfMyEntity.insertAndReturnIdsList(entities)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -107,7 +106,7 @@
var _total: Int = 0
__db.beginTransaction()
try {
- _total += __deletionAdapterOfMyEntity.handle(entity)
+ _total += __deleteAdapterOfMyEntity.handle(entity)
__db.setTransactionSuccessful()
return _total
} finally {
@@ -137,7 +136,7 @@
public override fun call(): List<Long> {
__db.beginTransaction()
try {
- val _result: List<Long> = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
+ val _result: List<Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(entities)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -147,7 +146,7 @@
})
public override fun getListenableFuture(vararg arg: String?): ListenableFuture<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -191,7 +190,7 @@
public override fun getListenableFutureNullable(vararg arg: String?):
ListenableFuture<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
index 9232de4..9fcec8d 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
@@ -18,12 +18,12 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
- private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+ private val __upsertAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`data`) VALUES (?,?)"
@@ -32,7 +32,7 @@
statement.bindString(2, entity.data)
}
}
- this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
+ this.__upsertAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`data`) VALUES (?,?)"
@@ -57,7 +57,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -68,7 +68,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- val _result: Long = __insertionAdapterOfMyEntity.insertAndReturnId(item)
+ val _result: Long = __insertAdapterOfMyEntity.insertAndReturnId(item)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -80,7 +80,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- val _result: List<Long> = __insertionAdapterOfMyEntity.insertAndReturnIdsList(items)
+ val _result: List<Long> = __insertAdapterOfMyEntity.insertAndReturnIdsList(items)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -92,7 +92,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __upsertionAdapterOfMyEntity.upsert(item)
+ __upsertAdapterOfMyEntity.upsert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -103,7 +103,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- val _result: Long = __upsertionAdapterOfMyEntity.upsertAndReturnId(item)
+ val _result: Long = __upsertAdapterOfMyEntity.upsertAndReturnId(item)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -115,7 +115,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- val _result: List<Long> = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(items)
+ val _result: List<Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(items)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -127,7 +127,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- val _result: Array<Long> = (__upsertionAdapterOfMyEntity.upsertAndReturnIdsArrayBox(items)) as
+ val _result: Array<Long> = (__upsertAdapterOfMyEntity.upsertAndReturnIdsArrayBox(items)) as
Array<Long>
__db.setTransactionSuccessful()
return _result
@@ -141,7 +141,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- val _result: Array<out Long> = __upsertionAdapterOfMyEntity.upsertAndReturnIdsArrayBox(items)
+ val _result: Array<out Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsArrayBox(items)
__db.setTransactionSuccessful()
return _result
} finally {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
index f21c664..e49ab6e 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
@@ -5,9 +5,7 @@
import androidx.room.RoomSQLiteQuery.Companion.acquire
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
import androidx.room.util.query
-import java.lang.StringBuilder
import java.util.concurrent.Callable
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -15,6 +13,7 @@
import kotlin.Suppress
import kotlin.collections.List
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -27,7 +26,7 @@
}
public override fun getLiveData(vararg arg: String?): LiveData<MyEntity> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -74,7 +73,7 @@
}
public override fun getLiveDataNullable(vararg arg: String?): LiveData<MyEntity?> {
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
val _inputSize: Int = arg.size
appendPlaceholders(_stringBuilder, _inputSize)
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
index 5f0d758..382538f 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
@@ -6,13 +6,13 @@
import androidx.room.RoomSQLiteQuery.Companion.acquire
import androidx.room.paging.LimitOffsetPagingSource
import androidx.room.paging.guava.LimitOffsetListenableFuturePagingSource
-import java.util.ArrayList
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.String
import kotlin.Suppress
import kotlin.collections.List
import kotlin.collections.MutableList
+import kotlin.collections.mutableListOf
import kotlin.reflect.KClass
import androidx.paging.rxjava2.RxPagingSource as Rxjava2RxPagingSource
import androidx.paging.rxjava3.RxPagingSource as Rxjava3RxPagingSource
@@ -35,7 +35,7 @@
return object : LimitOffsetPagingSource<MyEntity>(_statement, __db, "MyEntity") {
protected override fun convertRows(cursor: Cursor): List<MyEntity> {
val _cursorIndexOfPk: Int = 0
- val _result: MutableList<MyEntity> = ArrayList<MyEntity>(cursor.getCount())
+ val _result: MutableList<MyEntity> = mutableListOf()
while (cursor.moveToNext()) {
val _item: MyEntity
val _tmpPk: Int
@@ -54,7 +54,7 @@
return object : Rxjava2LimitOffsetRxPagingSource<MyEntity>(_statement, __db, "MyEntity") {
protected override fun convertRows(cursor: Cursor): List<MyEntity> {
val _cursorIndexOfPk: Int = 0
- val _result: MutableList<MyEntity> = ArrayList<MyEntity>(cursor.getCount())
+ val _result: MutableList<MyEntity> = mutableListOf()
while (cursor.moveToNext()) {
val _item: MyEntity
val _tmpPk: Int
@@ -73,7 +73,7 @@
return object : Rxjava3LimitOffsetRxPagingSource<MyEntity>(_statement, __db, "MyEntity") {
protected override fun convertRows(cursor: Cursor): List<MyEntity> {
val _cursorIndexOfPk: Int = 0
- val _result: MutableList<MyEntity> = ArrayList<MyEntity>(cursor.getCount())
+ val _result: MutableList<MyEntity> = mutableListOf()
while (cursor.moveToNext()) {
val _item: MyEntity
val _tmpPk: Int
@@ -93,7 +93,7 @@
{
protected override fun convertRows(cursor: Cursor): List<MyEntity> {
val _cursorIndexOfPk: Int = 0
- val _result: MutableList<MyEntity> = ArrayList<MyEntity>(cursor.getCount())
+ val _result: MutableList<MyEntity> = mutableListOf()
while (cursor.moveToNext()) {
val _item: MyEntity
val _tmpPk: Int
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/paging_dataSource.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/paging_dataSource.kt
index ddefe13..5eccef9 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/paging_dataSource.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/paging_dataSource.kt
@@ -5,13 +5,13 @@
import androidx.room.RoomSQLiteQuery.Companion.acquire
import androidx.room.paging.LimitOffsetDataSource
import androidx.room.util.getColumnIndexOrThrow
-import java.util.ArrayList
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.String
import kotlin.Suppress
import kotlin.collections.List
import kotlin.collections.MutableList
+import kotlin.collections.mutableListOf
import kotlin.reflect.KClass
@Generated(value = ["androidx.room.RoomProcessor"])
@@ -33,7 +33,7 @@
protected override fun convertRows(cursor: Cursor): List<MyEntity> {
val _cursorIndexOfPk: Int = getColumnIndexOrThrow(cursor, "pk")
val _cursorIndexOfOther: Int = getColumnIndexOrThrow(cursor, "other")
- val _res: MutableList<MyEntity> = ArrayList<MyEntity>(cursor.getCount())
+ val _res: MutableList<MyEntity> = mutableListOf()
while (cursor.moveToNext()) {
val _item: MyEntity
val _tmpPk: Int
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
index aad0dc3..b1e1adc 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Boolean
@@ -18,10 +19,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`boolean`,`nullableBoolean`) VALUES (?,?,?)"
@@ -44,7 +45,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -53,31 +54,36 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfBoolean: Int = getColumnIndexOrThrow(_stmt, "boolean")
- val _cursorIndexOfNullableBoolean: Int = getColumnIndexOrThrow(_stmt, "nullableBoolean")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpBoolean: Boolean
- val _tmp: Int
- _tmp = _stmt.getLong(_cursorIndexOfBoolean).toInt()
- _tmpBoolean = _tmp != 0
- val _tmpNullableBoolean: Boolean?
- val _tmp_1: Int?
- if (_stmt.isNull(_cursorIndexOfNullableBoolean)) {
- _tmp_1 = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfBoolean: Int = getColumnIndexOrThrow(_stmt, "boolean")
+ val _cursorIndexOfNullableBoolean: Int = getColumnIndexOrThrow(_stmt, "nullableBoolean")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpBoolean: Boolean
+ val _tmp: Int
+ _tmp = _stmt.getLong(_cursorIndexOfBoolean).toInt()
+ _tmpBoolean = _tmp != 0
+ val _tmpNullableBoolean: Boolean?
+ val _tmp_1: Int?
+ if (_stmt.isNull(_cursorIndexOfNullableBoolean)) {
+ _tmp_1 = null
+ } else {
+ _tmp_1 = _stmt.getLong(_cursorIndexOfNullableBoolean).toInt()
+ }
+ _tmpNullableBoolean = _tmp_1?.let { it != 0 }
+ _result = MyEntity(_tmpPk,_tmpBoolean,_tmpNullableBoolean)
} else {
- _tmp_1 = _stmt.getLong(_cursorIndexOfNullableBoolean).toInt()
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- _tmpNullableBoolean = _tmp_1?.let { it != 0 }
- _result = MyEntity(_tmpPk,_tmpBoolean,_tmpNullableBoolean)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
index 7eb2cc7..18ff43b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.ByteArray
@@ -18,10 +19,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`byteArray`,`nullableByteArray`) VALUES (?,?,?)"
@@ -42,7 +43,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -51,27 +52,32 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfByteArray: Int = getColumnIndexOrThrow(_stmt, "byteArray")
- val _cursorIndexOfNullableByteArray: Int = getColumnIndexOrThrow(_stmt, "nullableByteArray")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpByteArray: ByteArray
- _tmpByteArray = _stmt.getBlob(_cursorIndexOfByteArray)
- val _tmpNullableByteArray: ByteArray?
- if (_stmt.isNull(_cursorIndexOfNullableByteArray)) {
- _tmpNullableByteArray = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfByteArray: Int = getColumnIndexOrThrow(_stmt, "byteArray")
+ val _cursorIndexOfNullableByteArray: Int = getColumnIndexOrThrow(_stmt, "nullableByteArray")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpByteArray: ByteArray
+ _tmpByteArray = _stmt.getBlob(_cursorIndexOfByteArray)
+ val _tmpNullableByteArray: ByteArray?
+ if (_stmt.isNull(_cursorIndexOfNullableByteArray)) {
+ _tmpNullableByteArray = null
+ } else {
+ _tmpNullableByteArray = _stmt.getBlob(_cursorIndexOfNullableByteArray)
+ }
+ _result = MyEntity(_tmpPk,_tmpByteArray,_tmpNullableByteArray)
} else {
- _tmpNullableByteArray = _stmt.getBlob(_cursorIndexOfNullableByteArray)
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- _result = MyEntity(_tmpPk,_tmpByteArray,_tmpNullableByteArray)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
index 4aa26d3..e2713a9 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -17,12 +18,12 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
private val __fooConverter: FooConverter = FooConverter()
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
@@ -38,7 +39,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -47,22 +48,27 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfFoo: Int = getColumnIndexOrThrow(_stmt, "foo")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpFoo: Foo
- val _tmp: String
- _tmp = _stmt.getText(_cursorIndexOfFoo)
- _tmpFoo = __fooConverter.fromString(_tmp)
- _result = MyEntity(_tmpPk,_tmpFoo)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfFoo: Int = getColumnIndexOrThrow(_stmt, "foo")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpFoo: Foo
+ val _tmp: String
+ _tmp = _stmt.getText(_cursorIndexOfFoo)
+ _tmpFoo = __fooConverter.fromString(_tmp)
+ _result = MyEntity(_tmpPk,_tmpFoo)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
index 49d9278..a559701 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -17,10 +18,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`bar`) VALUES (?,?)"
@@ -37,7 +38,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -46,23 +47,28 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfBar: Int = getColumnIndexOrThrow(_stmt, "bar")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpBar: Bar
- val _tmp: String
- _tmp = _stmt.getText(_cursorIndexOfBar)
- val _tmp_1: Foo = FooBarConverter.fromString(_tmp)
- _tmpBar = FooBarConverter.fromFoo(_tmp_1)
- _result = MyEntity(_tmpPk,_tmpBar)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfBar: Int = getColumnIndexOrThrow(_stmt, "bar")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpBar: Bar
+ val _tmp: String
+ _tmp = _stmt.getText(_cursorIndexOfBar)
+ val _tmp_1: Foo = FooBarConverter.fromString(_tmp)
+ _tmpBar = FooBarConverter.fromFoo(_tmp_1)
+ _result = MyEntity(_tmpPk,_tmpBar)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt
index 62fff6bf0..031c7fd 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_internalVisibility.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -17,12 +18,12 @@
) : MyDao() {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
private val __fooConverter: FooConverter = FooConverter()
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
@@ -38,7 +39,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -47,22 +48,27 @@
internal override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfFoo: Int = getColumnIndexOrThrow(_stmt, "foo")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpFoo: Foo
- val _tmp: String
- _tmp = _stmt.getText(_cursorIndexOfFoo)
- _tmpFoo = __fooConverter.fromString(_tmp)
- _result = MyEntity(_tmpPk,_tmpFoo)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfFoo: Int = getColumnIndexOrThrow(_stmt, "foo")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpFoo: Foo
+ val _tmp: String
+ _tmp = _stmt.getText(_cursorIndexOfFoo)
+ _tmpFoo = __fooConverter.fromString(_tmp)
+ _result = MyEntity(_tmpPk,_tmpFoo)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
index c8784aa..0e2caeb 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -17,10 +18,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`,`bar`) VALUES (?,?,?)"
@@ -47,7 +48,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -56,51 +57,56 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfFoo: Int = getColumnIndexOrThrow(_stmt, "foo")
- val _cursorIndexOfBar: Int = getColumnIndexOrThrow(_stmt, "bar")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpFoo: Foo
- val _tmp: String?
- if (_stmt.isNull(_cursorIndexOfFoo)) {
- _tmp = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfFoo: Int = getColumnIndexOrThrow(_stmt, "foo")
+ val _cursorIndexOfBar: Int = getColumnIndexOrThrow(_stmt, "bar")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpFoo: Foo
+ val _tmp: String?
+ if (_stmt.isNull(_cursorIndexOfFoo)) {
+ _tmp = null
+ } else {
+ _tmp = _stmt.getText(_cursorIndexOfFoo)
+ }
+ val _tmp_1: Foo? = FooBarConverter.fromString(_tmp)
+ if (_tmp_1 == null) {
+ error("Expected NON-NULL 'Foo', but it was NULL.")
+ } else {
+ _tmpFoo = _tmp_1
+ }
+ val _tmpBar: Bar
+ val _tmp_2: String?
+ if (_stmt.isNull(_cursorIndexOfBar)) {
+ _tmp_2 = null
+ } else {
+ _tmp_2 = _stmt.getText(_cursorIndexOfBar)
+ }
+ val _tmp_3: Foo? = FooBarConverter.fromString(_tmp_2)
+ val _tmp_4: Bar?
+ if (_tmp_3 == null) {
+ _tmp_4 = null
+ } else {
+ _tmp_4 = FooBarConverter.fromFoo(_tmp_3)
+ }
+ if (_tmp_4 == null) {
+ error("Expected NON-NULL 'Bar', but it was NULL.")
+ } else {
+ _tmpBar = _tmp_4
+ }
+ _result = MyEntity(_tmpPk,_tmpFoo,_tmpBar)
} else {
- _tmp = _stmt.getText(_cursorIndexOfFoo)
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- val _tmp_1: Foo? = FooBarConverter.fromString(_tmp)
- if (_tmp_1 == null) {
- error("Expected NON-NULL 'Foo', but it was NULL.")
- } else {
- _tmpFoo = _tmp_1
- }
- val _tmpBar: Bar
- val _tmp_2: String?
- if (_stmt.isNull(_cursorIndexOfBar)) {
- _tmp_2 = null
- } else {
- _tmp_2 = _stmt.getText(_cursorIndexOfBar)
- }
- val _tmp_3: Foo? = FooBarConverter.fromString(_tmp_2)
- val _tmp_4: Bar?
- if (_tmp_3 == null) {
- _tmp_4 = null
- } else {
- _tmp_4 = FooBarConverter.fromFoo(_tmp_3)
- }
- if (_tmp_4 == null) {
- error("Expected NON-NULL 'Bar', but it was NULL.")
- } else {
- _tmpBar = _tmp_4
- }
- _result = MyEntity(_tmpPk,_tmpFoo,_tmpBar)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
index 9343803..6c38251 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -18,7 +19,7 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
private val __fooConverter: Lazy<FooConverter> = lazy {
checkNotNull(__db.getTypeConverter(FooConverter::class))
@@ -26,7 +27,7 @@
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
@@ -42,7 +43,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -51,22 +52,27 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfFoo: Int = getColumnIndexOrThrow(_stmt, "foo")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpFoo: Foo
- val _tmp: String
- _tmp = _stmt.getText(_cursorIndexOfFoo)
- _tmpFoo = __fooConverter().fromString(_tmp)
- _result = MyEntity(_tmpPk,_tmpFoo)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfFoo: Int = getColumnIndexOrThrow(_stmt, "foo")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpFoo: Foo
+ val _tmp: String
+ _tmp = _stmt.getText(_cursorIndexOfFoo)
+ _tmpFoo = __fooConverter().fromString(_tmp)
+ _result = MyEntity(_tmpPk,_tmpFoo)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt
index e9eebbc..339e1f1 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt
@@ -21,10 +21,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
public override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`foo`) VALUES (?,?)"
@@ -44,7 +44,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
index 0db09a5..47f863c 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -18,10 +19,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`numberData`,`stringData`,`nullablenumberData`,`nullablestringData`) VALUES (?,?,?,?,?)"
@@ -46,7 +47,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -55,38 +56,43 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfNumberData: Int = getColumnIndexOrThrow(_stmt, "numberData")
- val _cursorIndexOfStringData: Int = getColumnIndexOrThrow(_stmt, "stringData")
- val _cursorIndexOfNumberData_1: Int = getColumnIndexOrThrow(_stmt, "nullablenumberData")
- val _cursorIndexOfStringData_1: Int = getColumnIndexOrThrow(_stmt, "nullablestringData")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpFoo: Foo
- val _tmpNumberData: Long
- _tmpNumberData = _stmt.getLong(_cursorIndexOfNumberData)
- val _tmpStringData: String
- _tmpStringData = _stmt.getText(_cursorIndexOfStringData)
- _tmpFoo = Foo(_tmpNumberData,_tmpStringData)
- val _tmpNullableFoo: Foo?
- if (!(_stmt.isNull(_cursorIndexOfNumberData_1) &&
- _stmt.isNull(_cursorIndexOfStringData_1))) {
- val _tmpNumberData_1: Long
- _tmpNumberData_1 = _stmt.getLong(_cursorIndexOfNumberData_1)
- val _tmpStringData_1: String
- _tmpStringData_1 = _stmt.getText(_cursorIndexOfStringData_1)
- _tmpNullableFoo = Foo(_tmpNumberData_1,_tmpStringData_1)
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfNumberData: Int = getColumnIndexOrThrow(_stmt, "numberData")
+ val _cursorIndexOfStringData: Int = getColumnIndexOrThrow(_stmt, "stringData")
+ val _cursorIndexOfNumberData_1: Int = getColumnIndexOrThrow(_stmt, "nullablenumberData")
+ val _cursorIndexOfStringData_1: Int = getColumnIndexOrThrow(_stmt, "nullablestringData")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpFoo: Foo
+ val _tmpNumberData: Long
+ _tmpNumberData = _stmt.getLong(_cursorIndexOfNumberData)
+ val _tmpStringData: String
+ _tmpStringData = _stmt.getText(_cursorIndexOfStringData)
+ _tmpFoo = Foo(_tmpNumberData,_tmpStringData)
+ val _tmpNullableFoo: Foo?
+ if (!(_stmt.isNull(_cursorIndexOfNumberData_1) &&
+ _stmt.isNull(_cursorIndexOfStringData_1))) {
+ val _tmpNumberData_1: Long
+ _tmpNumberData_1 = _stmt.getLong(_cursorIndexOfNumberData_1)
+ val _tmpStringData_1: String
+ _tmpStringData_1 = _stmt.getText(_cursorIndexOfStringData_1)
+ _tmpNullableFoo = Foo(_tmpNumberData_1,_tmpStringData_1)
+ } else {
+ _tmpNullableFoo = null
+ }
+ _result = MyEntity(_tmpPk,_tmpFoo,_tmpNullableFoo)
} else {
- _tmpNullableFoo = null
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- _result = MyEntity(_tmpPk,_tmpFoo,_tmpNullableFoo)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
index 4e51605..a7c26e8 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
@@ -1,10 +1,11 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
-import java.lang.IllegalArgumentException
import javax.`annotation`.processing.Generated
+import kotlin.IllegalArgumentException
import kotlin.Int
import kotlin.String
import kotlin.Suppress
@@ -18,10 +19,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`enum`,`nullableEnum`) VALUES (?,?,?)"
@@ -42,7 +43,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -51,27 +52,32 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfEnum: Int = getColumnIndexOrThrow(_stmt, "enum")
- val _cursorIndexOfNullableEnum: Int = getColumnIndexOrThrow(_stmt, "nullableEnum")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpEnum: Fruit
- _tmpEnum = __Fruit_stringToEnum(_stmt.getText(_cursorIndexOfEnum))
- val _tmpNullableEnum: Fruit?
- if (_stmt.isNull(_cursorIndexOfNullableEnum)) {
- _tmpNullableEnum = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfEnum: Int = getColumnIndexOrThrow(_stmt, "enum")
+ val _cursorIndexOfNullableEnum: Int = getColumnIndexOrThrow(_stmt, "nullableEnum")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpEnum: Fruit
+ _tmpEnum = __Fruit_stringToEnum(_stmt.getText(_cursorIndexOfEnum))
+ val _tmpNullableEnum: Fruit?
+ if (_stmt.isNull(_cursorIndexOfNullableEnum)) {
+ _tmpNullableEnum = null
+ } else {
+ _tmpNullableEnum = __Fruit_stringToEnum(_stmt.getText(_cursorIndexOfNullableEnum))
+ }
+ _result = MyEntity(_tmpPk,_tmpEnum,_tmpNullableEnum)
} else {
- _tmpNullableEnum = __Fruit_stringToEnum(_stmt.getText(_cursorIndexOfNullableEnum))
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- _result = MyEntity(_tmpPk,_tmpEnum,_tmpNullableEnum)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
index 5d46ece..92a6c7a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -18,10 +19,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`internalVal`,`internalVar`,`internalSetterVar`) VALUES (?,?,?,?)"
@@ -38,7 +39,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -47,24 +48,29 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfInternalVal: Int = getColumnIndexOrThrow(_stmt, "internalVal")
- val _cursorIndexOfInternalVar: Int = getColumnIndexOrThrow(_stmt, "internalVar")
- val _cursorIndexOfInternalSetterVar: Int = getColumnIndexOrThrow(_stmt, "internalSetterVar")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpInternalVal: Long
- _tmpInternalVal = _stmt.getLong(_cursorIndexOfInternalVal)
- _result = MyEntity(_tmpPk,_tmpInternalVal)
- _result.internalVar = _stmt.getLong(_cursorIndexOfInternalVar)
- _result.internalSetterVar = _stmt.getLong(_cursorIndexOfInternalSetterVar)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfInternalVal: Int = getColumnIndexOrThrow(_stmt, "internalVal")
+ val _cursorIndexOfInternalVar: Int = getColumnIndexOrThrow(_stmt, "internalVar")
+ val _cursorIndexOfInternalSetterVar: Int = getColumnIndexOrThrow(_stmt, "internalSetterVar")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpInternalVal: Long
+ _tmpInternalVal = _stmt.getLong(_cursorIndexOfInternalVal)
+ _result = MyEntity(_tmpPk,_tmpInternalVal)
+ _result.internalVar = _stmt.getLong(_cursorIndexOfInternalVar)
+ _result.internalSetterVar = _stmt.getLong(_cursorIndexOfInternalSetterVar)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt
index fb297a9..5c4799f 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_otherModule.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -18,10 +19,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`primitive`,`string`,`nullableString`,`fieldString`,`nullableFieldString`,`variablePrimitive`,`variableString`,`variableNullableString`,`variableFieldString`,`variableNullableFieldString`) VALUES (?,?,?,?,?,?,?,?,?,?,?)"
@@ -65,7 +66,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -74,64 +75,69 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfPrimitive: Int = getColumnIndexOrThrow(_stmt, "primitive")
- val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
- val _cursorIndexOfNullableString: Int = getColumnIndexOrThrow(_stmt, "nullableString")
- val _cursorIndexOfFieldString: Int = getColumnIndexOrThrow(_stmt, "fieldString")
- val _cursorIndexOfNullableFieldString: Int = getColumnIndexOrThrow(_stmt,
- "nullableFieldString")
- val _cursorIndexOfVariablePrimitive: Int = getColumnIndexOrThrow(_stmt, "variablePrimitive")
- val _cursorIndexOfVariableString: Int = getColumnIndexOrThrow(_stmt, "variableString")
- val _cursorIndexOfVariableNullableString: Int = getColumnIndexOrThrow(_stmt,
- "variableNullableString")
- val _cursorIndexOfVariableFieldString: Int = getColumnIndexOrThrow(_stmt,
- "variableFieldString")
- val _cursorIndexOfVariableNullableFieldString: Int = getColumnIndexOrThrow(_stmt,
- "variableNullableFieldString")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpPrimitive: Long
- _tmpPrimitive = _stmt.getLong(_cursorIndexOfPrimitive)
- val _tmpString: String
- _tmpString = _stmt.getText(_cursorIndexOfString)
- val _tmpNullableString: String?
- if (_stmt.isNull(_cursorIndexOfNullableString)) {
- _tmpNullableString = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfPrimitive: Int = getColumnIndexOrThrow(_stmt, "primitive")
+ val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
+ val _cursorIndexOfNullableString: Int = getColumnIndexOrThrow(_stmt, "nullableString")
+ val _cursorIndexOfFieldString: Int = getColumnIndexOrThrow(_stmt, "fieldString")
+ val _cursorIndexOfNullableFieldString: Int = getColumnIndexOrThrow(_stmt,
+ "nullableFieldString")
+ val _cursorIndexOfVariablePrimitive: Int = getColumnIndexOrThrow(_stmt, "variablePrimitive")
+ val _cursorIndexOfVariableString: Int = getColumnIndexOrThrow(_stmt, "variableString")
+ val _cursorIndexOfVariableNullableString: Int = getColumnIndexOrThrow(_stmt,
+ "variableNullableString")
+ val _cursorIndexOfVariableFieldString: Int = getColumnIndexOrThrow(_stmt,
+ "variableFieldString")
+ val _cursorIndexOfVariableNullableFieldString: Int = getColumnIndexOrThrow(_stmt,
+ "variableNullableFieldString")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpPrimitive: Long
+ _tmpPrimitive = _stmt.getLong(_cursorIndexOfPrimitive)
+ val _tmpString: String
+ _tmpString = _stmt.getText(_cursorIndexOfString)
+ val _tmpNullableString: String?
+ if (_stmt.isNull(_cursorIndexOfNullableString)) {
+ _tmpNullableString = null
+ } else {
+ _tmpNullableString = _stmt.getText(_cursorIndexOfNullableString)
+ }
+ val _tmpFieldString: String
+ _tmpFieldString = _stmt.getText(_cursorIndexOfFieldString)
+ val _tmpNullableFieldString: String?
+ if (_stmt.isNull(_cursorIndexOfNullableFieldString)) {
+ _tmpNullableFieldString = null
+ } else {
+ _tmpNullableFieldString = _stmt.getText(_cursorIndexOfNullableFieldString)
+ }
+ _result =
+ MyEntity(_tmpPk,_tmpPrimitive,_tmpString,_tmpNullableString,_tmpFieldString,_tmpNullableFieldString)
+ _result.variablePrimitive = _stmt.getLong(_cursorIndexOfVariablePrimitive)
+ _result.variableString = _stmt.getText(_cursorIndexOfVariableString)
+ if (_stmt.isNull(_cursorIndexOfVariableNullableString)) {
+ _result.variableNullableString = null
+ } else {
+ _result.variableNullableString = _stmt.getText(_cursorIndexOfVariableNullableString)
+ }
+ _result.variableFieldString = _stmt.getText(_cursorIndexOfVariableFieldString)
+ if (_stmt.isNull(_cursorIndexOfVariableNullableFieldString)) {
+ _result.variableNullableFieldString = null
+ } else {
+ _result.variableNullableFieldString =
+ _stmt.getText(_cursorIndexOfVariableNullableFieldString)
+ }
} else {
- _tmpNullableString = _stmt.getText(_cursorIndexOfNullableString)
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- val _tmpFieldString: String
- _tmpFieldString = _stmt.getText(_cursorIndexOfFieldString)
- val _tmpNullableFieldString: String?
- if (_stmt.isNull(_cursorIndexOfNullableFieldString)) {
- _tmpNullableFieldString = null
- } else {
- _tmpNullableFieldString = _stmt.getText(_cursorIndexOfNullableFieldString)
- }
- _result =
- MyEntity(_tmpPk,_tmpPrimitive,_tmpString,_tmpNullableString,_tmpFieldString,_tmpNullableFieldString)
- _result.variablePrimitive = _stmt.getLong(_cursorIndexOfVariablePrimitive)
- _result.variableString = _stmt.getText(_cursorIndexOfVariableString)
- if (_stmt.isNull(_cursorIndexOfVariableNullableString)) {
- _result.variableNullableString = null
- } else {
- _result.variableNullableString = _stmt.getText(_cursorIndexOfVariableNullableString)
- }
- _result.variableFieldString = _stmt.getText(_cursorIndexOfVariableFieldString)
- if (_stmt.isNull(_cursorIndexOfVariableNullableFieldString)) {
- _result.variableNullableFieldString = null
- } else {
- _result.variableNullableFieldString =
- _stmt.getText(_cursorIndexOfVariableNullableFieldString)
- }
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
index 406fc14..212034f 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Byte
@@ -23,10 +24,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`int`,`short`,`byte`,`long`,`char`,`float`,`double`) VALUES (?,?,?,?,?,?,?)"
@@ -46,7 +47,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -55,35 +56,40 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfInt: Int = getColumnIndexOrThrow(_stmt, "int")
- val _cursorIndexOfShort: Int = getColumnIndexOrThrow(_stmt, "short")
- val _cursorIndexOfByte: Int = getColumnIndexOrThrow(_stmt, "byte")
- val _cursorIndexOfLong: Int = getColumnIndexOrThrow(_stmt, "long")
- val _cursorIndexOfChar: Int = getColumnIndexOrThrow(_stmt, "char")
- val _cursorIndexOfFloat: Int = getColumnIndexOrThrow(_stmt, "float")
- val _cursorIndexOfDouble: Int = getColumnIndexOrThrow(_stmt, "double")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpInt: Int
- _tmpInt = _stmt.getLong(_cursorIndexOfInt).toInt()
- val _tmpShort: Short
- _tmpShort = _stmt.getLong(_cursorIndexOfShort).toShort()
- val _tmpByte: Byte
- _tmpByte = _stmt.getLong(_cursorIndexOfByte).toByte()
- val _tmpLong: Long
- _tmpLong = _stmt.getLong(_cursorIndexOfLong)
- val _tmpChar: Char
- _tmpChar = _stmt.getLong(_cursorIndexOfChar).toChar()
- val _tmpFloat: Float
- _tmpFloat = _stmt.getDouble(_cursorIndexOfFloat).toFloat()
- val _tmpDouble: Double
- _tmpDouble = _stmt.getDouble(_cursorIndexOfDouble)
- _result = MyEntity(_tmpInt,_tmpShort,_tmpByte,_tmpLong,_tmpChar,_tmpFloat,_tmpDouble)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfInt: Int = getColumnIndexOrThrow(_stmt, "int")
+ val _cursorIndexOfShort: Int = getColumnIndexOrThrow(_stmt, "short")
+ val _cursorIndexOfByte: Int = getColumnIndexOrThrow(_stmt, "byte")
+ val _cursorIndexOfLong: Int = getColumnIndexOrThrow(_stmt, "long")
+ val _cursorIndexOfChar: Int = getColumnIndexOrThrow(_stmt, "char")
+ val _cursorIndexOfFloat: Int = getColumnIndexOrThrow(_stmt, "float")
+ val _cursorIndexOfDouble: Int = getColumnIndexOrThrow(_stmt, "double")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpInt: Int
+ _tmpInt = _stmt.getLong(_cursorIndexOfInt).toInt()
+ val _tmpShort: Short
+ _tmpShort = _stmt.getLong(_cursorIndexOfShort).toShort()
+ val _tmpByte: Byte
+ _tmpByte = _stmt.getLong(_cursorIndexOfByte).toByte()
+ val _tmpLong: Long
+ _tmpLong = _stmt.getLong(_cursorIndexOfLong)
+ val _tmpChar: Char
+ _tmpChar = _stmt.getLong(_cursorIndexOfChar).toChar()
+ val _tmpFloat: Float
+ _tmpFloat = _stmt.getDouble(_cursorIndexOfFloat).toFloat()
+ val _tmpDouble: Double
+ _tmpDouble = _stmt.getDouble(_cursorIndexOfDouble)
+ _result = MyEntity(_tmpInt,_tmpShort,_tmpByte,_tmpLong,_tmpChar,_tmpFloat,_tmpDouble)
+ } else {
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
index fbbe920..db9bec8 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Byte
@@ -23,10 +24,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`int`,`short`,`byte`,`long`,`char`,`float`,`double`) VALUES (?,?,?,?,?,?,?)"
@@ -81,7 +82,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -90,63 +91,68 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfInt: Int = getColumnIndexOrThrow(_stmt, "int")
- val _cursorIndexOfShort: Int = getColumnIndexOrThrow(_stmt, "short")
- val _cursorIndexOfByte: Int = getColumnIndexOrThrow(_stmt, "byte")
- val _cursorIndexOfLong: Int = getColumnIndexOrThrow(_stmt, "long")
- val _cursorIndexOfChar: Int = getColumnIndexOrThrow(_stmt, "char")
- val _cursorIndexOfFloat: Int = getColumnIndexOrThrow(_stmt, "float")
- val _cursorIndexOfDouble: Int = getColumnIndexOrThrow(_stmt, "double")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpInt: Int?
- if (_stmt.isNull(_cursorIndexOfInt)) {
- _tmpInt = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfInt: Int = getColumnIndexOrThrow(_stmt, "int")
+ val _cursorIndexOfShort: Int = getColumnIndexOrThrow(_stmt, "short")
+ val _cursorIndexOfByte: Int = getColumnIndexOrThrow(_stmt, "byte")
+ val _cursorIndexOfLong: Int = getColumnIndexOrThrow(_stmt, "long")
+ val _cursorIndexOfChar: Int = getColumnIndexOrThrow(_stmt, "char")
+ val _cursorIndexOfFloat: Int = getColumnIndexOrThrow(_stmt, "float")
+ val _cursorIndexOfDouble: Int = getColumnIndexOrThrow(_stmt, "double")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpInt: Int?
+ if (_stmt.isNull(_cursorIndexOfInt)) {
+ _tmpInt = null
+ } else {
+ _tmpInt = _stmt.getLong(_cursorIndexOfInt).toInt()
+ }
+ val _tmpShort: Short?
+ if (_stmt.isNull(_cursorIndexOfShort)) {
+ _tmpShort = null
+ } else {
+ _tmpShort = _stmt.getLong(_cursorIndexOfShort).toShort()
+ }
+ val _tmpByte: Byte?
+ if (_stmt.isNull(_cursorIndexOfByte)) {
+ _tmpByte = null
+ } else {
+ _tmpByte = _stmt.getLong(_cursorIndexOfByte).toByte()
+ }
+ val _tmpLong: Long?
+ if (_stmt.isNull(_cursorIndexOfLong)) {
+ _tmpLong = null
+ } else {
+ _tmpLong = _stmt.getLong(_cursorIndexOfLong)
+ }
+ val _tmpChar: Char?
+ if (_stmt.isNull(_cursorIndexOfChar)) {
+ _tmpChar = null
+ } else {
+ _tmpChar = _stmt.getLong(_cursorIndexOfChar).toChar()
+ }
+ val _tmpFloat: Float?
+ if (_stmt.isNull(_cursorIndexOfFloat)) {
+ _tmpFloat = null
+ } else {
+ _tmpFloat = _stmt.getDouble(_cursorIndexOfFloat).toFloat()
+ }
+ val _tmpDouble: Double?
+ if (_stmt.isNull(_cursorIndexOfDouble)) {
+ _tmpDouble = null
+ } else {
+ _tmpDouble = _stmt.getDouble(_cursorIndexOfDouble)
+ }
+ _result = MyEntity(_tmpInt,_tmpShort,_tmpByte,_tmpLong,_tmpChar,_tmpFloat,_tmpDouble)
} else {
- _tmpInt = _stmt.getLong(_cursorIndexOfInt).toInt()
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- val _tmpShort: Short?
- if (_stmt.isNull(_cursorIndexOfShort)) {
- _tmpShort = null
- } else {
- _tmpShort = _stmt.getLong(_cursorIndexOfShort).toShort()
- }
- val _tmpByte: Byte?
- if (_stmt.isNull(_cursorIndexOfByte)) {
- _tmpByte = null
- } else {
- _tmpByte = _stmt.getLong(_cursorIndexOfByte).toByte()
- }
- val _tmpLong: Long?
- if (_stmt.isNull(_cursorIndexOfLong)) {
- _tmpLong = null
- } else {
- _tmpLong = _stmt.getLong(_cursorIndexOfLong)
- }
- val _tmpChar: Char?
- if (_stmt.isNull(_cursorIndexOfChar)) {
- _tmpChar = null
- } else {
- _tmpChar = _stmt.getLong(_cursorIndexOfChar).toChar()
- }
- val _tmpFloat: Float?
- if (_stmt.isNull(_cursorIndexOfFloat)) {
- _tmpFloat = null
- } else {
- _tmpFloat = _stmt.getDouble(_cursorIndexOfFloat).toFloat()
- }
- val _tmpDouble: Double?
- if (_stmt.isNull(_cursorIndexOfDouble)) {
- _tmpDouble = null
- } else {
- _tmpDouble = _stmt.getDouble(_cursorIndexOfDouble)
- }
- _result = MyEntity(_tmpInt,_tmpShort,_tmpByte,_tmpLong,_tmpChar,_tmpFloat,_tmpDouble)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
index 7f73a90..65ed82a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -17,10 +18,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`string`,`nullableString`) VALUES (?,?)"
@@ -40,7 +41,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -49,24 +50,29 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
- val _cursorIndexOfNullableString: Int = getColumnIndexOrThrow(_stmt, "nullableString")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpString: String
- _tmpString = _stmt.getText(_cursorIndexOfString)
- val _tmpNullableString: String?
- if (_stmt.isNull(_cursorIndexOfNullableString)) {
- _tmpNullableString = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfString: Int = getColumnIndexOrThrow(_stmt, "string")
+ val _cursorIndexOfNullableString: Int = getColumnIndexOrThrow(_stmt, "nullableString")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpString: String
+ _tmpString = _stmt.getText(_cursorIndexOfString)
+ val _tmpNullableString: String?
+ if (_stmt.isNull(_cursorIndexOfNullableString)) {
+ _tmpNullableString = null
+ } else {
+ _tmpNullableString = _stmt.getText(_cursorIndexOfNullableString)
+ }
+ _result = MyEntity(_tmpString,_tmpNullableString)
} else {
- _tmpNullableString = _stmt.getText(_cursorIndexOfNullableString)
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- _result = MyEntity(_tmpString,_tmpNullableString)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
index b02b393..9786ab7 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
@@ -3,7 +3,8 @@
import androidx.room.util.convertByteToUUID
import androidx.room.util.convertUUIDToByte
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import java.util.UUID
import javax.`annotation`.processing.Generated
@@ -20,10 +21,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`uuid`,`nullableUuid`) VALUES (?,?,?)"
@@ -44,7 +45,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -53,27 +54,32 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfUuid: Int = getColumnIndexOrThrow(_stmt, "uuid")
- val _cursorIndexOfNullableUuid: Int = getColumnIndexOrThrow(_stmt, "nullableUuid")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- val _tmpUuid: UUID
- _tmpUuid = convertByteToUUID(_stmt.getBlob(_cursorIndexOfUuid))
- val _tmpNullableUuid: UUID?
- if (_stmt.isNull(_cursorIndexOfNullableUuid)) {
- _tmpNullableUuid = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfUuid: Int = getColumnIndexOrThrow(_stmt, "uuid")
+ val _cursorIndexOfNullableUuid: Int = getColumnIndexOrThrow(_stmt, "nullableUuid")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpUuid: UUID
+ _tmpUuid = convertByteToUUID(_stmt.getBlob(_cursorIndexOfUuid))
+ val _tmpNullableUuid: UUID?
+ if (_stmt.isNull(_cursorIndexOfNullableUuid)) {
+ _tmpNullableUuid = null
+ } else {
+ _tmpNullableUuid = convertByteToUUID(_stmt.getBlob(_cursorIndexOfNullableUuid))
+ }
+ _result = MyEntity(_tmpPk,_tmpUuid,_tmpNullableUuid)
} else {
- _tmpNullableUuid = convertByteToUUID(_stmt.getBlob(_cursorIndexOfNullableUuid))
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- _result = MyEntity(_tmpPk,_tmpUuid,_tmpNullableUuid)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt
index cb9cb3e..fe0102e 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_valueClassConverter.kt
@@ -3,7 +3,8 @@
import androidx.room.util.convertByteToUUID
import androidx.room.util.convertUUIDToByte
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import java.util.UUID
import javax.`annotation`.processing.Generated
@@ -21,10 +22,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`uuidData`,`nullableUuidData`,`nullableLongData`,`doubleNullableLongData`,`genericData`) VALUES (?,?,?,?,?,?)"
@@ -67,7 +68,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -76,54 +77,59 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfUuidData: Int = getColumnIndexOrThrow(_stmt, "uuidData")
- val _cursorIndexOfNullableUuidData: Int = getColumnIndexOrThrow(_stmt, "nullableUuidData")
- val _cursorIndexOfNullableLongData: Int = getColumnIndexOrThrow(_stmt, "nullableLongData")
- val _cursorIndexOfDoubleNullableLongData: Int = getColumnIndexOrThrow(_stmt,
- "doubleNullableLongData")
- val _cursorIndexOfGenericData: Int = getColumnIndexOrThrow(_stmt, "genericData")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: LongValueClass
- val _data: Long
- _data = _stmt.getLong(_cursorIndexOfPk)
- _tmpPk = LongValueClass(_data)
- val _tmpUuidData: UUIDValueClass
- val _data_1: UUID
- _data_1 = convertByteToUUID(_stmt.getBlob(_cursorIndexOfUuidData))
- _tmpUuidData = UUIDValueClass(_data_1)
- val _tmpNullableUuidData: UUIDValueClass?
- if (_stmt.isNull(_cursorIndexOfNullableUuidData)) {
- _tmpNullableUuidData = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfUuidData: Int = getColumnIndexOrThrow(_stmt, "uuidData")
+ val _cursorIndexOfNullableUuidData: Int = getColumnIndexOrThrow(_stmt, "nullableUuidData")
+ val _cursorIndexOfNullableLongData: Int = getColumnIndexOrThrow(_stmt, "nullableLongData")
+ val _cursorIndexOfDoubleNullableLongData: Int = getColumnIndexOrThrow(_stmt,
+ "doubleNullableLongData")
+ val _cursorIndexOfGenericData: Int = getColumnIndexOrThrow(_stmt, "genericData")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: LongValueClass
+ val _data: Long
+ _data = _stmt.getLong(_cursorIndexOfPk)
+ _tmpPk = LongValueClass(_data)
+ val _tmpUuidData: UUIDValueClass
+ val _data_1: UUID
+ _data_1 = convertByteToUUID(_stmt.getBlob(_cursorIndexOfUuidData))
+ _tmpUuidData = UUIDValueClass(_data_1)
+ val _tmpNullableUuidData: UUIDValueClass?
+ if (_stmt.isNull(_cursorIndexOfNullableUuidData)) {
+ _tmpNullableUuidData = null
+ } else {
+ val _data_2: UUID
+ _data_2 = convertByteToUUID(_stmt.getBlob(_cursorIndexOfNullableUuidData))
+ _tmpNullableUuidData = UUIDValueClass(_data_2)
+ }
+ val _tmpNullableLongData: NullableLongValueClass
+ val _data_3: Long
+ _data_3 = _stmt.getLong(_cursorIndexOfNullableLongData)
+ _tmpNullableLongData = NullableLongValueClass(_data_3)
+ val _tmpDoubleNullableLongData: NullableLongValueClass?
+ if (_stmt.isNull(_cursorIndexOfDoubleNullableLongData)) {
+ _tmpDoubleNullableLongData = null
+ } else {
+ val _data_4: Long
+ _data_4 = _stmt.getLong(_cursorIndexOfDoubleNullableLongData)
+ _tmpDoubleNullableLongData = NullableLongValueClass(_data_4)
+ }
+ val _tmpGenericData: GenericValueClass<String>
+ val _password: String
+ _password = _stmt.getText(_cursorIndexOfGenericData)
+ _tmpGenericData = GenericValueClass<String>(_password)
+ _result =
+ MyEntity(_tmpPk,_tmpUuidData,_tmpNullableUuidData,_tmpNullableLongData,_tmpDoubleNullableLongData,_tmpGenericData)
} else {
- val _data_2: UUID
- _data_2 = convertByteToUUID(_stmt.getBlob(_cursorIndexOfNullableUuidData))
- _tmpNullableUuidData = UUIDValueClass(_data_2)
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- val _tmpNullableLongData: NullableLongValueClass
- val _data_3: Long
- _data_3 = _stmt.getLong(_cursorIndexOfNullableLongData)
- _tmpNullableLongData = NullableLongValueClass(_data_3)
- val _tmpDoubleNullableLongData: NullableLongValueClass?
- if (_stmt.isNull(_cursorIndexOfDoubleNullableLongData)) {
- _tmpDoubleNullableLongData = null
- } else {
- val _data_4: Long
- _data_4 = _stmt.getLong(_cursorIndexOfDoubleNullableLongData)
- _tmpDoubleNullableLongData = NullableLongValueClass(_data_4)
- }
- val _tmpGenericData: GenericValueClass<String>
- val _password: String
- _password = _stmt.getText(_cursorIndexOfGenericData)
- _tmpGenericData = GenericValueClass<String>(_password)
- _result =
- MyEntity(_tmpPk,_tmpUuidData,_tmpNullableUuidData,_tmpNullableLongData,_tmpDoubleNullableLongData,_tmpGenericData)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
index 3b85238..b171058 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -17,10 +18,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`variablePrimitive`,`variableString`,`variableNullableString`) VALUES (?,?,?,?)"
@@ -42,7 +43,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -51,28 +52,33 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
- val _cursorIndexOfVariablePrimitive: Int = getColumnIndexOrThrow(_stmt, "variablePrimitive")
- val _cursorIndexOfVariableString: Int = getColumnIndexOrThrow(_stmt, "variableString")
- val _cursorIndexOfVariableNullableString: Int = getColumnIndexOrThrow(_stmt,
- "variableNullableString")
- val _result: MyEntity
- if (_stmt.step()) {
- val _tmpPk: Int
- _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
- _result = MyEntity(_tmpPk)
- _result.variablePrimitive = _stmt.getLong(_cursorIndexOfVariablePrimitive)
- _result.variableString = _stmt.getText(_cursorIndexOfVariableString)
- if (_stmt.isNull(_cursorIndexOfVariableNullableString)) {
- _result.variableNullableString = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfVariablePrimitive: Int = getColumnIndexOrThrow(_stmt, "variablePrimitive")
+ val _cursorIndexOfVariableString: Int = getColumnIndexOrThrow(_stmt, "variableString")
+ val _cursorIndexOfVariableNullableString: Int = getColumnIndexOrThrow(_stmt,
+ "variableNullableString")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ _result = MyEntity(_tmpPk)
+ _result.variablePrimitive = _stmt.getLong(_cursorIndexOfVariablePrimitive)
+ _result.variableString = _stmt.getText(_cursorIndexOfVariableString)
+ if (_stmt.isNull(_cursorIndexOfVariableNullableString)) {
+ _result.variableNullableString = null
+ } else {
+ _result.variableNullableString = _stmt.getText(_cursorIndexOfVariableNullableString)
+ }
} else {
- _result.variableNullableString = _stmt.getText(_cursorIndexOfVariableNullableString)
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
index 1c6b4ee..a1f5fc7 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
@@ -1,7 +1,8 @@
import androidx.room.EntityInsertionAdapter
import androidx.room.RoomDatabase
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.performReadBlocking
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -18,10 +19,10 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`mValue`,`mNullableValue`) VALUES (?,?)"
@@ -41,7 +42,7 @@
__db.assertNotSuspendingTransaction()
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(item)
+ __insertAdapterOfMyEntity.insert(item)
__db.setTransactionSuccessful()
} finally {
__db.endTransaction()
@@ -50,26 +51,31 @@
public override fun getEntity(): MyEntity {
val _sql: String = "SELECT * FROM MyEntity"
- return performReadBlocking(__db, _sql) { _stmt ->
- val _cursorIndexOfMValue: Int = getColumnIndexOrThrow(_stmt, "mValue")
- val _cursorIndexOfMNullableValue: Int = getColumnIndexOrThrow(_stmt, "mNullableValue")
- val _result: MyEntity
- if (_stmt.step()) {
- _result = MyEntity()
- val _tmpMValue: Long
- _tmpMValue = _stmt.getLong(_cursorIndexOfMValue)
- _result.setValue(_tmpMValue)
- val _tmpMNullableValue: String?
- if (_stmt.isNull(_cursorIndexOfMNullableValue)) {
- _tmpMNullableValue = null
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfMValue: Int = getColumnIndexOrThrow(_stmt, "mValue")
+ val _cursorIndexOfMNullableValue: Int = getColumnIndexOrThrow(_stmt, "mNullableValue")
+ val _result: MyEntity
+ if (_stmt.step()) {
+ _result = MyEntity()
+ val _tmpMValue: Long
+ _tmpMValue = _stmt.getLong(_cursorIndexOfMValue)
+ _result.setValue(_tmpMValue)
+ val _tmpMNullableValue: String?
+ if (_stmt.isNull(_cursorIndexOfMNullableValue)) {
+ _tmpMNullableValue = null
+ } else {
+ _tmpMNullableValue = _stmt.getText(_cursorIndexOfMNullableValue)
+ }
+ _result.setNullableValue(_tmpMNullableValue)
} else {
- _tmpMNullableValue = _stmt.getText(_cursorIndexOfMNullableValue)
+ error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
}
- _result.setNullableValue(_tmpMNullableValue)
- } else {
- error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+ _result
+ } finally {
+ _stmt.close()
}
- _result
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt
index bbaaea9..bb47406 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt
@@ -1,5 +1,4 @@
import androidx.room.RoomDatabase
-import androidx.room.SharedSQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import io.reactivex.Completable
import io.reactivex.Maybe
@@ -20,37 +19,26 @@
__db: RoomDatabase,
) : MyDao {
private val __db: RoomDatabase
-
- private val __preparedStmtOfInsertPublisherSingle: SharedSQLiteStatement
init {
this.__db = __db
- this.__preparedStmtOfInsertPublisherSingle = object : SharedSQLiteStatement(__db) {
- public override fun createQuery(): String {
- val _query: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
- return _query
- }
- }
}
public override fun insertPublisherSingle(id: String, name: String): Single<Long> =
Single.fromCallable(object : Callable<Long?> {
public override fun call(): Long? {
- val _stmt: SupportSQLiteStatement = __preparedStmtOfInsertPublisherSingle.acquire()
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
var _argIndex: Int = 1
_stmt.bindString(_argIndex, id)
_argIndex = 2
_stmt.bindString(_argIndex, name)
+ __db.beginTransaction()
try {
- __db.beginTransaction()
- try {
- val _result: Long? = _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return _result
- } finally {
- __db.endTransaction()
- }
+ val _result: Long? = _stmt.executeInsert()
+ __db.setTransactionSuccessful()
+ return _result
} finally {
- __preparedStmtOfInsertPublisherSingle.release(_stmt)
+ __db.endTransaction()
}
}
})
@@ -58,22 +46,19 @@
public override fun insertPublisherMaybe(id: String, name: String): Maybe<Long> =
Maybe.fromCallable(object : Callable<Long?> {
public override fun call(): Long? {
- val _stmt: SupportSQLiteStatement = __preparedStmtOfInsertPublisherSingle.acquire()
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
var _argIndex: Int = 1
_stmt.bindString(_argIndex, id)
_argIndex = 2
_stmt.bindString(_argIndex, name)
+ __db.beginTransaction()
try {
- __db.beginTransaction()
- try {
- val _result: Long? = _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return _result
- } finally {
- __db.endTransaction()
- }
+ val _result: Long? = _stmt.executeInsert()
+ __db.setTransactionSuccessful()
+ return _result
} finally {
- __preparedStmtOfInsertPublisherSingle.release(_stmt)
+ __db.endTransaction()
}
}
})
@@ -81,22 +66,19 @@
public override fun insertPublisherCompletable(id: String, name: String): Completable =
Completable.fromCallable(object : Callable<Void?> {
public override fun call(): Void? {
- val _stmt: SupportSQLiteStatement = __preparedStmtOfInsertPublisherSingle.acquire()
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
var _argIndex: Int = 1
_stmt.bindString(_argIndex, id)
_argIndex = 2
_stmt.bindString(_argIndex, name)
+ __db.beginTransaction()
try {
- __db.beginTransaction()
- try {
- _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return null
- } finally {
- __db.endTransaction()
- }
+ _stmt.executeInsert()
+ __db.setTransactionSuccessful()
+ return null
} finally {
- __preparedStmtOfInsertPublisherSingle.release(_stmt)
+ __db.endTransaction()
}
}
})
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt
index 23a85cc..7e61736 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt
@@ -1,5 +1,4 @@
import androidx.room.RoomDatabase
-import androidx.room.SharedSQLiteStatement
import androidx.sqlite.db.SupportSQLiteStatement
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Maybe
@@ -20,37 +19,26 @@
__db: RoomDatabase,
) : MyDao {
private val __db: RoomDatabase
-
- private val __preparedStmtOfInsertPublisherSingle: SharedSQLiteStatement
init {
this.__db = __db
- this.__preparedStmtOfInsertPublisherSingle = object : SharedSQLiteStatement(__db) {
- public override fun createQuery(): String {
- val _query: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
- return _query
- }
- }
}
public override fun insertPublisherSingle(id: String, name: String): Single<Long> =
Single.fromCallable(object : Callable<Long?> {
public override fun call(): Long? {
- val _stmt: SupportSQLiteStatement = __preparedStmtOfInsertPublisherSingle.acquire()
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
var _argIndex: Int = 1
_stmt.bindString(_argIndex, id)
_argIndex = 2
_stmt.bindString(_argIndex, name)
+ __db.beginTransaction()
try {
- __db.beginTransaction()
- try {
- val _result: Long? = _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return _result
- } finally {
- __db.endTransaction()
- }
+ val _result: Long? = _stmt.executeInsert()
+ __db.setTransactionSuccessful()
+ return _result
} finally {
- __preparedStmtOfInsertPublisherSingle.release(_stmt)
+ __db.endTransaction()
}
}
})
@@ -58,22 +46,19 @@
public override fun insertPublisherMaybe(id: String, name: String): Maybe<Long> =
Maybe.fromCallable(object : Callable<Long?> {
public override fun call(): Long? {
- val _stmt: SupportSQLiteStatement = __preparedStmtOfInsertPublisherSingle.acquire()
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
var _argIndex: Int = 1
_stmt.bindString(_argIndex, id)
_argIndex = 2
_stmt.bindString(_argIndex, name)
+ __db.beginTransaction()
try {
- __db.beginTransaction()
- try {
- val _result: Long? = _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return _result
- } finally {
- __db.endTransaction()
- }
+ val _result: Long? = _stmt.executeInsert()
+ __db.setTransactionSuccessful()
+ return _result
} finally {
- __preparedStmtOfInsertPublisherSingle.release(_stmt)
+ __db.endTransaction()
}
}
})
@@ -81,22 +66,19 @@
public override fun insertPublisherCompletable(id: String, name: String): Completable =
Completable.fromCallable(object : Callable<Void?> {
public override fun call(): Void? {
- val _stmt: SupportSQLiteStatement = __preparedStmtOfInsertPublisherSingle.acquire()
+ val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+ val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
var _argIndex: Int = 1
_stmt.bindString(_argIndex, id)
_argIndex = 2
_stmt.bindString(_argIndex, name)
+ __db.beginTransaction()
try {
- __db.beginTransaction()
- try {
- _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return null
- } finally {
- __db.endTransaction()
- }
+ _stmt.executeInsert()
+ __db.setTransactionSuccessful()
+ return null
} finally {
- __preparedStmtOfInsertPublisherSingle.release(_stmt)
+ __db.endTransaction()
}
}
})
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt
index 19961f0..20406d5 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt
@@ -1,6 +1,9 @@
import androidx.room.RoomDatabase
-import androidx.room.SharedSQLiteStatement
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.util.appendPlaceholders
+import androidx.room.util.getLastInsertedRowId
+import androidx.room.util.getTotalChangedRows
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.Long
@@ -8,6 +11,7 @@
import kotlin.Suppress
import kotlin.collections.List
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -15,148 +19,114 @@
__db: RoomDatabase,
) : MyDao {
private val __db: RoomDatabase
-
- private val __preparedStmtOfInsertEntity: SharedSQLiteStatement
-
- private val __preparedStmtOfUpdateEntity: SharedSQLiteStatement
-
- private val __preparedStmtOfUpdateEntityReturnInt: SharedSQLiteStatement
-
- private val __preparedStmtOfDeleteEntity: SharedSQLiteStatement
init {
this.__db = __db
- this.__preparedStmtOfInsertEntity = object : SharedSQLiteStatement(__db) {
- public override fun createQuery(): String {
- val _query: String = "INSERT INTO MyEntity (id) VALUES (?)"
- return _query
- }
- }
- this.__preparedStmtOfUpdateEntity = object : SharedSQLiteStatement(__db) {
- public override fun createQuery(): String {
- val _query: String = "UPDATE MyEntity SET text = ?"
- return _query
- }
- }
- this.__preparedStmtOfUpdateEntityReturnInt = object : SharedSQLiteStatement(__db) {
- public override fun createQuery(): String {
- val _query: String = "UPDATE MyEntity SET text = ? WHERE id = ?"
- return _query
- }
- }
- this.__preparedStmtOfDeleteEntity = object : SharedSQLiteStatement(__db) {
- public override fun createQuery(): String {
- val _query: String = "DELETE FROM MyEntity"
- return _query
- }
- }
}
public override fun insertEntity(id: Long) {
- __db.assertNotSuspendingTransaction()
- val _stmt: SupportSQLiteStatement = __preparedStmtOfInsertEntity.acquire()
- var _argIndex: Int = 1
- _stmt.bindLong(_argIndex, id)
- try {
- __db.beginTransaction()
+ val _sql: String = "INSERT INTO MyEntity (id) VALUES (?)"
+ return performBlocking(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- _stmt.executeInsert()
- __db.setTransactionSuccessful()
+ var _argIndex: Int = 1
+ _stmt.bindLong(_argIndex, id)
+ _stmt.step()
} finally {
- __db.endTransaction()
+ _stmt.close()
}
- } finally {
- __preparedStmtOfInsertEntity.release(_stmt)
}
}
public override fun insertEntityReturnLong(id: Long): Long {
- __db.assertNotSuspendingTransaction()
- val _stmt: SupportSQLiteStatement = __preparedStmtOfInsertEntity.acquire()
- var _argIndex: Int = 1
- _stmt.bindLong(_argIndex, id)
- try {
- __db.beginTransaction()
+ val _sql: String = "INSERT INTO MyEntity (id) VALUES (?)"
+ return performBlocking(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- val _result: Long = _stmt.executeInsert()
- __db.setTransactionSuccessful()
- return _result
+ var _argIndex: Int = 1
+ _stmt.bindLong(_argIndex, id)
+ _stmt.step()
+ getLastInsertedRowId(_connection)
} finally {
- __db.endTransaction()
+ _stmt.close()
}
- } finally {
- __preparedStmtOfInsertEntity.release(_stmt)
}
}
public override fun updateEntity(text: String) {
- __db.assertNotSuspendingTransaction()
- val _stmt: SupportSQLiteStatement = __preparedStmtOfUpdateEntity.acquire()
- var _argIndex: Int = 1
- _stmt.bindString(_argIndex, text)
- try {
- __db.beginTransaction()
+ val _sql: String = "UPDATE MyEntity SET text = ?"
+ return performBlocking(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- _stmt.executeUpdateDelete()
- __db.setTransactionSuccessful()
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, text)
+ _stmt.step()
} finally {
- __db.endTransaction()
+ _stmt.close()
}
- } finally {
- __preparedStmtOfUpdateEntity.release(_stmt)
}
}
public override fun updateEntityReturnInt(id: Long, text: String): Int {
- __db.assertNotSuspendingTransaction()
- val _stmt: SupportSQLiteStatement = __preparedStmtOfUpdateEntityReturnInt.acquire()
- var _argIndex: Int = 1
- _stmt.bindString(_argIndex, text)
- _argIndex = 2
- _stmt.bindLong(_argIndex, id)
- try {
- __db.beginTransaction()
+ val _sql: String = "UPDATE MyEntity SET text = ? WHERE id = ?"
+ return performBlocking(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- val _result: Int = _stmt.executeUpdateDelete()
- __db.setTransactionSuccessful()
- return _result
+ var _argIndex: Int = 1
+ _stmt.bindText(_argIndex, text)
+ _argIndex = 2
+ _stmt.bindLong(_argIndex, id)
+ _stmt.step()
+ getTotalChangedRows(_connection)
} finally {
- __db.endTransaction()
+ _stmt.close()
}
- } finally {
- __preparedStmtOfUpdateEntityReturnInt.release(_stmt)
}
}
public override fun deleteEntity() {
- __db.assertNotSuspendingTransaction()
- val _stmt: SupportSQLiteStatement = __preparedStmtOfDeleteEntity.acquire()
- try {
- __db.beginTransaction()
+ val _sql: String = "DELETE FROM MyEntity"
+ return performBlocking(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- _stmt.executeUpdateDelete()
- __db.setTransactionSuccessful()
+ _stmt.step()
} finally {
- __db.endTransaction()
+ _stmt.close()
}
- } finally {
- __preparedStmtOfDeleteEntity.release(_stmt)
}
}
public override fun deleteEntityReturnInt(): Int {
- __db.assertNotSuspendingTransaction()
- val _stmt: SupportSQLiteStatement = __preparedStmtOfDeleteEntity.acquire()
- try {
- __db.beginTransaction()
+ val _sql: String = "DELETE FROM MyEntity"
+ return performBlocking(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
try {
- val _result: Int = _stmt.executeUpdateDelete()
- __db.setTransactionSuccessful()
- return _result
+ _stmt.step()
+ getTotalChangedRows(_connection)
} finally {
- __db.endTransaction()
+ _stmt.close()
}
- } finally {
- __preparedStmtOfDeleteEntity.release(_stmt)
+ }
+ }
+
+ public override fun deleteEntitiesIn(ids: List<Long>) {
+ val _stringBuilder: StringBuilder = StringBuilder()
+ _stringBuilder.append("DELETE FROM MyEntity WHERE id IN (")
+ val _inputSize: Int = ids.size
+ appendPlaceholders(_stringBuilder, _inputSize)
+ _stringBuilder.append(")")
+ val _sql: String = _stringBuilder.toString()
+ return performBlocking(__db, false, true) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ var _argIndex: Int = 1
+ for (_item: Long in ids) {
+ _stmt.bindLong(_argIndex, _item)
+ _argIndex++
+ }
+ _stmt.step()
+ } finally {
+ _stmt.close()
+ }
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_list.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_list.kt
index 9362296..9e57d0a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_list.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_list.kt
@@ -1,16 +1,14 @@
-import android.database.Cursor
import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
-import java.util.ArrayList
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteStatement
import javax.`annotation`.processing.Generated
import kotlin.Int
import kotlin.String
import kotlin.Suppress
import kotlin.collections.List
import kotlin.collections.MutableList
+import kotlin.collections.mutableListOf
import kotlin.reflect.KClass
@Generated(value = ["androidx.room.RoomProcessor"])
@@ -25,26 +23,25 @@
public override fun queryOfList(): List<MyEntity> {
val _sql: String = "SELECT * FROM MyEntity"
- val _statement: RoomSQLiteQuery = acquire(_sql, 0)
- __db.assertNotSuspendingTransaction()
- val _cursor: Cursor = query(__db, _statement, false, null)
- try {
- val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
- val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
- val _result: MutableList<MyEntity> = ArrayList<MyEntity>(_cursor.getCount())
- while (_cursor.moveToNext()) {
- val _item: MyEntity
- val _tmpPk: Int
- _tmpPk = _cursor.getInt(_cursorIndexOfPk)
- val _tmpOther: String
- _tmpOther = _cursor.getString(_cursorIndexOfOther)
- _item = MyEntity(_tmpPk,_tmpOther)
- _result.add(_item)
+ return performBlocking(__db, true, false) { _connection ->
+ val _stmt: SQLiteStatement = _connection.prepare(_sql)
+ try {
+ val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+ val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+ val _result: MutableList<MyEntity> = mutableListOf()
+ while (_stmt.step()) {
+ val _item: MyEntity
+ val _tmpPk: Int
+ _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+ val _tmpOther: String
+ _tmpOther = _stmt.getText(_cursorIndexOfOther)
+ _item = MyEntity(_tmpPk,_tmpOther)
+ _result.add(_item)
+ }
+ _result
+ } finally {
+ _stmt.close()
}
- return _result
- } finally {
- _cursor.close()
- _statement.release()
}
}
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
index 3005ab5..c5e4c05 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
@@ -5,10 +5,8 @@
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndex
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
import androidx.room.util.query
import androidx.room.util.recursiveFetchHashMap
-import java.lang.StringBuilder
import java.util.ArrayList
import java.util.HashMap
import javax.`annotation`.processing.Generated
@@ -19,6 +17,7 @@
import kotlin.collections.List
import kotlin.collections.Set
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -159,7 +158,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `artistId` FROM `Artist` WHERE `artistId` IN (")
val _inputSize: Int = __mapKeySet.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -206,7 +205,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `songId`,`artistKey` FROM `Song` WHERE `artistKey` IN (")
val _inputSize: Int = __mapKeySet.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -257,7 +256,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `Song`.`songId` AS `songId`,`Song`.`artistKey` AS `artistKey`,_junction.`playlistKey` FROM `PlaylistSongXRef` AS _junction INNER JOIN `Song` ON (_junction.`songKey` = `Song`.`songId`) WHERE _junction.`playlistKey` IN (")
val _inputSize: Int = __mapKeySet.size
appendPlaceholders(_stringBuilder, _inputSize)
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
index ede09f4..b7b0a98 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
@@ -6,10 +6,8 @@
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndex
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
import androidx.room.util.query
import androidx.room.util.recursiveFetchArrayMap
-import java.lang.StringBuilder
import java.util.ArrayList
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -19,6 +17,7 @@
import kotlin.collections.List
import kotlin.collections.Set
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -159,7 +158,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `artistId` FROM `Artist` WHERE `artistId` IN (")
val _inputSize: Int = __mapKeySet.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -206,7 +205,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `songId`,`artistKey` FROM `Song` WHERE `artistKey` IN (")
val _inputSize: Int = __mapKeySet.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -257,7 +256,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `Song`.`songId` AS `songId`,`Song`.`artistKey` AS `artistKey`,_junction.`playlistKey` FROM `PlaylistSongXRef` AS _junction INNER JOIN `Song` ON (_junction.`songKey` = `Song`.`songId`) WHERE _junction.`playlistKey` IN (")
val _inputSize: Int = __mapKeySet.size
appendPlaceholders(_stringBuilder, _inputSize)
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
index 0fbb9e0..e2b505a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
@@ -5,10 +5,8 @@
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndex
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
import androidx.room.util.query
import androidx.room.util.recursiveFetchHashMap
-import java.lang.StringBuilder
import java.nio.ByteBuffer
import java.util.HashMap
import javax.`annotation`.processing.Generated
@@ -20,6 +18,7 @@
import kotlin.collections.List
import kotlin.collections.Set
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -84,7 +83,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `artistId` FROM `Artist` WHERE `artistId` IN (")
val _inputSize: Int = __mapKeySet.size
appendPlaceholders(_stringBuilder, _inputSize)
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
index 869e96c..bfc0824 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
@@ -6,10 +6,8 @@
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndex
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
import androidx.room.util.query
import androidx.room.util.recursiveFetchLongSparseArray
-import java.lang.StringBuilder
import java.util.ArrayList
import javax.`annotation`.processing.Generated
import kotlin.Int
@@ -18,6 +16,7 @@
import kotlin.Suppress
import kotlin.collections.List
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -157,7 +156,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `artistId` FROM `Artist` WHERE `artistId` IN (")
val _inputSize: Int = _map.size()
appendPlaceholders(_stringBuilder, _inputSize)
@@ -204,7 +203,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `songId`,`artistKey` FROM `Song` WHERE `artistKey` IN (")
val _inputSize: Int = _map.size()
appendPlaceholders(_stringBuilder, _inputSize)
@@ -255,7 +254,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `Song`.`songId` AS `songId`,`Song`.`artistKey` AS `artistKey`,_junction.`playlistKey` FROM `PlaylistSongXRef` AS _junction INNER JOIN `Song` ON (_junction.`songKey` = `Song`.`songId`) WHERE _junction.`playlistKey` IN (")
val _inputSize: Int = _map.size()
appendPlaceholders(_stringBuilder, _inputSize)
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
index 33ce9a4..65941b2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
@@ -5,10 +5,8 @@
import androidx.room.util.appendPlaceholders
import androidx.room.util.getColumnIndex
import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.newStringBuilder
import androidx.room.util.query
import androidx.room.util.recursiveFetchHashMap
-import java.lang.StringBuilder
import java.util.ArrayList
import java.util.HashMap
import javax.`annotation`.processing.Generated
@@ -19,6 +17,7 @@
import kotlin.collections.List
import kotlin.collections.Set
import kotlin.reflect.KClass
+import kotlin.text.StringBuilder
@Generated(value = ["androidx.room.RoomProcessor"])
@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION"])
@@ -174,7 +173,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `artistId` FROM `Artist` WHERE `artistId` IN (")
val _inputSize: Int = __mapKeySet.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -221,7 +220,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `songId`,`artistKey` FROM `Song` WHERE `artistKey` IN (")
val _inputSize: Int = __mapKeySet.size
appendPlaceholders(_stringBuilder, _inputSize)
@@ -282,7 +281,7 @@
}
return
}
- val _stringBuilder: StringBuilder = newStringBuilder()
+ val _stringBuilder: StringBuilder = StringBuilder()
_stringBuilder.append("SELECT `Song`.`songId` AS `songId`,`Song`.`artistKey` AS `artistKey`,_junction.`playlistKey` FROM `PlaylistSongXRef` AS _junction INNER JOIN `Song` ON (_junction.`songKey` = `Song`.`songId`) WHERE _junction.`playlistKey` IN (")
val _inputSize: Int = __mapKeySet.size
appendPlaceholders(_stringBuilder, _inputSize)
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
index cc97dc0..a553570 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
@@ -22,16 +22,16 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
- private val __deletionAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+ private val __deleteAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
private val __updateAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
- private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+ private val __upsertAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -40,7 +40,7 @@
statement.bindString(2, entity.other)
}
}
- this.__deletionAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ this.__deleteAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
@@ -57,7 +57,7 @@
statement.bindLong(3, entity.pk.toLong())
}
}
- this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
+ this.__upsertAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -83,7 +83,7 @@
public override fun call(): List<Long>? {
__db.beginTransaction()
try {
- val _result: List<Long>? = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
+ val _result: List<Long>? = __insertAdapterOfMyEntity.insertAndReturnIdsList(entities)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -97,7 +97,7 @@
public override fun call(): Void? {
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(entities)
+ __insertAdapterOfMyEntity.insert(entities)
__db.setTransactionSuccessful()
return null
} finally {
@@ -112,7 +112,7 @@
var _total: Int = 0
__db.beginTransaction()
try {
- _total += __deletionAdapterOfMyEntity.handle(entity)
+ _total += __deleteAdapterOfMyEntity.handle(entity)
__db.setTransactionSuccessful()
return _total
} finally {
@@ -126,7 +126,7 @@
public override fun call(): Void? {
__db.beginTransaction()
try {
- __deletionAdapterOfMyEntity.handle(entity)
+ __deleteAdapterOfMyEntity.handle(entity)
__db.setTransactionSuccessful()
return null
} finally {
@@ -169,7 +169,7 @@
public override fun call(): List<Long>? {
__db.beginTransaction()
try {
- val _result: List<Long>? = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
+ val _result: List<Long>? = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(entities)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -183,7 +183,7 @@
public override fun call(): Void? {
__db.beginTransaction()
try {
- __upsertionAdapterOfMyEntity.upsert(entities)
+ __upsertAdapterOfMyEntity.upsert(entities)
__db.setTransactionSuccessful()
return null
} finally {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
index 5864124..70f13eb 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
@@ -22,16 +22,16 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
- private val __deletionAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+ private val __deleteAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
private val __updateAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
- private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+ private val __upsertAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -40,7 +40,7 @@
statement.bindString(2, entity.other)
}
}
- this.__deletionAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ this.__deleteAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
@@ -57,7 +57,7 @@
statement.bindLong(3, entity.pk.toLong())
}
}
- this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
+ this.__upsertAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -83,7 +83,7 @@
public override fun call(): List<Long>? {
__db.beginTransaction()
try {
- val _result: List<Long>? = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
+ val _result: List<Long>? = __insertAdapterOfMyEntity.insertAndReturnIdsList(entities)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -97,7 +97,7 @@
public override fun call(): Void? {
__db.beginTransaction()
try {
- __insertionAdapterOfMyEntity.insert(entities)
+ __insertAdapterOfMyEntity.insert(entities)
__db.setTransactionSuccessful()
return null
} finally {
@@ -112,7 +112,7 @@
var _total: Int = 0
__db.beginTransaction()
try {
- _total += __deletionAdapterOfMyEntity.handle(entity)
+ _total += __deleteAdapterOfMyEntity.handle(entity)
__db.setTransactionSuccessful()
return _total
} finally {
@@ -126,7 +126,7 @@
public override fun call(): Void? {
__db.beginTransaction()
try {
- __deletionAdapterOfMyEntity.handle(entity)
+ __deleteAdapterOfMyEntity.handle(entity)
__db.setTransactionSuccessful()
return null
} finally {
@@ -169,7 +169,7 @@
public override fun call(): List<Long>? {
__db.beginTransaction()
try {
- val _result: List<Long>? = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
+ val _result: List<Long>? = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(entities)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -183,7 +183,7 @@
public override fun call(): Void? {
__db.beginTransaction()
try {
- __upsertionAdapterOfMyEntity.upsert(entities)
+ __upsertAdapterOfMyEntity.upsert(entities)
__db.setTransactionSuccessful()
return null
} finally {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt
index 72714e3..bbdaf84 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_suspend.kt
@@ -20,16 +20,16 @@
) : MyDao {
private val __db: RoomDatabase
- private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+ private val __insertAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
- private val __deletionAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+ private val __deleteAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
private val __updateAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
- private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+ private val __upsertAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
init {
this.__db = __db
- this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+ this.__insertAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -38,7 +38,7 @@
statement.bindString(2, entity.other)
}
}
- this.__deletionAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+ this.__deleteAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
@@ -55,7 +55,7 @@
statement.bindLong(3, entity.pk.toLong())
}
}
- this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
+ this.__upsertAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
EntityInsertionAdapter<MyEntity>(__db) {
protected override fun createQuery(): String =
"INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
@@ -81,7 +81,7 @@
public override fun call(): List<Long> {
__db.beginTransaction()
try {
- val _result: List<Long> = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
+ val _result: List<Long> = __insertAdapterOfMyEntity.insertAndReturnIdsList(entities)
__db.setTransactionSuccessful()
return _result
} finally {
@@ -96,7 +96,7 @@
var _total: Int = 0
__db.beginTransaction()
try {
- _total += __deletionAdapterOfMyEntity.handle(entity)
+ _total += __deleteAdapterOfMyEntity.handle(entity)
__db.setTransactionSuccessful()
return _total
} finally {
@@ -125,7 +125,7 @@
public override fun call(): List<Long> {
__db.beginTransaction()
try {
- val _result: List<Long> = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
+ val _result: List<Long> = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(entities)
__db.setTransactionSuccessful()
return _result
} finally {
diff --git a/room/room-gradle-plugin/lint-baseline.xml b/room/room-gradle-plugin/lint-baseline.xml
new file mode 100644
index 0000000..00f7b2e
--- /dev/null
+++ b/room/room-gradle-plugin/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
+
+ <issue
+ id="WithPluginClasspathUsage"
+ message="Avoid usage of GradleRunner#withPluginClasspath, which is broken. Instead use something like https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/testkit#gradle-testkit-support-plugin"
+ errorLine1=" .withPluginClasspath()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt"/>
+ </issue>
+
+</issues>
diff --git a/room/room-runtime/api/current.txt b/room/room-runtime/api/current.txt
index bd860e33..9a881a6 100644
--- a/room/room-runtime/api/current.txt
+++ b/room/room-runtime/api/current.txt
@@ -132,16 +132,17 @@
public abstract static class RoomDatabase.Callback {
ctor public RoomDatabase.Callback();
method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase db);
+ method public void onCreate(androidx.sqlite.SQLiteConnection connection);
method public void onDestructiveMigration(androidx.sqlite.db.SupportSQLiteDatabase db);
+ method public void onDestructiveMigration(androidx.sqlite.SQLiteConnection connection);
method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase db);
+ method public void onOpen(androidx.sqlite.SQLiteConnection connection);
}
public static final class RoomDatabase.Companion {
}
public enum RoomDatabase.JournalMode {
- method public static androidx.room.RoomDatabase.JournalMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.RoomDatabase.JournalMode[] values();
enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
enum_constant public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
@@ -184,8 +185,6 @@
}
public enum Transactor.SQLiteTransactionType {
- method public static androidx.room.Transactor.SQLiteTransactionType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.Transactor.SQLiteTransactionType[] values();
enum_constant public static final androidx.room.Transactor.SQLiteTransactionType DEFERRED;
enum_constant public static final androidx.room.Transactor.SQLiteTransactionType EXCLUSIVE;
enum_constant public static final androidx.room.Transactor.SQLiteTransactionType IMMEDIATE;
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt
index d69f1ac..16ab42f 100644
--- a/room/room-runtime/api/restricted_current.txt
+++ b/room/room-runtime/api/restricted_current.txt
@@ -50,6 +50,15 @@
field public final java.util.List<java.lang.Object> typeConverters;
}
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityDeleteOrUpdateAdapter<T> {
+ ctor public EntityDeleteOrUpdateAdapter();
+ method protected abstract void bind(androidx.sqlite.SQLiteStatement statement, T entity);
+ method protected abstract String createQuery();
+ method public final int handle(androidx.sqlite.SQLiteConnection connection, T entity);
+ method public final int handleMultiple(androidx.sqlite.SQLiteConnection connection, Iterable<? extends T> entities);
+ method public final int handleMultiple(androidx.sqlite.SQLiteConnection connection, T[] entities);
+ }
+
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityDeletionOrUpdateAdapter<T> extends androidx.room.SharedSQLiteStatement {
ctor public EntityDeletionOrUpdateAdapter(androidx.room.RoomDatabase database);
method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement statement, T entity);
@@ -58,6 +67,16 @@
method public final int handleMultiple(T[] entities);
}
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityInsertAdapter<T> {
+ ctor public EntityInsertAdapter();
+ method protected abstract void bind(androidx.sqlite.SQLiteStatement statement, T entity);
+ method protected abstract String createQuery();
+ method public final void insert(androidx.sqlite.SQLiteConnection connection, Iterable<? extends T> entities);
+ method public final void insert(androidx.sqlite.SQLiteConnection connection, T entity);
+ method public final void insert(androidx.sqlite.SQLiteConnection connection, T[] entities);
+ method public final long insertAndReturnId(androidx.sqlite.SQLiteConnection connection, T entity);
+ }
+
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityInsertionAdapter<T> extends androidx.room.SharedSQLiteStatement {
ctor public EntityInsertionAdapter(androidx.room.RoomDatabase database);
method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement statement, T entity);
@@ -73,6 +92,24 @@
method public final java.util.List<java.lang.Long> insertAndReturnIdsList(T[] entities);
}
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class EntityUpsertAdapter<T> {
+ ctor public EntityUpsertAdapter(androidx.room.EntityInsertAdapter<T> entityInsertAdapter, androidx.room.EntityDeleteOrUpdateAdapter<T> updateAdapter);
+ method public void upsert(androidx.sqlite.SQLiteConnection connection, Iterable<? extends T> entities);
+ method public void upsert(androidx.sqlite.SQLiteConnection connection, T entity);
+ method public void upsert(androidx.sqlite.SQLiteConnection connection, T[] entities);
+ method public long upsertAndReturnId(androidx.sqlite.SQLiteConnection connection, T entity);
+ method public long[] upsertAndReturnIdsArray(androidx.sqlite.SQLiteConnection connection, java.util.Collection<? extends T> entities);
+ method public long[] upsertAndReturnIdsArray(androidx.sqlite.SQLiteConnection connection, T[] entities);
+ method public Long[] upsertAndReturnIdsArrayBox(androidx.sqlite.SQLiteConnection connection, java.util.Collection<? extends T> entities);
+ method public Long[] upsertAndReturnIdsArrayBox(androidx.sqlite.SQLiteConnection connection, T[] entities);
+ method public java.util.List<java.lang.Long> upsertAndReturnIdsList(androidx.sqlite.SQLiteConnection connection, java.util.Collection<? extends T> entities);
+ method public java.util.List<java.lang.Long> upsertAndReturnIdsList(androidx.sqlite.SQLiteConnection connection, T[] entities);
+ field public static final androidx.room.EntityUpsertAdapter.Companion Companion;
+ }
+
+ public static final class EntityUpsertAdapter.Companion {
+ }
+
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class EntityUpsertionAdapter<T> {
ctor public EntityUpsertionAdapter(androidx.room.EntityInsertionAdapter<T> insertionAdapter, androidx.room.EntityDeletionOrUpdateAdapter<T> updateAdapter);
method public void upsert(Iterable<? extends T> entities);
@@ -207,16 +244,17 @@
public abstract static class RoomDatabase.Callback {
ctor public RoomDatabase.Callback();
method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase db);
+ method public void onCreate(androidx.sqlite.SQLiteConnection connection);
method public void onDestructiveMigration(androidx.sqlite.db.SupportSQLiteDatabase db);
+ method public void onDestructiveMigration(androidx.sqlite.SQLiteConnection connection);
method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase db);
+ method public void onOpen(androidx.sqlite.SQLiteConnection connection);
}
public static final class RoomDatabase.Companion {
}
public enum RoomDatabase.JournalMode {
- method public static androidx.room.RoomDatabase.JournalMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.RoomDatabase.JournalMode[] values();
enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
enum_constant public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
@@ -332,8 +370,6 @@
}
public enum Transactor.SQLiteTransactionType {
- method public static androidx.room.Transactor.SQLiteTransactionType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.room.Transactor.SQLiteTransactionType[] values();
enum_constant public static final androidx.room.Transactor.SQLiteTransactionType DEFERRED;
enum_constant public static final androidx.room.Transactor.SQLiteTransactionType EXCLUSIVE;
enum_constant public static final androidx.room.Transactor.SQLiteTransactionType IMMEDIATE;
@@ -399,12 +435,8 @@
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void dropFtsSyncTriggers(androidx.sqlite.db.SupportSQLiteDatabase db);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void dropFtsSyncTriggers(androidx.sqlite.SQLiteConnection connection);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void foreignKeyCheck(androidx.sqlite.db.SupportSQLiteDatabase db, String tableName);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static suspend Object? getLastInsertedRowId(androidx.room.PooledConnection, kotlin.coroutines.Continuation<? super java.lang.Long>);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static suspend Object? getTotalChangedRows(androidx.room.PooledConnection, kotlin.coroutines.Continuation<? super java.lang.Long>);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <R> R performReadBlocking(androidx.room.RoomDatabase db, String sql, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteStatement,? extends R> block);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static suspend <R> Object? performReadSuspending(androidx.room.RoomDatabase db, String sql, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteStatement,? extends R> block, kotlin.coroutines.Continuation<? super R>);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <R> R performReadTransactionBlocking(androidx.room.RoomDatabase db, String sql, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteStatement,? extends R> block);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static suspend <R> Object? performReadTransactionSuspending(androidx.room.RoomDatabase db, String sql, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteStatement,? extends R> block, kotlin.coroutines.Continuation<? super R>);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <R> R performBlocking(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends R> block);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static suspend <R> Object? performSuspending(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends R> block, kotlin.coroutines.Continuation<? super R>);
method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.database.Cursor query(androidx.room.RoomDatabase db, androidx.sqlite.db.SupportSQLiteQuery sqLiteQuery, boolean maybeCopy);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.database.Cursor query(androidx.room.RoomDatabase db, androidx.sqlite.db.SupportSQLiteQuery sqLiteQuery, boolean maybeCopy, android.os.CancellationSignal? signal);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @kotlin.jvm.Throws(exceptionClasses=IOException::class) public static int readVersion(java.io.File databaseFile) throws java.io.IOException;
@@ -436,16 +468,23 @@
method public static <V> void recursiveFetchLongSparseArray(androidx.collection.LongSparseArray<V> map, boolean isRelationCollection, kotlin.jvm.functions.Function1<? super androidx.collection.LongSparseArray<V>,kotlin.Unit> fetchBlock);
}
+ public final class SQLiteConnectionUtil {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static long getLastInsertedRowId(androidx.sqlite.SQLiteConnection connection);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int getTotalChangedRows(androidx.sqlite.SQLiteConnection connection);
+ }
+
public final class SQLiteStatementUtil {
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int getColumnIndexOrThrow(androidx.sqlite.SQLiteStatement stmt, String name);
}
@RestrictTo({androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX}) public final class StringUtil {
method public static void appendPlaceholders(StringBuilder builder, int count);
+ method @Deprecated public String?[] getEMPTY_STRING_ARRAY();
method public static String? joinIntoString(java.util.List<java.lang.Integer>? input);
- method public static StringBuilder newStringBuilder();
+ method @Deprecated public static StringBuilder newStringBuilder();
method public static java.util.List<java.lang.Integer>? splitToIntList(String? input);
- field public static final String?[] EMPTY_STRING_ARRAY;
+ property @Deprecated public String?[] EMPTY_STRING_ARRAY;
+ field @Deprecated public static final String?[] EMPTY_STRING_ARRAY;
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TableInfo {
diff --git a/room/room-runtime/build.gradle b/room/room-runtime/build.gradle
index 88190e2..919c59b 100644
--- a/room/room-runtime/build.gradle
+++ b/room/room-runtime/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
+
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
import androidx.build.Publish
@@ -36,7 +36,6 @@
id("com.google.devtools.ksp")
}
-def nativeEnabled = KmpPlatformsKt.enableNative(project)
configurations {
// Configuration for resolving shared archive file of androidx's SQLite compilation
@@ -153,18 +152,16 @@
implementation("androidx.arch.core:core-testing:2.2.0")
}
}
- if (nativeEnabled) {
- nativeMain {
- dependsOn(commonMain)
- dependencies {
- api(project(":sqlite:sqlite-framework"))
- }
+ nativeMain {
+ dependsOn(commonMain)
+ dependencies {
+ api(project(":sqlite:sqlite-framework"))
}
- nativeTest {
- dependsOn(commonTest)
- dependencies {
- implementation(project(":sqlite:sqlite-bundled"))
- }
+ }
+ nativeTest {
+ dependsOn(commonTest)
+ dependencies {
+ implementation(project(":sqlite:sqlite-bundled"))
}
}
targets.all { target ->
diff --git a/room/room-runtime/lint-baseline.xml b/room/room-runtime/lint-baseline.xml
index bcfeb52..5a234f8 100644
--- a/room/room-runtime/lint-baseline.xml
+++ b/room/room-runtime/lint-baseline.xml
@@ -7,7 +7,7 @@
errorLine1=" SupportSQLiteCompat.Api29Impl.setNotificationUris(delegate, cr, uris)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt"/>
</issue>
<issue
@@ -16,7 +16,7 @@
errorLine1=" SupportSQLiteCompat.Api29Impl.setNotificationUris(delegate, cr, uris)"
errorLine2=" ~~~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt"/>
</issue>
<issue
@@ -25,7 +25,7 @@
errorLine1=" SupportSQLiteCompat.Api29Impl.setNotificationUris(delegate, cr, uris)"
errorLine2=" ~~">
<location
- file="src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt"/>
</issue>
<issue
@@ -34,7 +34,7 @@
errorLine1=" SupportSQLiteCompat.Api29Impl.setNotificationUris(delegate, cr, uris)"
errorLine2=" ~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt"/>
</issue>
<issue
@@ -43,7 +43,7 @@
errorLine1=" return SupportSQLiteCompat.Api29Impl.getNotificationUris(delegate)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt"/>
</issue>
<issue
@@ -52,7 +52,7 @@
errorLine1=" return SupportSQLiteCompat.Api29Impl.getNotificationUris(delegate)"
errorLine2=" ~~~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt"/>
</issue>
<issue
@@ -61,7 +61,7 @@
errorLine1=" SupportSQLiteCompat.Api23Impl.setExtras(delegate, extras)"
errorLine2=" ~~~~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt"/>
</issue>
<issue
@@ -70,7 +70,7 @@
errorLine1=" SupportSQLiteCompat.Api23Impl.setExtras(delegate, extras)"
errorLine2=" ~~~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt"/>
</issue>
<issue
@@ -79,7 +79,7 @@
errorLine1=" SupportSQLiteCompat.Api23Impl.setExtras(delegate, extras)"
errorLine2=" ~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt"/>
</issue>
<issue
@@ -88,7 +88,7 @@
errorLine1=" val copyLock = ProcessLock("
errorLine2=" ~~~~~~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelper.android.kt"/>
</issue>
<issue
@@ -97,7 +97,7 @@
errorLine1=" name,"
errorLine2=" ~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelper.android.kt"/>
</issue>
<issue
@@ -106,7 +106,7 @@
errorLine1=" context.filesDir,"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelper.android.kt"/>
</issue>
<issue
@@ -115,7 +115,7 @@
errorLine1=" copyLock.lock()"
errorLine2=" ~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelper.android.kt"/>
</issue>
<issue
@@ -124,7 +124,7 @@
errorLine1=" copyLock.unlock()"
errorLine2=" ~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelper.android.kt"/>
+ file="src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelper.android.kt"/>
</issue>
</issues>
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
index 36c22ac..6374434 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.os.Build
import androidx.kruth.assertThat
+import androidx.room.support.AutoClosingRoomOpenHelper
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SdkSuppress
import java.util.concurrent.CountDownLatch
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/AutoCloserTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt
similarity index 99%
rename from room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/AutoCloserTest.kt
rename to room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt
index 1513ee6..9be2a51 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/AutoCloserTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import android.annotation.SuppressLint
import androidx.arch.core.executor.ArchTaskExecutor
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/AutoClosingRoomOpenHelperFactoryTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt
similarity index 99%
rename from room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/AutoClosingRoomOpenHelperFactoryTest.kt
rename to room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt
index 93a3f73..2b3b186 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/AutoClosingRoomOpenHelperFactoryTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import android.annotation.SuppressLint
import android.content.Context
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/AutoClosingRoomOpenHelperTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt
similarity index 99%
rename from room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/AutoClosingRoomOpenHelperTest.kt
rename to room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt
index 8621c189..3e26d9b 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/AutoClosingRoomOpenHelperTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import android.annotation.SuppressLint
import android.content.Context
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/DatabaseConfiguration.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/DatabaseConfiguration.android.kt
index 100a3aa..5e0d283 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/DatabaseConfiguration.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/DatabaseConfiguration.android.kt
@@ -61,7 +61,7 @@
actual val migrationContainer: RoomDatabase.MigrationContainer,
@JvmField
- val callbacks: List<RoomDatabase.Callback>?,
+ actual val callbacks: List<RoomDatabase.Callback>?,
/**
* Whether Room should throw an exception for queries run on the main thread.
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
index be39682..39464f9 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
@@ -27,6 +27,7 @@
import androidx.lifecycle.LiveData
import androidx.room.Room.LOG_TAG
import androidx.room.driver.SupportSQLiteConnection
+import androidx.room.support.AutoCloser
import androidx.room.util.useCursor
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.db.SimpleSQLiteQuery
@@ -149,7 +150,8 @@
@Suppress("DEPRECATION")
internalInit(connection.db)
} else {
- TODO("Not yet migrated to use SQLiteDriver - b/309990302")
+ Log.e(LOG_TAG, "Invalidation tracker is disabled due to lack of driver " +
+ "support. - b/309990302")
}
}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomAndroidConnectionManager.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomAndroidConnectionManager.android.kt
index da3cdc4..3ffdd09 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomAndroidConnectionManager.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomAndroidConnectionManager.android.kt
@@ -17,6 +17,7 @@
package androidx.room
import androidx.room.coroutines.ConnectionPool
+import androidx.room.coroutines.RawConnectionAccessor
import androidx.room.coroutines.newConnectionPool
import androidx.room.coroutines.newSingleConnectionPool
import androidx.room.driver.SupportSQLiteConnection
@@ -36,8 +37,7 @@
override val configuration: DatabaseConfiguration
override val connectionPool: ConnectionPool
override val openDelegate: RoomOpenDelegate
-
- private val callbacks: List<RoomDatabase.Callback>
+ override val callbacks: List<RoomDatabase.Callback>
internal val supportOpenHelper: SupportSQLiteOpenHelper?
get() = (connectionPool as? SupportConnectionPool)?.supportDriver?.openHelper
@@ -126,33 +126,6 @@
connectionPool.close()
}
- override fun invokeCreateCallback(connection: SQLiteConnection) {
- // TODO(b/316944352): Add callback mirror of SQLiteConnection
- callbacks.forEach {
- if (connection is SupportSQLiteConnection) {
- it.onCreate(connection.db)
- }
- }
- }
-
- override fun invokeDestructiveMigrationCallback(connection: SQLiteConnection) {
- // TODO(b/316944352): Add callback mirror of SQLiteConnection
- callbacks.forEach {
- if (connection is SupportSQLiteConnection) {
- it.onDestructiveMigration(connection.db)
- }
- }
- }
-
- override fun invokeOpenCallback(connection: SQLiteConnection) {
- // TODO(b/316944352): Add callback mirror of SQLiteConnection
- callbacks.forEach {
- if (connection is SupportSQLiteConnection) {
- it.onOpen(connection.db)
- }
- }
- }
-
// TODO(b/316944352): Figure out auto-close with driver APIs
fun isSupportDatabaseOpen() = supportDatabase?.isOpen ?: false
@@ -239,10 +212,13 @@
*/
private class SupportPooledConnection(
val delegate: SupportSQLiteConnection
- ) : Transactor {
+ ) : Transactor, RawConnectionAccessor {
private var currentTransactionType: Transactor.SQLiteTransactionType? = null
+ override val rawConnection: SQLiteConnection
+ get() = delegate
+
override suspend fun <R> usePrepared(sql: String, block: (SQLiteStatement) -> R): R {
return delegate.prepare(sql).use { block.invoke(it) }
}
@@ -289,7 +265,10 @@
private class RollbackException(val result: Any?) : Throwable()
- private inner class SupportTransactor<T> : TransactionScope<T> {
+ private inner class SupportTransactor<T> : TransactionScope<T>, RawConnectionAccessor {
+
+ override val rawConnection: SQLiteConnection
+ get() = this@SupportPooledConnection.rawConnection
override suspend fun <R> usePrepared(sql: String, block: (SQLiteStatement) -> R): R {
return this@SupportPooledConnection.usePrepared(sql, block)
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
index 450ae01..e152491 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:JvmMultifileClass
@file:JvmName("RoomDatabaseKt")
package androidx.room
@@ -35,11 +36,16 @@
import androidx.room.driver.SupportSQLiteConnection
import androidx.room.migration.AutoMigrationSpec
import androidx.room.migration.Migration
+import androidx.room.support.AutoCloser
+import androidx.room.support.AutoClosingRoomOpenHelper
+import androidx.room.support.AutoClosingRoomOpenHelperFactory
+import androidx.room.support.PrePackagedCopyOpenHelper
+import androidx.room.support.PrePackagedCopyOpenHelperFactory
+import androidx.room.support.QueryInterceptorOpenHelperFactory
import androidx.room.util.contains as containsExt
import androidx.room.util.findMigrationPath as findMigrationPathExt
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.SQLiteDriver
-import androidx.sqlite.SQLiteStatement
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
@@ -230,7 +236,7 @@
// Configure SQLiteCopyOpenHelper if it is available
unwrapOpenHelper(
- clazz = SQLiteCopyOpenHelper::class.java,
+ clazz = PrePackagedCopyOpenHelper::class.java,
openHelper = connectionManager.supportOpenHelper
)?.setDatabaseConfiguration(configuration)
@@ -557,43 +563,13 @@
}
/**
- * Performs a database operation.
+ * Use a connection to perform database operations.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- actual suspend fun <R> perform(
+ internal actual suspend fun <R> useConnection(
isReadOnly: Boolean,
- sql: String,
- block: (SQLiteStatement) -> R
+ block: suspend (Transactor) -> R
): R {
- return connectionManager.useConnection(isReadOnly) { connection ->
- connection.usePrepared(sql, block)
- }
- }
-
- /**
- * Performs a transactional database operation.
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- actual suspend fun <R> performTransaction(
- isReadOnly: Boolean,
- block: suspend (TransactionScope<R>) -> R
- ): R {
- return connectionManager.useConnection(isReadOnly) { transactor ->
- val type = if (isReadOnly) {
- Transactor.SQLiteTransactionType.DEFERRED
- } else {
- Transactor.SQLiteTransactionType.IMMEDIATE
- }
- // TODO(b/309990302): Commonize Invalidation Tracker
- if (!isReadOnly) {
- invalidationTracker.syncTriggers(openHelper.writableDatabase)
- }
- val result = transactor.withTransaction(type, block)
- if (!isReadOnly && !transactor.inTransaction()) {
- invalidationTracker.refreshVersionsAsync()
- }
- result
- }
+ return connectionManager.useConnection(isReadOnly, block)
}
/**
@@ -1456,7 +1432,7 @@
* @param callback The callback.
* @return This builder instance.
*/
- open fun addCallback(callback: Callback) = apply {
+ actual open fun addCallback(callback: Callback) = apply {
this.callbacks.add(callback)
}
@@ -1631,7 +1607,7 @@
"Builder, but the database can only be created using one of the " +
"three configurations."
}
- SQLiteCopyOpenHelperFactory(
+ PrePackagedCopyOpenHelperFactory(
copyFromAssetPath,
copyFromFile,
copyFromInputStream,
@@ -1778,7 +1754,7 @@
/**
* Callback for [RoomDatabase].
*/
- abstract class Callback {
+ actual abstract class Callback {
/**
* Called when the database is created for the first time. This is called after all the
* tables are created.
@@ -1788,11 +1764,17 @@
open fun onCreate(db: SupportSQLiteDatabase) {}
/**
- * Called when the database has been opened.
+ * Called when the database is created for the first time.
*
- * @param db The database.
+ * This function called after all the tables are created.
+ *
+ * @param connection The database connection.
*/
- open fun onOpen(db: SupportSQLiteDatabase) {}
+ actual open fun onCreate(connection: SQLiteConnection) {
+ if (connection is SupportSQLiteConnection) {
+ onCreate(connection.db)
+ }
+ }
/**
* Called after the database was destructively migrated
@@ -1800,6 +1782,35 @@
* @param db The database.
*/
open fun onDestructiveMigration(db: SupportSQLiteDatabase) {}
+
+ /**
+ * Called after the database was destructively migrated.
+ *
+ * @param connection The database connection.
+ */
+ actual open fun onDestructiveMigration(connection: SQLiteConnection) {
+ if (connection is SupportSQLiteConnection) {
+ onDestructiveMigration(connection.db)
+ }
+ }
+
+ /**
+ * Called when the database has been opened.
+ *
+ * @param db The database.
+ */
+ open fun onOpen(db: SupportSQLiteDatabase) {}
+
+ /**
+ * Called when the database has been opened.
+ *
+ * @param connection The database connection.
+ */
+ actual open fun onOpen(connection: SQLiteConnection) {
+ if (connection is SupportSQLiteConnection) {
+ onOpen(connection.db)
+ }
+ }
}
/**
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/AutoCloser.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoCloser.android.kt
similarity index 98%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/AutoCloser.android.kt
rename to room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoCloser.android.kt
index 885f035..3ff8ac4 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/AutoCloser.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoCloser.android.kt
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import androidx.annotation.GuardedBy
-import androidx.annotation.VisibleForTesting
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
import java.io.IOException
@@ -204,7 +203,6 @@
*
* @return current ref count
*/
- @get:VisibleForTesting
internal val refCountForTest: Int
get() {
synchronized(lock) { return refCount }
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt
similarity index 99%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt
rename to room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt
index 2d29e36..dcd71f6 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelper.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import android.content.ContentResolver
import android.content.ContentValues
@@ -26,6 +26,7 @@
import android.os.CancellationSignal
import android.util.Pair
import androidx.annotation.RequiresApi
+import androidx.room.DelegatingOpenHelper
import androidx.sqlite.db.SupportSQLiteCompat
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelperFactory.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactory.android.kt
similarity index 97%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelperFactory.android.kt
rename to room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactory.android.kt
index 59ee286..1103b8a 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/AutoClosingRoomOpenHelperFactory.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactory.android.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import androidx.sqlite.db.SupportSQLiteOpenHelper
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelper.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelper.android.kt
similarity index 97%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelper.android.kt
rename to room/room-runtime/src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelper.android.kt
index 883fe26..d174d2a 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelper.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelper.android.kt
@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import android.content.Context
import android.util.Log
+import androidx.room.DatabaseConfiguration
+import androidx.room.DelegatingOpenHelper
import androidx.room.Room.LOG_TAG
import androidx.room.util.copy
import androidx.room.util.readVersion
@@ -38,7 +40,7 @@
* storage.
*/
@Suppress("BanSynchronizedMethods")
-internal class SQLiteCopyOpenHelper(
+internal class PrePackagedCopyOpenHelper(
private val context: Context,
private val copyFromAssetPath: String?,
private val copyFromFile: File?,
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelperFactory.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelperFactory.android.kt
similarity index 89%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelperFactory.android.kt
rename to room/room-runtime/src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelperFactory.android.kt
index 9ecb35b..e06f99c 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/SQLiteCopyOpenHelperFactory.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/PrePackagedCopyOpenHelperFactory.android.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import androidx.sqlite.db.SupportSQLiteOpenHelper
import java.io.File
@@ -22,9 +22,9 @@
/**
* Implementation of [SupportSQLiteOpenHelper.Factory] that creates
- * [SQLiteCopyOpenHelper].
+ * [PrePackagedCopyOpenHelper].
*/
-internal class SQLiteCopyOpenHelperFactory(
+internal class PrePackagedCopyOpenHelperFactory(
private val mCopyFromAssetPath: String?,
private val mCopyFromFile: File?,
private val mCopyFromInputStream: Callable<InputStream>?,
@@ -33,7 +33,7 @@
override fun create(
configuration: SupportSQLiteOpenHelper.Configuration
): SupportSQLiteOpenHelper {
- return SQLiteCopyOpenHelper(
+ return PrePackagedCopyOpenHelper(
configuration.context,
mCopyFromAssetPath,
mCopyFromFile,
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorDatabase.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt
similarity index 98%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorDatabase.android.kt
rename to room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt
index 0f1ec17..8d34df5 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorDatabase.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import android.database.Cursor
import android.database.sqlite.SQLiteTransactionListener
import android.os.CancellationSignal
+import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteQuery
import androidx.sqlite.db.SupportSQLiteStatement
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorOpenHelper.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorOpenHelper.android.kt
similarity index 92%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorOpenHelper.android.kt
rename to room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorOpenHelper.android.kt
index c15672f..9405ae6 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorOpenHelper.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorOpenHelper.android.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
+import androidx.room.DelegatingOpenHelper
+import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
import java.util.concurrent.Executor
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorOpenHelperFactory.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorOpenHelperFactory.android.kt
similarity index 95%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorOpenHelperFactory.android.kt
rename to room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorOpenHelperFactory.android.kt
index 1d86465..e704e6b 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorOpenHelperFactory.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorOpenHelperFactory.android.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
+import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
import java.util.concurrent.Executor
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorProgram.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorProgram.android.kt
similarity index 98%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorProgram.android.kt
rename to room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorProgram.android.kt
index 49aea34..3d841dfc5 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorProgram.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorProgram.android.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import androidx.sqlite.db.SupportSQLiteProgram
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorStatement.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorStatement.android.kt
similarity index 97%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorStatement.android.kt
rename to room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorStatement.android.kt
index 37eca34..5980fee 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/QueryInterceptorStatement.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorStatement.android.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
+import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteStatement
import java.util.concurrent.Executor
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/util/DBUtil.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/util/DBUtil.android.kt
index e31ac8c..d67a2cf 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/util/DBUtil.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/util/DBUtil.android.kt
@@ -27,10 +27,12 @@
import androidx.annotation.RestrictTo
import androidx.room.RoomDatabase
import androidx.room.TransactionElement
+import androidx.room.Transactor
+import androidx.room.coroutines.RawConnectionAccessor
import androidx.room.driver.SupportSQLiteConnection
import androidx.room.getQueryDispatcher
import androidx.room.transactionDispatcher
-import androidx.sqlite.SQLiteStatement
+import androidx.sqlite.SQLiteConnection
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteQuery
import java.io.File
@@ -42,80 +44,88 @@
import kotlinx.coroutines.withContext
/**
- * Performs a single database read operation.
+ * Performs a database operation.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-actual suspend fun <R> performReadSuspending(
+actual suspend fun <R> performSuspending(
db: RoomDatabase,
- sql: String,
- block: (SQLiteStatement) -> R
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> R
+): R = db.compatCoroutineExecute(inTransaction) {
+ db.internalPerform(isReadOnly, inTransaction, block)
+}
+
+/**
+ * Blocking version of [performSuspending]
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <R> performBlocking(
+ db: RoomDatabase,
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> R
): R {
- if (db.inCompatibilityMode()) {
- if (db.isOpenInternal && db.inTransaction()) {
- return db.perform(true, sql, block)
+ db.assertNotMainThread()
+ db.assertNotSuspendingTransaction()
+ return runBlocking {
+ db.internalPerform(isReadOnly, inTransaction, block)
+ }
+}
+
+private suspend inline fun <R> RoomDatabase.internalPerform(
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ crossinline block: (SQLiteConnection) -> R
+): R = useConnection(isReadOnly) { transactor ->
+ if (inTransaction) {
+ val type = if (isReadOnly) {
+ Transactor.SQLiteTransactionType.DEFERRED
+ } else {
+ Transactor.SQLiteTransactionType.IMMEDIATE
}
- val context =
- coroutineContext[TransactionElement]?.transactionDispatcher ?: db.getQueryDispatcher()
- return withContext(context) {
- db.perform(true, sql, block)
+ // TODO(b/309990302): Commonize Invalidation Tracker
+ if (inCompatibilityMode() && !isReadOnly) {
+ invalidationTracker.syncTriggers(openHelper.writableDatabase)
}
+ val result = transactor.withTransaction(type) {
+ val rawConnection = (this as RawConnectionAccessor).rawConnection
+ block.invoke(rawConnection)
+ }
+ if (inCompatibilityMode() && !isReadOnly && !transactor.inTransaction()) {
+ invalidationTracker.refreshVersionsAsync()
+ }
+ result
} else {
- return db.perform(true, sql, block)
+ val rawConnection = (transactor as RawConnectionAccessor).rawConnection
+ block.invoke(rawConnection)
}
}
/**
- * Performs a single database read transaction operation.
+ * Compatibility dispatcher behaviour in [androidx.room.CoroutinesRoom.execute] for driver codegen
+ * utility functions.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-actual suspend fun <R> performReadTransactionSuspending(
- db: RoomDatabase,
- sql: String,
- block: (SQLiteStatement) -> R
+private suspend inline fun <R> RoomDatabase.compatCoroutineExecute(
+ inTransaction: Boolean,
+ crossinline block: suspend () -> R
): R {
- if (db.inCompatibilityMode()) {
- if (db.isOpenInternal && db.inTransaction()) {
- return db.performTransaction(true) { it.usePrepared(sql, block) }
+ if (inCompatibilityMode()) {
+ if (isOpenInternal && inTransaction()) {
+ return block.invoke()
}
val context =
- coroutineContext[TransactionElement]?.transactionDispatcher ?: db.transactionDispatcher
+ coroutineContext[TransactionElement]?.transactionDispatcher
+ ?: if (inTransaction) transactionDispatcher else getQueryDispatcher()
return withContext(context) {
- db.performTransaction(true) { it.usePrepared(sql, block) }
+ block.invoke()
}
} else {
- return db.performTransaction(true) { it.usePrepared(sql, block) }
+ return block.invoke()
}
}
/**
- * Performs a single database read query operation.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-fun <R> performReadBlocking(
- db: RoomDatabase,
- sql: String,
- block: (SQLiteStatement) -> R
-): R {
- db.assertNotMainThread()
- db.assertNotSuspendingTransaction()
- return runBlocking { db.perform(isReadOnly = true, sql, block) }
-}
-
-/**
- * Performs a single database read query transaction operation.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-fun <R> performReadTransactionBlocking(
- db: RoomDatabase,
- sql: String,
- block: (SQLiteStatement) -> R
-): R {
- db.assertNotMainThread()
- db.assertNotSuspendingTransaction()
- return runBlocking { db.performTransaction(isReadOnly = true) { it.usePrepared(sql, block) } }
-}
-
-/**
* Performs the SQLiteQuery on the given database.
*
* This util method encapsulates copying the cursor if the `maybeCopy` parameter is
diff --git a/room/room-runtime/src/androidUnitTest/kotlin/androidx/room/SQLiteCopyOpenHelperTest.kt b/room/room-runtime/src/androidUnitTest/kotlin/androidx/room/support/PrePackagedCopyOpenHelperTest.kt
similarity index 96%
rename from room/room-runtime/src/androidUnitTest/kotlin/androidx/room/SQLiteCopyOpenHelperTest.kt
rename to room/room-runtime/src/androidUnitTest/kotlin/androidx/room/support/PrePackagedCopyOpenHelperTest.kt
index 2e4fba7..3757aab 100644
--- a/room/room-runtime/src/androidUnitTest/kotlin/androidx/room/SQLiteCopyOpenHelperTest.kt
+++ b/room/room-runtime/src/androidUnitTest/kotlin/androidx/room/support/PrePackagedCopyOpenHelperTest.kt
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package androidx.room
+package androidx.room.support
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.AssetManager
import android.os.Build
import androidx.annotation.RequiresApi
+import androidx.room.DatabaseConfiguration
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
import java.io.File
@@ -48,7 +49,7 @@
import org.mockito.Mockito.`when`
@RunWith(JUnit4::class)
-class SQLiteCopyOpenHelperTest {
+class PrePackagedCopyOpenHelperTest {
companion object {
const val DB_NAME = "test.db"
@@ -222,7 +223,7 @@
}
internal fun createOpenHelper(copyFromAssetFile: File) =
- SQLiteCopyOpenHelper(
+ PrePackagedCopyOpenHelper(
context,
copyFromAssetFile.name,
null,
@@ -282,7 +283,8 @@
fun main(args: Array<String>) {
val tmpDir = File(args[0])
val copyFromFile = File(args[1])
- val openHelper = SQLiteCopyOpenHelperTest().apply { setupMocks(tmpDir, copyFromFile) }
+ val openHelper = PrePackagedCopyOpenHelperTest()
+ .apply { setupMocks(tmpDir, copyFromFile) }
.createOpenHelper(copyFromFile)
openHelper.writableDatabase
}
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/DatabaseConfiguration.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/DatabaseConfiguration.kt
index 9f440ff..58f3fe1 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/DatabaseConfiguration.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/DatabaseConfiguration.kt
@@ -27,6 +27,7 @@
val name: String?
/* Collection of available migrations. */
val migrationContainer: RoomDatabase.MigrationContainer
+ val callbacks: List<RoomDatabase.Callback>?
val journalMode: RoomDatabase.JournalMode
val requireMigration: Boolean
val allowDestructiveMigrationOnDowngrade: Boolean
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityDeleteOrUpdateAdapter.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityDeleteOrUpdateAdapter.kt
new file mode 100644
index 0000000..485b155
--- /dev/null
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityDeleteOrUpdateAdapter.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room
+
+import androidx.annotation.RestrictTo
+import androidx.room.util.getTotalChangedRows
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteStatement
+import androidx.sqlite.use
+
+/**
+ * Implementations of this class know how to delete or update a particular entity.
+ *
+ * This is an library class and all of its implementations are auto-generated.
+ *
+ * @constructor Creates a DeletionOrUpdateAdapter that can delete or update the entity type T on the
+ * given database.
+ *
+ * @param T The type parameter of the entity to be deleted
+*/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+abstract class EntityDeleteOrUpdateAdapter<T> {
+ /**
+ * Create the deletion or update query
+ *
+ * @return An SQL query that can delete or update instances of T.
+ */
+ protected abstract fun createQuery(): String
+
+ /**
+ * Binds the entity into the given statement.
+ *
+ * @param statement The SQLite statement that prepared for the query returned from
+ * createQuery.
+ * @param entity The entity of type T.
+ */
+ protected abstract fun bind(statement: SQLiteStatement, entity: T)
+
+ /**
+ * Deletes or updates the given entities in the database and returns the affected row count.
+ *
+ * @param entity The entity to delete or update
+ * @return The number of affected rows
+ */
+ fun handle(
+ connection: SQLiteConnection,
+ entity: T
+ ): Int {
+ connection.prepare(createQuery()).use { stmt ->
+ bind(stmt, entity)
+ stmt.step()
+ }
+ return getTotalChangedRows(connection)
+ }
+
+ /**
+ * Deletes or updates the given entities in the database and returns the affected row count.
+ *
+ * @param entities Entities to delete or update
+ * @return The number of affected rows
+ */
+ fun handleMultiple(
+ connection: SQLiteConnection,
+ entities: Iterable<T>
+ ): Int {
+ var total = 0
+ connection.prepare(createQuery()).use { stmt ->
+ entities.forEach { entity ->
+ bind(stmt, entity)
+ stmt.step()
+ stmt.reset()
+ total += getTotalChangedRows(connection)
+ }
+ }
+ return total
+ }
+
+ /**
+ * Deletes or updates the given entities in the database and returns the affected row count.
+ *
+ * @param entities Entities to delete or update
+ * @return The number of affected rows
+ */
+ fun handleMultiple(
+ connection: SQLiteConnection,
+ entities: Array<out T>
+ ): Int {
+ var total = 0
+ connection.prepare(createQuery()).use { stmt ->
+ entities.forEach { entity ->
+ bind(stmt, entity)
+ stmt.step()
+ stmt.reset()
+ total += getTotalChangedRows(connection)
+ }
+ }
+ return total
+ }
+}
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityInsertAdapter.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityInsertAdapter.kt
new file mode 100644
index 0000000..3b29a47
--- /dev/null
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityInsertAdapter.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room
+
+import androidx.annotation.RestrictTo
+import androidx.room.util.getLastInsertedRowId
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteStatement
+import androidx.sqlite.use
+
+/**
+ * Implementations of this class knows how to insert a particular entity.
+ *
+ * This is an internal library class and all of its implementations are auto-generated.
+ *
+ * @constructor Creates an InsertionAdapter that can insert the entity type T into the given
+ * database.
+ *
+ * @param T The type parameter of the entity to be inserted
+*/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+abstract class EntityInsertAdapter<T> {
+ /**
+ * Create the query.
+ *
+ * @return The SQL query to prepare.
+ */
+ protected abstract fun createQuery(): String
+
+ /**
+ * Binds the entity into the given statement.
+ *
+ * @param statement The SQLite statement that prepared for the query returned from
+ * createInsertQuery.
+ * @param entity The entity of type T.
+ */
+ protected abstract fun bind(statement: SQLiteStatement, entity: T)
+
+ /**
+ * Inserts the entity into the database.
+ *
+ * @param entity The entity to insert
+ */
+ fun insert(connection: SQLiteConnection, entity: T) {
+ connection.prepare(createQuery()).use { stmt ->
+ bind(stmt, entity)
+ stmt.step()
+ }
+ }
+
+ /**
+ * Inserts the given entities into the database.
+ *
+ * @param entities Entities to insert
+ */
+ fun insert(
+ connection: SQLiteConnection,
+ entities: Array<out T>
+ ) {
+ connection.prepare(createQuery()).use { stmt ->
+ entities.forEach { entity ->
+ bind(stmt, entity)
+ stmt.step()
+ stmt.reset()
+ }
+ }
+ }
+
+ /**
+ * Inserts the given entities into the database.
+ *
+ * @param entities Entities to insert
+ */
+ fun insert(
+ connection: SQLiteConnection,
+ entities: Iterable<T>
+ ) {
+ connection.prepare(createQuery()).use { stmt ->
+ entities.forEach { entity ->
+ bind(stmt, entity)
+ stmt.step()
+ stmt.reset()
+ }
+ }
+ }
+
+ /**
+ * Inserts the given entity into the database and returns the row id.
+ *
+ * @param entity The entity to insert
+ * @return The SQLite row id or -1 if no row is inserted
+ */
+ fun insertAndReturnId(
+ connection: SQLiteConnection,
+ entity: T
+ ): Long {
+ connection.prepare(createQuery()).use { stmt ->
+ bind(stmt, entity)
+ stmt.step()
+ }
+ return getLastInsertedRowId(connection)
+ }
+
+ /**
+ * Inserts the given entities into the database and returns the row ids.
+ *
+ * @param entities Entities to insert
+ * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
+ */
+ internal fun insertAndReturnIdsArray(
+ connection: SQLiteConnection,
+ entities: Collection<T>
+ ): LongArray {
+ return connection.prepare(createQuery()).use { stmt ->
+ LongArray(entities.size) { index ->
+ bind(stmt, entities.elementAt(index))
+ stmt.step()
+ stmt.reset()
+ getLastInsertedRowId(connection)
+ }
+ }
+ }
+
+ /**
+ * Inserts the given entities into the database and returns the row ids.
+ *
+ * @param entities Entities to insert
+ * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
+ */
+ internal fun insertAndReturnIdsArray(
+ connection: SQLiteConnection,
+ entities: Array<out T>
+ ): LongArray {
+ return connection.prepare(createQuery()).use { stmt ->
+ LongArray(entities.size) { index ->
+ bind(stmt, entities.elementAt(index))
+ stmt.step()
+ stmt.reset()
+ getLastInsertedRowId(connection)
+ }
+ }
+ }
+
+ /**
+ * Inserts the given entities into the database and returns the row ids.
+ *
+ * @param entities Entities to insert
+ * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
+ */
+ internal fun insertAndReturnIdsArrayBox(
+ connection: SQLiteConnection,
+ entities: Collection<T>
+ ): Array<out Long> {
+ return connection.prepare(createQuery()).use { stmt ->
+ Array(entities.size) { index ->
+ bind(stmt, entities.elementAt(index))
+ stmt.step()
+ stmt.reset()
+ getLastInsertedRowId(connection)
+ }
+ }
+ }
+
+ /**
+ * Inserts the given entities into the database and returns the row ids.
+ *
+ * @param entities Entities to insert
+ * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
+ */
+ internal fun insertAndReturnIdsArrayBox(
+ connection: SQLiteConnection,
+ entities: Array<out T>
+ ): Array<out Long> {
+ return connection.prepare(createQuery()).use { stmt ->
+ Array(entities.size) { index ->
+ bind(stmt, entities.elementAt(index))
+ stmt.step()
+ stmt.reset()
+ getLastInsertedRowId(connection)
+ }
+ }
+ }
+
+ /**
+ * Inserts the given entities into the database and returns the row ids.
+ *
+ * @param entities Entities to insert
+ * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
+ */
+ internal fun insertAndReturnIdsList(
+ connection: SQLiteConnection,
+ entities: Array<out T>
+ ): List<Long> {
+ return buildList {
+ connection.prepare(createQuery()).use { stmt ->
+ entities.forEach { entity ->
+ bind(stmt, entity)
+ stmt.step()
+ stmt.reset()
+ add(getLastInsertedRowId(connection))
+ }
+ }
+ }
+ }
+
+ /**
+ * Inserts the given entities into the database and returns the row ids.
+ *
+ * @param entities Entities to insert
+ * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
+ */
+ internal fun insertAndReturnIdsList(
+ connection: SQLiteConnection,
+ entities: Collection<T>
+ ): List<Long> {
+ return buildList {
+ connection.prepare(createQuery()).use { stmt ->
+ entities.forEach { entity ->
+ bind(stmt, entity)
+ stmt.step()
+ stmt.reset()
+ add(getLastInsertedRowId(connection))
+ }
+ }
+ }
+ }
+}
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityUpsertAdapter.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityUpsertAdapter.kt
new file mode 100644
index 0000000..878cca0
--- /dev/null
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/EntityUpsertAdapter.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+
+import androidx.annotation.RestrictTo
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteException
+
+/**
+ * This class knows how to insert an entity. When the insertion fails
+ * due to a unique constraint conflict (i.e. primary key conflict),
+ * it will perform an update.
+ *
+ * @constructor Creates an EntityUpsertionAdapter that can upsert entity of type T
+ * into the database using the given insertionAdapter to perform insertion and
+ * updateAdapter to perform update when the insertion fails
+ *
+ * @param T the type param of the entity to be upserted
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+class EntityUpsertAdapter<T>(
+ private val entityInsertAdapter: EntityInsertAdapter<T>,
+ private val updateAdapter: EntityDeleteOrUpdateAdapter<T>
+) {
+
+ /**
+ * Inserts the entity into the database. If a constraint exception is thrown
+ * i.e. a primary key conflict, update the existing entity.
+ *
+ * @param entity The entity to insert
+ */
+ fun upsert(
+ connection: SQLiteConnection,
+ entity: T
+ ) {
+ try {
+ entityInsertAdapter.insert(connection, entity)
+ } catch (ex: SQLiteException) {
+ checkUniquenessException(ex)
+ updateAdapter.handle(connection, entity)
+ }
+ }
+
+ /**
+ * Upserts (insert or update) the given entities into the database.
+ * For each entity, insert if it is not already in the database
+ * update if there is a constraint conflict.
+ *
+ * @param entities array of entities to upsert
+ */
+ fun upsert(
+ connection: SQLiteConnection,
+ entities: Array<out T>
+ ) {
+ entities.forEach { entity ->
+ try {
+ entityInsertAdapter.insert(connection, entity)
+ } catch (ex: SQLiteException) {
+ checkUniquenessException(ex)
+ updateAdapter.handle(connection, entity)
+ }
+ }
+ }
+
+ fun upsert(
+ connection: SQLiteConnection,
+ entities: Iterable<T>
+ ) {
+ entities.forEach { entity ->
+ try {
+ entityInsertAdapter.insert(connection, entity)
+ } catch (ex: SQLiteException) {
+ checkUniquenessException(ex)
+ updateAdapter.handle(connection, entity)
+ }
+ }
+ }
+
+ /**
+ * Upserts the given entity into the database and returns the row id.
+ * If the insertion failed, update the existing entity and return -1L.
+ *
+ * @param entity The entity to upsert
+ * @return The SQLite row id or -1L if the insertion failed and update
+ * is performed
+ */
+ fun upsertAndReturnId(
+ connection: SQLiteConnection,
+ entity: T
+ ): Long {
+ return try {
+ entityInsertAdapter.insertAndReturnId(connection, entity)
+ } catch (ex: SQLiteException) {
+ checkUniquenessException(ex)
+ updateAdapter.handle(connection, entity)
+ -1L
+ }
+ }
+
+ /**
+ * Upserts the given entities into the database and returns the row ids.
+ *
+ * @param entities Entities to upsert
+ * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1L
+ */
+ fun upsertAndReturnIdsArray(
+ connection: SQLiteConnection,
+ entities: Array<out T>
+ ): LongArray {
+ return LongArray(entities.size) { index ->
+ try {
+ entityInsertAdapter.insertAndReturnId(connection, entities[index])
+ } catch (ex: SQLiteException) {
+ checkUniquenessException(ex)
+ updateAdapter.handle(connection, entities[index])
+ -1L
+ }
+ }
+ }
+
+ fun upsertAndReturnIdsArray(
+ connection: SQLiteConnection,
+ entities: Collection<T>
+ ): LongArray {
+ return LongArray(entities.size) { index ->
+ try {
+ entityInsertAdapter.insertAndReturnId(connection, entities.elementAt(index))
+ } catch (ex: SQLiteException) {
+ checkUniquenessException(ex)
+ updateAdapter.handle(connection, entities.elementAt(index))
+ -1L
+ }
+ }
+ }
+
+ fun upsertAndReturnIdsList(
+ connection: SQLiteConnection,
+ entities: Array<out T>
+ ): List<Long> {
+ return buildList {
+ entities.forEach { entity ->
+ try {
+ add(entityInsertAdapter.insertAndReturnId(connection, entity))
+ } catch (ex: SQLiteException) {
+ checkUniquenessException(ex)
+ updateAdapter.handle(connection, entity)
+ add(-1L)
+ }
+ }
+ }
+ }
+
+ fun upsertAndReturnIdsList(
+ connection: SQLiteConnection,
+ entities: Collection<T>
+ ): List<Long> {
+ return buildList {
+ entities.forEach { entity ->
+ try {
+ add(entityInsertAdapter.insertAndReturnId(connection, entity))
+ } catch (ex: SQLiteException) {
+ checkUniquenessException(ex)
+ updateAdapter.handle(connection, entity)
+ add(-1L)
+ }
+ }
+ }
+ }
+
+ fun upsertAndReturnIdsArrayBox(
+ connection: SQLiteConnection,
+ entities: Array<out T>
+ ): Array<out Long> {
+ return Array(entities.size) { index ->
+ try {
+ entityInsertAdapter.insertAndReturnId(connection, entities[index])
+ } catch (ex: SQLiteException) {
+ checkUniquenessException(ex)
+ updateAdapter.handle(connection, entities[index])
+ -1L
+ }
+ }
+ }
+
+ fun upsertAndReturnIdsArrayBox(
+ connection: SQLiteConnection,
+ entities: Collection<T>
+ ): Array<out Long> {
+ return Array(entities.size) { index ->
+ try {
+ entityInsertAdapter.insertAndReturnId(connection, entities.elementAt(index))
+ } catch (ex: SQLiteException) {
+ checkUniquenessException(ex)
+ updateAdapter.handle(connection, entities.elementAt(index))
+ -1L
+ }
+ }
+ }
+
+ /**
+ * Verify if the exception is caused by Uniqueness constraint (Primary Key Conflict).
+ * If yes, upsert should update the existing one. If not, upsert should re-throw the
+ * exception.
+ * For android of version newer than KITKAT(19), SQLite supports ErrorCode. Otherwise,
+ * check with Error Message.
+ *
+ * @param ex the exception thrown by the insert attempt
+ */
+ private fun checkUniquenessException(ex: SQLiteException) {
+ val message = ex.message ?: throw ex
+ val hasUniqueConstraintEx =
+ message.contains(ErrorMsg, ignoreCase = true) ||
+ message.contains(SQLITE_CONSTRAINT_UNIQUE) ||
+ message.contains(SQLITE_CONSTRAINT_PRIMARYKEY)
+
+ if (!hasUniqueConstraintEx) {
+ throw ex
+ }
+ }
+
+ companion object {
+ /**
+ * The error code defined by SQLite Library for SQLITE_CONSTRAINT_PRIMARYKEY error
+ * Only used by android of version newer than 19.
+ */
+ private const val SQLITE_CONSTRAINT_PRIMARYKEY = "1555"
+
+ /**
+ * The error code defined by SQLite Library for SQLITE_CONSTRAINT_UNIQUE error.
+ */
+ private const val SQLITE_CONSTRAINT_UNIQUE = "2067"
+
+ /**
+ * For android of version below and including 19, use error message instead of
+ * error code to check
+ */
+ private const val ErrorMsg = "unique"
+ }
+}
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt
index 90e8236..8614d63 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt
@@ -35,6 +35,7 @@
protected abstract val configuration: DatabaseConfiguration
protected abstract val connectionPool: ConnectionPool
protected abstract val openDelegate: RoomOpenDelegate
+ protected abstract val callbacks: List<RoomDatabase.Callback>
abstract suspend fun <R> useConnection(
isReadOnly: Boolean,
@@ -233,7 +234,15 @@
else -> error("Can't get max number of writers for journal mode '$this'")
}
- protected abstract fun invokeCreateCallback(connection: SQLiteConnection)
- protected abstract fun invokeDestructiveMigrationCallback(connection: SQLiteConnection)
- protected abstract fun invokeOpenCallback(connection: SQLiteConnection)
+ private fun invokeCreateCallback(connection: SQLiteConnection) {
+ callbacks.forEach { it.onCreate(connection) }
+ }
+
+ private fun invokeDestructiveMigrationCallback(connection: SQLiteConnection) {
+ callbacks.forEach { it.onDestructiveMigration(connection) }
+ }
+
+ private fun invokeOpenCallback(connection: SQLiteConnection) {
+ callbacks.forEach { it.onOpen(connection) }
+ }
}
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt
index bb3ec23..4c356f0 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt
@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-// TODO(b/317120607): Rename to RoomDatabaseKt once the room-ktx artifact is merged.
-@file:JvmName("RoomDatabaseUtils")
+@file:JvmMultifileClass
+@file:JvmName("RoomDatabaseKt")
package androidx.room
@@ -26,7 +25,7 @@
import androidx.room.util.isAssignableFrom
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.SQLiteDriver
-import androidx.sqlite.SQLiteStatement
+import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.reflect.KClass
@@ -164,19 +163,9 @@
fun close()
/**
- * Performs a database operation.
+ * Use a connection to perform database operations.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- suspend fun <R> perform(isReadOnly: Boolean, sql: String, block: (SQLiteStatement) -> R): R
-
- /**
- * Performs a database transaction operation.
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- suspend fun <R> performTransaction(
- isReadOnly: Boolean,
- block: suspend (TransactionScope<R>) -> R
- ): R
+ internal suspend fun <R> useConnection(isReadOnly: Boolean, block: suspend (Transactor) -> R): R
/**
* Journal modes for SQLite database.
@@ -210,6 +199,14 @@
fun setDriver(driver: SQLiteDriver): Builder<T>
/**
+ * Adds a [Callback] to this database.
+ *
+ * @param callback The callback.
+ * @return This builder instance.
+ */
+ fun addCallback(callback: Callback): Builder<T>
+
+ /**
* Creates the database and initializes it.
*
* @return A new database instance.
@@ -255,6 +252,34 @@
migrationStart: Int
): Pair<Map<Int, Migration>, Iterable<Int>>?
}
+
+ /**
+ * Callback for [RoomDatabase]
+ */
+ abstract class Callback() {
+ /**
+ * Called when the database is created for the first time.
+ *
+ * This function called after all the tables are created.
+ *
+ * @param connection The database connection.
+ */
+ open fun onCreate(connection: SQLiteConnection)
+
+ /**
+ * Called after the database was destructively migrated.
+ *
+ * @param connection The database connection.
+ */
+ open fun onDestructiveMigration(connection: SQLiteConnection)
+
+ /**
+ * Called when the database has been opened.
+ *
+ * @param connection The database connection.
+ */
+ open fun onOpen(connection: SQLiteConnection)
+ }
}
internal fun RoomDatabase.validateAutoMigrations(configuration: DatabaseConfiguration) {
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPool.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPool.kt
index 23dcc06..0e2a3bd 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPool.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPool.kt
@@ -96,3 +96,10 @@
maxNumOfReaders: Int,
maxNumOfWriters: Int
): ConnectionPool = ConnectionPoolImpl(driver, maxNumOfReaders, maxNumOfWriters)
+
+/**
+ * Defines an object that provides 'raw' access to a connection.
+ */
+internal interface RawConnectionAccessor {
+ val rawConnection: SQLiteConnection
+}
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPoolImpl.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPoolImpl.kt
index 74321c8..562bbfb 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPoolImpl.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPoolImpl.kt
@@ -278,12 +278,15 @@
private class PooledConnectionImpl(
val delegate: ConnectionWithLock,
val isReadOnly: Boolean,
-) : Transactor {
+) : Transactor, RawConnectionAccessor {
private val transactionStack = ArrayDeque<TransactionItem>()
private val _isRecycled = atomic(false)
private val isRecycled by _isRecycled
+ override val rawConnection: SQLiteConnection
+ get() = delegate
+
override suspend fun <R> usePrepared(
sql: String,
block: (SQLiteStatement) -> R
@@ -398,7 +401,11 @@
private class RollbackException(val result: Any?) : Throwable()
- private inner class TransactionImpl<T> : TransactionScope<T> {
+ private inner class TransactionImpl<T> : TransactionScope<T>, RawConnectionAccessor {
+
+ override val rawConnection: SQLiteConnection
+ get() = this@PooledConnectionImpl.rawConnection
+
override suspend fun <R> usePrepared(sql: String, block: (SQLiteStatement) -> R): R =
this@PooledConnectionImpl.usePrepared(sql, block)
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/ConnectionUtil.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/ConnectionUtil.kt
new file mode 100644
index 0000000..b5d56c9
--- /dev/null
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/ConnectionUtil.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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("SQLiteConnectionUtil")
+
+package androidx.room.util
+
+import androidx.annotation.RestrictTo
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.use
+import kotlin.jvm.JvmName
+
+/**
+ * Returns the ROWID of the last row insert from the database connection which invoked the
+ * function.
+ *
+ * See (official SQLite documentation)[http://www.sqlite.org/lang_corefunc.html#last_insert_rowid]
+ * for details.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun getLastInsertedRowId(connection: SQLiteConnection): Long {
+ return connection.prepare("SELECT last_insert_rowid()").use {
+ it.step()
+ it.getLong(0)
+ }
+}
+
+/**
+ * Returns the number of database rows that were changed or inserted or deleted by the most
+ * recently completed INSERT, DELETE, or UPDATE statement.
+ *
+ * See the (official SQLite documentation)[http://www.sqlite.org/lang_corefunc.html#changes] for
+ * details.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun getTotalChangedRows(connection: SQLiteConnection): Int {
+ return connection.prepare("SELECT changes()").use {
+ it.step()
+ it.getLong(0).toInt()
+ }
+}
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/DBUtil.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/DBUtil.kt
index a3448d8..786a5f3 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/DBUtil.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/DBUtil.kt
@@ -20,33 +20,19 @@
package androidx.room.util
import androidx.annotation.RestrictTo
-import androidx.room.PooledConnection
import androidx.room.RoomDatabase
import androidx.sqlite.SQLiteConnection
-import androidx.sqlite.SQLiteStatement
import androidx.sqlite.execSQL
import androidx.sqlite.use
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
-/**
- * Performs a single database read operation.
- */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-expect suspend fun <R> performReadSuspending(
+expect suspend fun <R> performSuspending(
db: RoomDatabase,
- sql: String,
- block: (SQLiteStatement) -> R
-): R
-
-/**
- * Performs a single database read transaction operation.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-expect suspend fun <R> performReadTransactionSuspending(
- db: RoomDatabase,
- sql: String,
- block: (SQLiteStatement) -> R
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> R
): R
/**
@@ -73,33 +59,3 @@
}
}
}
-
-/**
- * Returns the ROWID of the last row insert from the database connection which invoked the
- * function.
- *
- * See (official SQLite documentation)[http://www.sqlite.org/lang_corefunc.html#last_insert_rowid]
- * for details.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-suspend fun PooledConnection.getLastInsertedRowId(): Long {
- return this.usePrepared("SELECT last_insert_rowid()") {
- it.step()
- it.getLong(0)
- }
-}
-
-/**
- * Returns the number of database rows that were changed or inserted or deleted by the most
- * recently completed INSERT, DELETE, or UPDATE statement.
- *
- * See the (official SQLite documentation)[http://www.sqlite.org/lang_corefunc.html#changes] for
- * details.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-suspend fun PooledConnection.getTotalChangedRows(): Long {
- return this.usePrepared("SELECT changes()") {
- it.step()
- it.getLong(0)
- }
-}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/util/StringUtil.android.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StringUtil.kt
similarity index 89%
rename from room/room-runtime/src/androidMain/kotlin/androidx/room/util/StringUtil.android.kt
rename to room/room-runtime/src/commonMain/kotlin/androidx/room/util/StringUtil.kt
index f8565fa..c34a6eb 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/util/StringUtil.android.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StringUtil.kt
@@ -13,18 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
@file:JvmName("StringUtil")
@file:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
package androidx.room.util
-import android.util.Log
import androidx.annotation.RestrictTo
-import java.lang.NumberFormatException
-import java.lang.StringBuilder
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmName
@Suppress("unused")
@JvmField
+@Deprecated("No longer used by generated code")
val EMPTY_STRING_ARRAY = arrayOfNulls<String>(0)
/**
@@ -32,10 +33,8 @@
*
* @return A new or recycled StringBuilder
*/
-fun newStringBuilder(): StringBuilder {
- // TODO pool:
- return StringBuilder()
-}
+@Deprecated("No longer used by generated code")
+fun newStringBuilder(): StringBuilder = StringBuilder()
/**
* Adds bind variable placeholders (?) to the given string. Each placeholder is separated
@@ -56,7 +55,6 @@
/**
* Splits a comma separated list of integers to integer list.
*
- *
* If an input is malformed, it is omitted from the result.
*
* @param input Comma separated list of integers.
@@ -67,7 +65,6 @@
try {
item.toInt()
} catch (ex: NumberFormatException) {
- Log.e("ROOM", "Malformed integer list", ex)
null
}
}
diff --git a/room/room-runtime/src/commonTest/kotlin/androidx/room/coroutines/BaseConnectionPoolTest.kt b/room/room-runtime/src/commonTest/kotlin/androidx/room/coroutines/BaseConnectionPoolTest.kt
index 06d2f33..16a7bac 100644
--- a/room/room-runtime/src/commonTest/kotlin/androidx/room/coroutines/BaseConnectionPoolTest.kt
+++ b/room/room-runtime/src/commonTest/kotlin/androidx/room/coroutines/BaseConnectionPoolTest.kt
@@ -23,8 +23,6 @@
import androidx.room.exclusiveTransaction
import androidx.room.execSQL
import androidx.room.immediateTransaction
-import androidx.room.util.getLastInsertedRowId
-import androidx.room.util.getTotalChangedRows
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.SQLiteDriver
import androidx.sqlite.SQLiteException
@@ -127,44 +125,6 @@
}
@Test
- fun readLastRowId() = runTest {
- val driver = setupDriver()
- val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
- pool.useWriterConnection { connection ->
- connection.execSQL("CREATE TABLE Test (col)")
- connection.usePrepared("INSERT INTO Test (col) VALUES (?)") {
- it.bindNull(1)
- assertThat(it.step()).isFalse() // SQLITE_DONE
- }
- connection.usePrepared("INSERT INTO Test (col) VALUES (?)") {
- it.bindNull(1)
- assertThat(it.step()).isFalse() // SQLITE_DONE
- }
- val lastRowId = connection.getLastInsertedRowId()
- assertThat(lastRowId).isEqualTo(2)
- }
- pool.close()
- }
-
- @Test
- fun changes() = runTest {
- val driver = setupDriver()
- val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
- pool.useWriterConnection { connection ->
- connection.execSQL("CREATE TABLE Test (col)")
- connection.usePrepared("INSERT INTO Test (col) VALUES (?),(?),(?)") {
- it.bindNull(1)
- it.bindNull(2)
- it.bindNull(3)
- assertThat(it.step()).isFalse() // SQLITE_DONE
- }
- val changes = connection.getTotalChangedRows()
- assertThat(changes).isEqualTo(3)
- }
- pool.close()
- }
-
- @Test
fun reusingConnectionOnLaunch() = runTest {
val driver = setupDriver()
val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
diff --git a/room/room-runtime/src/jvmMain/kotlin/androidx/room/DatabaseConfiguration.jvm.kt b/room/room-runtime/src/jvmMain/kotlin/androidx/room/DatabaseConfiguration.jvm.kt
index d99923a..5c298ca 100644
--- a/room/room-runtime/src/jvmMain/kotlin/androidx/room/DatabaseConfiguration.jvm.kt
+++ b/room/room-runtime/src/jvmMain/kotlin/androidx/room/DatabaseConfiguration.jvm.kt
@@ -27,6 +27,7 @@
actual val name: String?,
/* Collection of available migrations. */
actual val migrationContainer: RoomDatabase.MigrationContainer,
+ actual val callbacks: List<RoomDatabase.Callback>?,
actual val journalMode: RoomDatabase.JournalMode,
actual val requireMigration: Boolean,
actual val allowDestructiveMigrationOnDowngrade: Boolean,
diff --git a/room/room-runtime/src/jvmMain/kotlin/androidx/room/Room.jvm.kt b/room/room-runtime/src/jvmMain/kotlin/androidx/room/Room.jvm.kt
new file mode 100644
index 0000000..4364d86
--- /dev/null
+++ b/room/room-runtime/src/jvmMain/kotlin/androidx/room/Room.jvm.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+
+// TODO(b/325111971): Add a builder API with a reflective approach.
+object Room {
+
+ /**
+ * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
+ * database disappears when the process is killed. Once a database is built, you should keep a
+ * reference to it and re-use it.
+ *
+ * @param T The type of the database class.
+ * @param factory The lambda calling `initializeImpl()` on the database class which returns
+ * the generated database implementation.
+ * @return A `RoomDatabaseBuilder<T>` which you can use to create the database.
+ */
+ inline fun <reified T : RoomDatabase> inMemoryDatabaseBuilder(
+ noinline factory: () -> T
+ ): RoomDatabase.Builder<T> {
+ return RoomDatabase.Builder(T::class, null, factory)
+ }
+
+ /**
+ * Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you
+ * should keep a reference to it and re-use it.
+ *
+ * @param T The type of the database class.
+ * @param name The name of the database file.
+ * @param factory The lambda calling `initializeImpl()` on the database class which returns
+ * the generated database implementation.
+ * @return A `RoomDatabaseBuilder<T>` which you can use to create the database.
+ */
+ inline fun <reified T : RoomDatabase> databaseBuilder(
+ name: String,
+ noinline factory: () -> T
+ ): RoomDatabase.Builder<T> {
+ return RoomDatabase.Builder(T::class, name, factory)
+ }
+}
diff --git a/room/room-runtime/src/jvmMain/kotlin/androidx/room/RoomDatabase.jvm.kt b/room/room-runtime/src/jvmMain/kotlin/androidx/room/RoomDatabase.jvm.kt
index 3299961..108926e 100644
--- a/room/room-runtime/src/jvmMain/kotlin/androidx/room/RoomDatabase.jvm.kt
+++ b/room/room-runtime/src/jvmMain/kotlin/androidx/room/RoomDatabase.jvm.kt
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+@file:JvmMultifileClass
+@file:JvmName("RoomDatabaseKt")
+
package androidx.room
import androidx.annotation.RestrictTo
@@ -21,7 +24,6 @@
import androidx.room.migration.Migration
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.SQLiteDriver
-import androidx.sqlite.SQLiteStatement
import kotlin.reflect.KClass
/**
@@ -73,7 +75,8 @@
): RoomConnectionManager = RoomJvmConnectionManager(
configuration = configuration,
sqliteDriver = checkNotNull(configuration.sqliteDriver),
- openDelegate = createOpenDelegate() as RoomOpenDelegate
+ openDelegate = createOpenDelegate() as RoomOpenDelegate,
+ callbacks = configuration.callbacks ?: emptyList()
)
/**
@@ -198,36 +201,13 @@
}
/**
- * Performs a database operation.
+ * Use a connection to perform database operations.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- actual suspend fun <R> perform(
+ internal actual suspend fun <R> useConnection(
isReadOnly: Boolean,
- sql: String,
- block: (SQLiteStatement) -> R
+ block: suspend (Transactor) -> R
): R {
- return connectionManager.useConnection(isReadOnly) { connection ->
- connection.usePrepared(sql, block)
- }
- }
-
- /**
- * Performs a database operation in a transaction.
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- actual suspend fun <R> performTransaction(
- isReadOnly: Boolean,
- block: suspend (TransactionScope<R>) -> R
- ): R {
- return connectionManager.useConnection(isReadOnly) { transactor ->
- val type = if (isReadOnly) {
- Transactor.SQLiteTransactionType.DEFERRED
- } else {
- Transactor.SQLiteTransactionType.IMMEDIATE
- }
- // TODO: Notify Invalidation Tracker before and after transaction block.
- transactor.withTransaction(type, block)
- }
+ return connectionManager.useConnection(isReadOnly, block)
}
/**
@@ -264,6 +244,7 @@
) {
private var driver: SQLiteDriver? = null
+ private val callbacks = mutableListOf<Callback>()
/**
* Sets the [SQLiteDriver] implementation to be used by Room to open database connections.
@@ -278,6 +259,16 @@
}
/**
+ * Adds a [Callback] to this database.
+ *
+ * @param callback The callback.
+ * @return This builder instance.
+ */
+ actual fun addCallback(callback: Callback) = apply {
+ this.callbacks.add(callback)
+ }
+
+ /**
* Creates the database and initializes it.
*
* @return A new database instance.
@@ -290,6 +281,7 @@
val configuration = DatabaseConfiguration(
name = name,
migrationContainer = MigrationContainer(),
+ callbacks = callbacks,
journalMode = JournalMode.WRITE_AHEAD_LOGGING,
requireMigration = false,
allowDestructiveMigrationOnDowngrade = false,
@@ -356,4 +348,32 @@
return targetNodes to targetNodes.keys.sortedDescending()
}
}
+
+ /**
+ * Callback for [RoomDatabase]
+ */
+ actual abstract class Callback {
+ /**
+ * Called when the database is created for the first time.
+ *
+ * This function called after all the tables are created.
+ *
+ * @param connection The database connection.
+ */
+ actual open fun onCreate(connection: SQLiteConnection) {}
+
+ /**
+ * Called after the database was destructively migrated.
+ *
+ * @param connection The database connection.
+ */
+ actual open fun onDestructiveMigration(connection: SQLiteConnection) {}
+
+ /**
+ * Called when the database has been opened.
+ *
+ * @param connection The database connection.
+ */
+ actual open fun onOpen(connection: SQLiteConnection) {}
+ }
}
diff --git a/room/room-runtime/src/jvmMain/kotlin/androidx/room/RoomJvmConnectionManager.jvm.kt b/room/room-runtime/src/jvmMain/kotlin/androidx/room/RoomJvmConnectionManager.jvm.kt
index 87711dc..27a1936 100644
--- a/room/room-runtime/src/jvmMain/kotlin/androidx/room/RoomJvmConnectionManager.jvm.kt
+++ b/room/room-runtime/src/jvmMain/kotlin/androidx/room/RoomJvmConnectionManager.jvm.kt
@@ -19,14 +19,14 @@
import androidx.room.coroutines.ConnectionPool
import androidx.room.coroutines.newConnectionPool
import androidx.room.coroutines.newSingleConnectionPool
-import androidx.sqlite.SQLiteConnection
import androidx.sqlite.SQLiteDriver
internal class RoomJvmConnectionManager(
override val configuration: DatabaseConfiguration,
sqliteDriver: SQLiteDriver,
override val openDelegate: RoomOpenDelegate,
-) : RoomConnectionManager() {
+ override val callbacks: List<RoomDatabase.Callback>,
+ ) : RoomConnectionManager() {
override val connectionPool: ConnectionPool =
if (configuration.name == null) {
@@ -50,16 +50,4 @@
fun close() {
connectionPool.close()
}
-
- override fun invokeCreateCallback(connection: SQLiteConnection) {
- // TODO(b/316944352): Add mirror to RoomDatabase.Callback
- }
-
- override fun invokeDestructiveMigrationCallback(connection: SQLiteConnection) {
- // TODO(b/316944352): Add mirror to RoomDatabase.Callback
- }
-
- override fun invokeOpenCallback(connection: SQLiteConnection) {
- // TODO(b/316944352): Add mirror to RoomDatabase.Callback
- }
}
diff --git a/room/room-runtime/src/jvmMain/kotlin/androidx/room/util/DBUtil.jvm.kt b/room/room-runtime/src/jvmMain/kotlin/androidx/room/util/DBUtil.jvm.kt
index 6533748..bafcd85c 100644
--- a/room/room-runtime/src/jvmMain/kotlin/androidx/room/util/DBUtil.jvm.kt
+++ b/room/room-runtime/src/jvmMain/kotlin/androidx/room/util/DBUtil.jvm.kt
@@ -21,26 +21,33 @@
import androidx.annotation.RestrictTo
import androidx.room.RoomDatabase
-import androidx.sqlite.SQLiteStatement
-import kotlin.jvm.JvmMultifileClass
-import kotlin.jvm.JvmName
+import androidx.room.Transactor
+import androidx.room.coroutines.RawConnectionAccessor
+import androidx.sqlite.SQLiteConnection
/**
- * Performs a single database read operation.
+ * Performs a database operation.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-actual suspend fun <R> performReadSuspending(
+actual suspend fun <R> performSuspending(
db: RoomDatabase,
- sql: String,
- block: (SQLiteStatement) -> R
-): R = db.perform(true, sql, block)
-
-/**
- * Performs a single database read transaction operation.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-actual suspend fun <R> performReadTransactionSuspending(
- db: RoomDatabase,
- sql: String,
- block: (SQLiteStatement) -> R
-): R = db.performTransaction(true) { it.usePrepared(sql, block) }
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> R
+): R = db.useConnection(isReadOnly) { transactor ->
+ if (inTransaction) {
+ val type = if (isReadOnly) {
+ Transactor.SQLiteTransactionType.DEFERRED
+ } else {
+ Transactor.SQLiteTransactionType.IMMEDIATE
+ }
+ // TODO(b/309990302): Notify Invalidation Tracker before and after transaction block.
+ transactor.withTransaction(type) {
+ val rawConnection = (this as RawConnectionAccessor).rawConnection
+ block.invoke(rawConnection)
+ }
+ } else {
+ val rawConnection = (transactor as RawConnectionAccessor).rawConnection
+ block.invoke(rawConnection)
+ }
+}
diff --git a/room/room-runtime/src/nativeMain/kotlin/androidx/room/DatabaseConfiguration.native.kt b/room/room-runtime/src/nativeMain/kotlin/androidx/room/DatabaseConfiguration.native.kt
index 4a101df..dbe8213 100644
--- a/room/room-runtime/src/nativeMain/kotlin/androidx/room/DatabaseConfiguration.native.kt
+++ b/room/room-runtime/src/nativeMain/kotlin/androidx/room/DatabaseConfiguration.native.kt
@@ -27,6 +27,7 @@
actual val name: String?,
/* Collection of available migrations. */
actual val migrationContainer: RoomDatabase.MigrationContainer,
+ actual val callbacks: List<RoomDatabase.Callback>?,
actual val journalMode: RoomDatabase.JournalMode,
actual val requireMigration: Boolean,
actual val allowDestructiveMigrationOnDowngrade: Boolean,
diff --git a/room/room-runtime/src/nativeMain/kotlin/androidx/room/Room.native.kt b/room/room-runtime/src/nativeMain/kotlin/androidx/room/Room.native.kt
index c46b725..6eb5937 100644
--- a/room/room-runtime/src/nativeMain/kotlin/androidx/room/Room.native.kt
+++ b/room/room-runtime/src/nativeMain/kotlin/androidx/room/Room.native.kt
@@ -19,12 +19,25 @@
object Room {
/**
+ * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
+ * database disappears when the process is killed. Once a database is built, you should keep a
+ * reference to it and re-use it.
+ *
+ * @param T The type of the database class.
+ * @param factory The lambda calling `initializeImpl()` on the database class which returns
+ * the generated database implementation.
+ * @return A `RoomDatabaseBuilder<T>` which you can use to create the database.
+ */
+ inline fun <reified T : RoomDatabase> inMemoryDatabaseBuilder(
+ noinline factory: () -> T
+ ): RoomDatabase.Builder<T> {
+ return RoomDatabase.Builder(T::class, null, factory)
+ }
+
+ /**
* Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you
* should keep a reference to it and re-use it.
*
- * This [databaseBuilder] avoids using reflection to access the generated database
- * implementation.
- *
* @param T The type of the database class.
* @param name The name of the database file.
* @param factory The lambda calling `initializeImpl()` on the database class which returns
@@ -35,10 +48,6 @@
name: String,
noinline factory: () -> T
): RoomDatabase.Builder<T> {
- return RoomDatabase.Builder(
- T::class,
- name,
- factory
- )
+ return RoomDatabase.Builder(T::class, name, factory)
}
}
diff --git a/room/room-runtime/src/nativeMain/kotlin/androidx/room/RoomDatabase.native.kt b/room/room-runtime/src/nativeMain/kotlin/androidx/room/RoomDatabase.native.kt
index ec3eed9..50a27be 100644
--- a/room/room-runtime/src/nativeMain/kotlin/androidx/room/RoomDatabase.native.kt
+++ b/room/room-runtime/src/nativeMain/kotlin/androidx/room/RoomDatabase.native.kt
@@ -21,7 +21,6 @@
import androidx.room.migration.Migration
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.SQLiteDriver
-import androidx.sqlite.SQLiteStatement
import kotlin.reflect.KClass
/**
@@ -73,7 +72,8 @@
): RoomConnectionManager = RoomNativeConnectionManager(
configuration = configuration,
sqliteDriver = checkNotNull(configuration.sqliteDriver),
- openDelegate = createOpenDelegate() as RoomOpenDelegate
+ openDelegate = createOpenDelegate() as RoomOpenDelegate,
+ callbacks = configuration.callbacks ?: emptyList()
)
/**
@@ -198,36 +198,13 @@
}
/**
- * Performs a database operation.
+ * Use a connection to perform database operations.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- actual suspend fun <R> perform(
+ internal actual suspend fun <R> useConnection(
isReadOnly: Boolean,
- sql: String,
- block: (SQLiteStatement) -> R
+ block: suspend (Transactor) -> R
): R {
- return connectionManager.useConnection(isReadOnly) { connection ->
- connection.usePrepared(sql, block)
- }
- }
-
- /**
- * Performs a database operation in a transaction.
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- actual suspend fun <R> performTransaction(
- isReadOnly: Boolean,
- block: suspend (TransactionScope<R>) -> R
- ): R {
- return connectionManager.useConnection(isReadOnly) { transactor ->
- val type = if (isReadOnly) {
- Transactor.SQLiteTransactionType.DEFERRED
- } else {
- Transactor.SQLiteTransactionType.IMMEDIATE
- }
- // TODO: Notify Invalidation Tracker before and after transaction block.
- transactor.withTransaction(type, block)
- }
+ return connectionManager.useConnection(isReadOnly, block)
}
/**
@@ -264,6 +241,7 @@
) {
private var driver: SQLiteDriver? = null
+ private val callbacks = mutableListOf<Callback>()
/**
* Sets the [SQLiteDriver] implementation to be used by Room to open database connections.
@@ -278,6 +256,16 @@
}
/**
+ * Adds a [Callback] to this database.
+ *
+ * @param callback The callback.
+ * @return This builder instance.
+ */
+ actual fun addCallback(callback: Callback) = apply {
+ this.callbacks.add(callback)
+ }
+
+ /**
* Creates the database and initializes it.
*
* @return A new database instance.
@@ -290,6 +278,7 @@
val configuration = DatabaseConfiguration(
name = name,
migrationContainer = MigrationContainer(),
+ callbacks = callbacks,
journalMode = JournalMode.WRITE_AHEAD_LOGGING,
requireMigration = false,
allowDestructiveMigrationOnDowngrade = false,
@@ -356,4 +345,32 @@
return targetNodes to targetNodes.keys.sortedDescending()
}
}
+
+ /**
+ * Callback for [RoomDatabase]
+ */
+ actual abstract class Callback {
+ /**
+ * Called when the database is created for the first time.
+ *
+ * This function called after all the tables are created.
+ *
+ * @param connection The database connection.
+ */
+ actual open fun onCreate(connection: SQLiteConnection) {}
+
+ /**
+ * Called after the database was destructively migrated.
+ *
+ * @param connection The database connection.
+ */
+ actual open fun onDestructiveMigration(connection: SQLiteConnection) {}
+
+ /**
+ * Called when the database has been opened.
+ *
+ * @param connection The database connection.
+ */
+ actual open fun onOpen(connection: SQLiteConnection) {}
+ }
}
diff --git a/room/room-runtime/src/nativeMain/kotlin/androidx/room/RoomNativeConnectionManager.native.kt b/room/room-runtime/src/nativeMain/kotlin/androidx/room/RoomNativeConnectionManager.native.kt
index e26cbd8..84bb0c94 100644
--- a/room/room-runtime/src/nativeMain/kotlin/androidx/room/RoomNativeConnectionManager.native.kt
+++ b/room/room-runtime/src/nativeMain/kotlin/androidx/room/RoomNativeConnectionManager.native.kt
@@ -19,13 +19,13 @@
import androidx.room.coroutines.ConnectionPool
import androidx.room.coroutines.newConnectionPool
import androidx.room.coroutines.newSingleConnectionPool
-import androidx.sqlite.SQLiteConnection
import androidx.sqlite.SQLiteDriver
internal class RoomNativeConnectionManager(
override val configuration: DatabaseConfiguration,
sqliteDriver: SQLiteDriver,
override val openDelegate: RoomOpenDelegate,
+ override val callbacks: List<RoomDatabase.Callback>,
) : RoomConnectionManager() {
override val connectionPool: ConnectionPool =
@@ -50,16 +50,4 @@
fun close() {
connectionPool.close()
}
-
- override fun invokeCreateCallback(connection: SQLiteConnection) {
- // TODO(b/316944352): Add mirror to RoomDatabase.Callback
- }
-
- override fun invokeDestructiveMigrationCallback(connection: SQLiteConnection) {
- // TODO(b/316944352): Add mirror to RoomDatabase.Callback
- }
-
- override fun invokeOpenCallback(connection: SQLiteConnection) {
- // TODO(b/316944352): Add mirror to RoomDatabase.Callback
- }
}
diff --git a/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/DBUtil.native.kt b/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/DBUtil.native.kt
index 6533748..668d5a5 100644
--- a/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/DBUtil.native.kt
+++ b/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/DBUtil.native.kt
@@ -21,26 +21,35 @@
import androidx.annotation.RestrictTo
import androidx.room.RoomDatabase
-import androidx.sqlite.SQLiteStatement
+import androidx.room.Transactor
+import androidx.room.coroutines.RawConnectionAccessor
+import androidx.sqlite.SQLiteConnection
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
/**
- * Performs a single database read operation.
+ * Performs a database operation.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-actual suspend fun <R> performReadSuspending(
+actual suspend fun <R> performSuspending(
db: RoomDatabase,
- sql: String,
- block: (SQLiteStatement) -> R
-): R = db.perform(true, sql, block)
-
-/**
- * Performs a single database read transaction operation.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-actual suspend fun <R> performReadTransactionSuspending(
- db: RoomDatabase,
- sql: String,
- block: (SQLiteStatement) -> R
-): R = db.performTransaction(true) { it.usePrepared(sql, block) }
+ isReadOnly: Boolean,
+ inTransaction: Boolean,
+ block: (SQLiteConnection) -> R
+): R = db.useConnection(isReadOnly) { transactor ->
+ if (inTransaction) {
+ val type = if (isReadOnly) {
+ Transactor.SQLiteTransactionType.DEFERRED
+ } else {
+ Transactor.SQLiteTransactionType.IMMEDIATE
+ }
+ // TODO(b/309990302): Notify Invalidation Tracker before and after transaction block.
+ transactor.withTransaction(type) {
+ val rawConnection = (this as RawConnectionAccessor).rawConnection
+ block.invoke(rawConnection)
+ }
+ } else {
+ val rawConnection = (transactor as RawConnectionAccessor).rawConnection
+ block.invoke(rawConnection)
+ }
+}
diff --git a/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/KClassUtil.native.kt b/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/KClassUtil.native.kt
index 838420c..57b174c 100644
--- a/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/KClassUtil.native.kt
+++ b/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/KClassUtil.native.kt
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-@file:JvmName("KClassUtil")
@file:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.room.util
import androidx.annotation.RestrictTo
-import kotlin.jvm.JvmName
import kotlin.reflect.KClass
/**
diff --git a/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/StatementUtil.native.kt b/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/StatementUtil.native.kt
index 437fe5c..46a0575 100644
--- a/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/StatementUtil.native.kt
+++ b/room/room-runtime/src/nativeMain/kotlin/androidx/room/util/StatementUtil.native.kt
@@ -14,14 +14,9 @@
* limitations under the License.
*/
-@file:JvmMultifileClass
-@file:JvmName("SQLiteStatementUtil")
-
package androidx.room.util
import androidx.sqlite.SQLiteStatement
-import kotlin.jvm.JvmMultifileClass
-import kotlin.jvm.JvmName
/**
* Returns the zero-based index for the given column name, or -1 if the column doesn't exist.
diff --git a/room/room-runtime/src/nativeTest/kotlin/androidx.room/coroutines/BundledSQLiteConnectionPoolTest.kt b/room/room-runtime/src/nativeTest/kotlin/androidx.room/coroutines/BundledSQLiteConnectionPoolTest.kt
index e0f9b03..8124a7c 100644
--- a/room/room-runtime/src/nativeTest/kotlin/androidx.room/coroutines/BundledSQLiteConnectionPoolTest.kt
+++ b/room/room-runtime/src/nativeTest/kotlin/androidx.room/coroutines/BundledSQLiteConnectionPoolTest.kt
@@ -18,13 +18,14 @@
import androidx.sqlite.SQLiteDriver
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlin.random.Random
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import platform.posix.remove
class BundledSQLiteConnectionPoolTest : BaseConnectionPoolTest() {
- private val filename = "/tmp/test.db"
+ private val filename = "/tmp/test-${Random.nextInt()}.db"
override fun getDriver(): SQLiteDriver {
return BundledSQLiteDriver(filename)
diff --git a/settings.gradle b/settings.gradle
index 24eb0af..c25060e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -531,6 +531,8 @@
includeProject(":compose:material:material-ripple", [BuildType.COMPOSE])
includeProject(":compose:material:material-ripple:material-ripple-benchmark", "compose/material/material-ripple/benchmark", [BuildType.COMPOSE])
includeProject(":compose:material:material:icons:generator", [BuildType.COMPOSE])
+includeProject(":compose:material:material-navigation", [BuildType.COMPOSE])
+includeProject(":compose:material:material-navigation-samples", "compose/material/material-navigation/samples", [BuildType.COMPOSE])
includeProject(":compose:material:material:integration-tests:material-demos", [BuildType.COMPOSE])
includeProject(":compose:material:material:integration-tests:material-catalog", [BuildType.COMPOSE])
includeProject(":compose:material3:material3:integration-tests:material3-demos", [BuildType.COMPOSE])
@@ -635,6 +637,7 @@
includeProject(":credentials:credentials-fido", [BuildType.MAIN])
includeProject(":credentials:credentials-play-services-auth", [BuildType.MAIN])
includeProject(":credentials:credentials-provider", [BuildType.MAIN])
+includeProject(":credentials:credentials-e2ee", [BuildType.MAIN])
includeProject(":cursoradapter:cursoradapter", [BuildType.MAIN])
includeProject(":customview:customview", [BuildType.MAIN])
includeProject(":customview:customview-poolingcontainer", [BuildType.MAIN, BuildType.COMPOSE])
@@ -840,6 +843,7 @@
includeProject(":privacysandbox:sdkruntime:test-sdks:v1", [BuildType.MAIN])
includeProject(":privacysandbox:sdkruntime:test-sdks:v2", [BuildType.MAIN])
includeProject(":privacysandbox:sdkruntime:test-sdks:v4", [BuildType.MAIN])
+includeProject(":privacysandbox:sdkruntime:test-sdks:v5", [BuildType.MAIN])
includeProject(":privacysandbox:tools:tools", [BuildType.MAIN])
includeProject(":privacysandbox:tools:tools-apicompiler", [BuildType.MAIN])
includeProject(":privacysandbox:tools:tools-apigenerator", [BuildType.MAIN])
diff --git a/sqlite/integration-tests/driver-conformance-test/build.gradle b/sqlite/integration-tests/driver-conformance-test/build.gradle
index ab1abc6..7dfaa55 100644
--- a/sqlite/integration-tests/driver-conformance-test/build.gradle
+++ b/sqlite/integration-tests/driver-conformance-test/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
+
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
plugins {
@@ -29,8 +29,6 @@
id("com.android.library")
}
-def nativeEnabled = KmpPlatformsKt.enableNative(project)
-
androidXMultiplatform {
android()
androidNative()
@@ -67,13 +65,11 @@
implementation(libs.kotlinTestJunit)
}
}
- if (nativeEnabled) {
- nativeTest {
- dependsOn(commonTest)
- dependencies {
- implementation(project(":sqlite:sqlite-framework"))
- implementation(project(":sqlite:sqlite-bundled"))
- }
+ nativeTest {
+ dependsOn(commonTest)
+ dependencies {
+ implementation(project(":sqlite:sqlite-framework"))
+ implementation(project(":sqlite:sqlite-bundled"))
}
}
targets.all { target ->
diff --git a/sqlite/sqlite-bundled/build.gradle b/sqlite/sqlite-bundled/build.gradle
index 025a419..c7f512f 100644
--- a/sqlite/sqlite-bundled/build.gradle
+++ b/sqlite/sqlite-bundled/build.gradle
@@ -22,7 +22,6 @@
* modifying its settings.
*/
import androidx.build.AndroidXConfig
-import androidx.build.KmpPlatformsKt
import androidx.build.PlatformIdentifier
import androidx.build.ProjectLayoutType
import androidx.build.Publish
@@ -96,8 +95,6 @@
}
}
-def nativeEnabled = KmpPlatformsKt.enableNative(project)
-
androidXMultiplatform {
// List of targets for native compilation that are needed for JVM / ART tests.
// Note that even if the native KMP targets are disabled, we still need to compile the C
@@ -137,12 +134,10 @@
}
// add SQLite compilation output as an artifact of a producer configuration
- if (nativeEnabled) {
- artifacts.add(
- "linuxSharedArchive",
- sqliteCompilation.sharedArchiveOutputFor(KonanTarget.LINUX_X64.INSTANCE)
- )
- }
+ artifacts.add(
+ "linuxSharedArchive",
+ sqliteCompilation.sharedArchiveOutputFor(KonanTarget.LINUX_X64.INSTANCE)
+ )
artifacts.add(
"sqliteSources",
prepareSqliteSourcesTask.map { it.destinationDirectory }
@@ -220,12 +215,10 @@
androidMain {
dependsOn(androidJvmCommonMain)
}
- if (nativeEnabled) {
- nativeMain {
- dependsOn(commonMain)
- dependencies {
- implementation(project(":sqlite:sqlite-framework"))
- }
+ nativeMain {
+ dependsOn(commonMain)
+ dependencies {
+ implementation(project(":sqlite:sqlite-framework"))
}
}
targets.all { target ->
diff --git a/sqlite/sqlite-framework/build.gradle b/sqlite/sqlite-framework/build.gradle
index 0e13cf7..9f4c995 100644
--- a/sqlite/sqlite-framework/build.gradle
+++ b/sqlite/sqlite-framework/build.gradle
@@ -21,7 +21,6 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
import androidx.build.PlatformIdentifier
import androidx.build.Publish
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -32,7 +31,6 @@
id("com.android.library")
}
-def nativeEnabled = KmpPlatformsKt.enableNative(project)
configurations {
// Configuration for resolving shared archive file of androidx's SQLite compilation
@@ -92,13 +90,11 @@
implementation(libs.testCore)
}
}
- if (nativeEnabled) {
- nativeMain {
- dependsOn(commonMain)
- }
- nativeTest {
- dependsOn(commonTest)
- }
+ nativeMain {
+ dependsOn(commonMain)
+ }
+ nativeTest {
+ dependsOn(commonTest)
}
targets.all { target ->
if (target.platformType == KotlinPlatformType.native) {
diff --git a/sqlite/sqlite/build.gradle b/sqlite/sqlite/build.gradle
index ce5dd33..9a1b291 100644
--- a/sqlite/sqlite/build.gradle
+++ b/sqlite/sqlite/build.gradle
@@ -21,7 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
-import androidx.build.KmpPlatformsKt
+
import androidx.build.PlatformIdentifier
import androidx.build.Publish
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -31,7 +31,6 @@
id("com.android.library")
}
-def nativeEnabled = KmpPlatformsKt.enableNative(project)
androidXMultiplatform {
android()
@@ -69,10 +68,8 @@
jvmMain {
dependsOn(commonMain)
}
- if (nativeEnabled) {
- nativeMain {
- dependsOn(commonMain)
- }
+ nativeMain {
+ dependsOn(commonMain)
}
targets.all { target ->
if (target.platformType == KotlinPlatformType.native) {
diff --git a/stableaidl/stableaidl-gradle-plugin/lint-baseline.xml b/stableaidl/stableaidl-gradle-plugin/lint-baseline.xml
index 713ffa8..6ce5472 100644
--- a/stableaidl/stableaidl-gradle-plugin/lint-baseline.xml
+++ b/stableaidl/stableaidl-gradle-plugin/lint-baseline.xml
@@ -19,4 +19,13 @@
file="src/main/java/androidx/stableaidl/internal/process/GradleProcessResult.kt"/>
</issue>
+ <issue
+ id="WithPluginClasspathUsage"
+ message="Avoid usage of GradleRunner#withPluginClasspath, which is broken. Instead use something like https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/testkit#gradle-testkit-support-plugin"
+ errorLine1=" .withPluginClasspath()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/test/java/androidx/stableaidl/StableAidlPluginTest.kt"/>
+ </issue>
+
</issues>
diff --git a/test/ext/junit-gtest/build.gradle b/test/ext/junit-gtest/build.gradle
index 4f5b227..0ca8d49 100644
--- a/test/ext/junit-gtest/build.gradle
+++ b/test/ext/junit-gtest/build.gradle
@@ -52,6 +52,7 @@
android {
defaultConfig {
+ minSdkVersion 21
externalNativeBuild {
cmake {
arguments "-DANDROID_STL=c++_shared"
diff --git a/test/integration-tests/junit-gtest-test/build.gradle b/test/integration-tests/junit-gtest-test/build.gradle
index 6bc805a..878d17f 100644
--- a/test/integration-tests/junit-gtest-test/build.gradle
+++ b/test/integration-tests/junit-gtest-test/build.gradle
@@ -44,6 +44,7 @@
android {
defaultConfig {
+ minSdkVersion 21
externalNativeBuild {
cmake {
arguments "-DANDROID_STL=c++_shared"
diff --git a/test/uiautomator/uiautomator/api/current.ignore b/test/uiautomator/uiautomator/api/current.ignore
deleted file mode 100644
index 3d43de2..0000000
--- a/test/uiautomator/uiautomator/api/current.ignore
+++ /dev/null
@@ -1,11 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercentage(float):
- Added method androidx.test.uiautomator.UiObject2.setGestureMarginPercentage(float)
-AddedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginsPercentage(float, float, float, float):
- Added method androidx.test.uiautomator.UiObject2.setGestureMarginsPercentage(float,float,float,float)
-
-
-RemovedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercent(float):
- Removed method androidx.test.uiautomator.UiObject2.setGestureMarginPercent(float)
-RemovedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercent(float, float, float, float):
- Removed method androidx.test.uiautomator.UiObject2.setGestureMarginPercent(float,float,float,float)
diff --git a/test/uiautomator/uiautomator/api/restricted_current.ignore b/test/uiautomator/uiautomator/api/restricted_current.ignore
deleted file mode 100644
index 3d43de2..0000000
--- a/test/uiautomator/uiautomator/api/restricted_current.ignore
+++ /dev/null
@@ -1,11 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercentage(float):
- Added method androidx.test.uiautomator.UiObject2.setGestureMarginPercentage(float)
-AddedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginsPercentage(float, float, float, float):
- Added method androidx.test.uiautomator.UiObject2.setGestureMarginsPercentage(float,float,float,float)
-
-
-RemovedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercent(float):
- Removed method androidx.test.uiautomator.UiObject2.setGestureMarginPercent(float)
-RemovedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercent(float, float, float, float):
- Removed method androidx.test.uiautomator.UiObject2.setGestureMarginPercent(float,float,float,float)
diff --git a/testutils/testutils-runtime/src/main/java/androidx/testutils/ActivityScenario.kt b/testutils/testutils-runtime/src/main/java/androidx/testutils/ActivityScenario.kt
index 8dc2fca..bdb44b6 100644
--- a/testutils/testutils-runtime/src/main/java/androidx/testutils/ActivityScenario.kt
+++ b/testutils/testutils-runtime/src/main/java/androidx/testutils/ActivityScenario.kt
@@ -17,6 +17,7 @@
package androidx.testutils
import android.app.Activity
+import androidx.fragment.app.FragmentActivity
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.rules.ActivityScenarioRule
import java.io.Closeable
@@ -47,6 +48,16 @@
return value
}
+inline fun <reified A : FragmentActivity> ActivityScenario<A>.waitForExecution(cycles: Int = 2) {
+ try {
+ for (i in 0 until cycles) {
+ withActivity {}
+ }
+ } catch (throwable: Throwable) {
+ throw RuntimeException(throwable)
+ }
+}
+
/**
* Run [block] in a [use] block when using [ActivityScenario.launch], rather
* than just a [with] block to ensure the Activity is closed once test is complete.
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
index 79afb25..f6ff142 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
@@ -76,11 +76,11 @@
get() = if (!_maxIntrinsicWidth.isNaN()) {
_maxIntrinsicWidth
} else {
- var desiredWidth = boringMetrics?.width?.toFloat()
+ var desiredWidth = (boringMetrics?.width ?: -1).toFloat()
// boring metrics doesn't cover RTL text so we fallback to different calculation when boring
// metrics can't be calculated
- if (desiredWidth == null) {
+ if (desiredWidth < 0) {
// b/233856978, apply `ceil` function here to be consistent with the boring metrics
// width calculation that does it under the hood, too
desiredWidth = ceil(
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
index 456ef29..e2fad8c 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
@@ -209,7 +209,7 @@
*/
private val lastLineExtra: Int
- private val lineHeightSpans: Array<LineHeightStyleSpan>
+ private val lineHeightSpans: Array<LineHeightStyleSpan>?
private val rect: Rect = Rect()
@@ -319,7 +319,7 @@
val verticalPaddings = getVerticalPaddings()
lineHeightSpans = getLineHeightSpans()
- val lineHeightPaddings = getLineHeightPaddings(lineHeightSpans)
+ val lineHeightPaddings = lineHeightSpans?.getLineHeightPaddings() ?: ZeroVerticalPadding
topPadding = max(verticalPaddings.topPadding, lineHeightPaddings.topPadding)
bottomPadding = max(verticalPaddings.bottomPadding, lineHeightPaddings.bottomPadding)
@@ -337,7 +337,14 @@
rightPadding = layout.getEllipsizedRightPadding(lastLine)
}
- private val layoutHelper by lazy(LazyThreadSafetyMode.NONE) { LayoutHelper(layout) }
+ private var backingLayoutHelper: LayoutHelper? = null
+ private val layoutHelper: LayoutHelper
+ get() {
+ if (backingLayoutHelper == null) {
+ return LayoutHelper(layout).also { backingLayoutHelper = it }
+ }
+ return backingLayoutHelper!!
+ }
val text: CharSequence
get() = layout.text
@@ -942,13 +949,11 @@
private val ZeroVerticalPadding = VerticalPaddings(0, 0)
@OptIn(InternalPlatformTextApi::class)
-private fun TextLayout.getLineHeightPaddings(
- lineHeightSpans: Array<LineHeightStyleSpan>
-): VerticalPaddings {
+private fun Array<LineHeightStyleSpan>.getLineHeightPaddings(): VerticalPaddings {
var firstAscentDiff = 0
var lastDescentDiff = 0
- for (span in lineHeightSpans) {
+ for (span in this) {
if (span.firstAscentDiff < 0) {
firstAscentDiff = max(firstAscentDiff, abs(span.firstAscentDiff))
}
@@ -968,12 +973,12 @@
private fun TextLayout.getLastLineMetrics(
textPaint: TextPaint,
frameworkTextDir: TextDirectionHeuristic,
- lineHeightSpans: Array<LineHeightStyleSpan>
+ lineHeightSpans: Array<LineHeightStyleSpan>?
): FontMetricsInt? {
val lastLine = lineCount - 1
// did not check for "\n" since the last line might include zero width characters
if (layout.getLineStart(lastLine) == layout.getLineEnd(lastLine) &&
- lineHeightSpans.isNotEmpty()
+ !lineHeightSpans.isNullOrEmpty()
) {
val emptyText = SpannableString("\u200B")
val lineHeightSpan = lineHeightSpans.first()
@@ -1018,12 +1023,16 @@
}
@OptIn(InternalPlatformTextApi::class)
-private fun TextLayout.getLineHeightSpans(): Array<LineHeightStyleSpan> {
- if (text !is Spanned) return emptyArray()
+private fun TextLayout.getLineHeightSpans(): Array<LineHeightStyleSpan>? {
+ if (text !is Spanned) return null
+ // text can be empty but still include a LineHeightStyleSpan. In that case hasSpan returns false
+ // because nextSpanTransition ends up being 0 == text.length
+ if (!(text as Spanned).hasSpan(LineHeightStyleSpan::class.java) && text.isNotEmpty()) {
+ return null
+ }
val lineHeightStyleSpans = (text as Spanned).getSpans(
0, text.length, LineHeightStyleSpan::class.java
)
- if (lineHeightStyleSpans.isEmpty()) return emptyArray()
return lineHeightStyleSpans
}
diff --git a/transition/transition/api/restricted_current.ignore b/transition/transition/api/restricted_current.ignore
deleted file mode 100644
index 51635f9..0000000
--- a/transition/transition/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedMethod: androidx.transition.Transition#createAnimators(android.view.ViewGroup, androidx.transition.TransitionValuesMaps, androidx.transition.TransitionValuesMaps, java.util.ArrayList<androidx.transition.TransitionValues>, java.util.ArrayList<androidx.transition.TransitionValues>):
- Removed method androidx.transition.Transition.createAnimators(android.view.ViewGroup,androidx.transition.TransitionValuesMaps,androidx.transition.TransitionValuesMaps,java.util.ArrayList<androidx.transition.TransitionValues>,java.util.ArrayList<androidx.transition.TransitionValues>)
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt
index fa20a3c..a2445a9 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt
@@ -22,10 +22,13 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
+import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
import androidx.testutils.waitForExecution
+import androidx.testutils.withActivity
+import androidx.testutils.withUse
import androidx.transition.test.R
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
@@ -46,6 +49,7 @@
FragmentTransitionTestActivity::class.java
)
+ @Ignore // b/324309532
@Test
fun replaceOperationWithTransitionsThenGestureBack() {
val fm1 = activityRule.activity.supportFragmentManager
@@ -193,6 +197,7 @@
assertThat(fragment2.requireView()).isNotNull()
}
+ @Ignore // b/324309532
@Test
fun replaceOperationWithTransitionsThenGestureBackTwice() {
val fm1 = activityRule.activity.supportFragmentManager
@@ -445,4 +450,63 @@
// Make sure the original fragment was correctly readded to the container
assertThat(fragment1.requireView().parent).isNotNull()
}
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun replaceOperationWithAnimatorsInterruptCommit() {
+ withUse(ActivityScenario.launch(FragmentTransitionTestActivity::class.java)) {
+ val fm1 = withActivity { supportFragmentManager }
+
+ val fragment1 = TransitionFragment(R.layout.scene1)
+ fragment1.exitTransition.apply {
+ setRealTransition(true)
+ duration = 1000
+ }
+ fragment1.reenterTransition.apply {
+ setRealTransition(true)
+ duration = 1000
+ }
+ withActivity {
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment1, "1")
+ .addToBackStack(null)
+ .commit()
+ fm1.executePendingTransactions()
+ }
+
+ val fragment2 = TransitionFragment()
+ fragment2.enterTransition.apply {
+ setRealTransition(true)
+ duration = 1000
+ }
+ fragment2.returnTransition.apply {
+ setRealTransition(true)
+ duration = 1000
+ }
+
+ var resumedBeforeOnBackStarted = false
+ var resumedAfterOnBackStarted = false
+
+ withActivity {
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2, "2")
+ .addToBackStack(null)
+ .commit()
+ fm1.executePendingTransactions()
+
+ resumedBeforeOnBackStarted = fragment2.isResumed
+
+ val dispatcher = onBackPressedDispatcher
+ dispatcher.dispatchOnBackStarted(
+ BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT)
+ )
+ resumedAfterOnBackStarted = fragment2.isResumed
+
+ dispatcher.onBackPressed()
+ }
+
+ assertThat(resumedBeforeOnBackStarted).isFalse()
+ assertThat(resumedAfterOnBackStarted).isTrue()
+ }
+ }
}
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
index 35163ab..86bb5df 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
@@ -1117,7 +1117,11 @@
.commit()
}
- fragment1.waitForTransition()
+ // In the none reordered case, the new transaction immediately completes the pop so
+ // there is no transition to wait on.
+ if (reorderingAllowed == Reordered) {
+ fragment1.waitForTransition()
+ }
// This shouldn't give an error.
activityRule.executePendingTransactions()
diff --git a/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java b/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
index 6f6f488..2d66ce9 100644
--- a/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
+++ b/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
@@ -337,6 +337,7 @@
signal.setOnCancelListener(() -> {
if (cancelRunnable == null) {
realTransition.cancel();
+ transitionCompleteRunnable.run();
} else {
cancelRunnable.run();
}
diff --git a/tv/integration-tests/playground/build.gradle b/tv/integration-tests/playground/build.gradle
index 17edbb4..cd284a3 100644
--- a/tv/integration-tests/playground/build.gradle
+++ b/tv/integration-tests/playground/build.gradle
@@ -39,9 +39,6 @@
implementation(project(":compose:material3:material3"))
implementation(project(":navigation:navigation-compose"))
implementation(project(":profileinstaller:profileinstaller"))
- implementation(project(":compose:material:material-icons-core"))
- implementation(project(":compose:material:material-icons-extended"))
-
implementation(project(":tv:tv-foundation"))
implementation(project(":tv:tv-material"))
diff --git a/tv/integration-tests/presentation/build.gradle b/tv/integration-tests/presentation/build.gradle
index c8afcc1..bc10c93 100644
--- a/tv/integration-tests/presentation/build.gradle
+++ b/tv/integration-tests/presentation/build.gradle
@@ -34,17 +34,12 @@
implementation(libs.kotlinStdlib)
implementation("androidx.appcompat:appcompat:1.6.1")
-
implementation(project(":activity:activity-compose"))
implementation(project(":compose:material3:material3"))
implementation(project(":navigation:navigation-runtime"))
implementation(project(":profileinstaller:profileinstaller"))
- implementation(project(":compose:material:material-icons-core"))
- implementation(project(":compose:material:material-icons-extended"))
-
-// implementation 'io.coil-kt:coil-compose:2.2.2'
-// implementation 'com.google.code.gson:gson:2.8.9'
-
+ implementation("androidx.compose.material:material-icons-core:1.6.0")
+ implementation("androidx.compose.material:material-icons-extended:1.6.0")
implementation(project(":tv:tv-foundation"))
implementation(project(":tv:tv-material"))
@@ -100,8 +95,3 @@
using project(":lifecycle:lifecycle-viewmodel-savedstate")
}
}
-
-repositories {
- mavenCentral()
- google()
-}
diff --git a/tv/tv-foundation/api/current.txt b/tv/tv-foundation/api/current.txt
index 3674015..e73c12d 100644
--- a/tv/tv-foundation/api/current.txt
+++ b/tv/tv-foundation/api/current.txt
@@ -259,9 +259,7 @@
}
@SuppressCompatibility @androidx.tv.foundation.ExperimentalTvFoundationApi public enum TvKeyboardAlignment {
- method public final String? getOption();
- method public static androidx.tv.foundation.text.TvKeyboardAlignment valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.tv.foundation.text.TvKeyboardAlignment[] values();
+ method public String? getOption();
property public final String? option;
enum_constant public static final androidx.tv.foundation.text.TvKeyboardAlignment Center;
enum_constant public static final androidx.tv.foundation.text.TvKeyboardAlignment Fullscreen;
diff --git a/tv/tv-foundation/api/restricted_current.txt b/tv/tv-foundation/api/restricted_current.txt
index 3674015..e73c12d 100644
--- a/tv/tv-foundation/api/restricted_current.txt
+++ b/tv/tv-foundation/api/restricted_current.txt
@@ -259,9 +259,7 @@
}
@SuppressCompatibility @androidx.tv.foundation.ExperimentalTvFoundationApi public enum TvKeyboardAlignment {
- method public final String? getOption();
- method public static androidx.tv.foundation.text.TvKeyboardAlignment valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.tv.foundation.text.TvKeyboardAlignment[] values();
+ method public String? getOption();
property public final String? option;
enum_constant public static final androidx.tv.foundation.text.TvKeyboardAlignment Center;
enum_constant public static final androidx.tv.foundation.text.TvKeyboardAlignment Fullscreen;
diff --git a/tv/tv-foundation/build.gradle b/tv/tv-foundation/build.gradle
index 5aae051..bb1ae47 100644
--- a/tv/tv-foundation/build.gradle
+++ b/tv/tv-foundation/build.gradle
@@ -37,21 +37,17 @@
dependencies {
api(libs.kotlinStdlib)
- def composeVersion = '1.5.3'
-
- implementation(libs.kotlinStdlibCommon)
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
api("androidx.annotation:annotation:1.6.0")
- api("androidx.compose.animation:animation:$composeVersion")
- api("androidx.compose.runtime:runtime:$composeVersion")
-
- api(project(":compose:foundation:foundation"))
- api(project(":compose:ui:ui-util"))
- api("androidx.compose.ui:ui:$composeVersion")
- api("androidx.compose.foundation:foundation-layout:$composeVersion")
- api("androidx.compose.ui:ui-graphics:$composeVersion")
- api("androidx.compose.ui:ui-text:$composeVersion")
+ api("androidx.compose.animation:animation:1.6.0")
+ api("androidx.compose.foundation:foundation:1.6.0")
+ api("androidx.compose.foundation:foundation-layout:1.6.0")
+ api("androidx.compose.runtime:runtime:1.6.0")
+ api("androidx.compose.ui:ui-util:1.6.0")
+ api("androidx.compose.ui:ui:1.6.0")
+ api("androidx.compose.ui:ui-graphics:1.6.0")
+ api("androidx.compose.ui:ui-text:1.6.0")
androidTestImplementation(libs.truth)
androidTestImplementation(project(":compose:runtime:runtime"))
@@ -66,20 +62,16 @@
defaultConfig {
minSdkVersion 21
}
- lintOptions {
- disable 'IllegalExperimentalApiUsage' // TODO (b/233188423): Address before moving to beta
- }
}
androidx {
name = "TV Foundation"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2022"
description = "This library makes it easier for developers" +
"to write Jetpack Compose applications for TV devices by providing " +
"functionality to support TV specific devices sizes, shapes and d-pad navigation " +
"supported components. It builds upon the Jetpack Compose libraries."
- targetsJavaConsumers = false
}
// Functions and tasks to monitor changes in copied files.
diff --git a/tv/tv-foundation/lint-baseline.xml b/tv/tv-foundation/lint-baseline.xml
index 4ce24bc..a4b14b8 100644
--- a/tv/tv-foundation/lint-baseline.xml
+++ b/tv/tv-foundation/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
<issue
id="BanThreadSleep"
@@ -271,40 +271,4 @@
file="src/main/java/androidx/tv/foundation/lazy/list/TvLazyListIntervalContent.kt"/>
</issue>
- <issue
- id="PrimitiveInCollection"
- message="return type List<Integer> of getHeaderIndexes: replace with IntList"
- errorLine1=" val headerIndexes: List<Int> get() = _headerIndexes ?: emptyList()"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/tv/foundation/lazy/list/TvLazyListIntervalContent.kt"/>
- </issue>
-
- <issue
- id="PrimitiveInCollection"
- message="variable vard55442a7 with type List<Integer>: replace with IntList"
- errorLine1=" val headerIndexes: List<Int> get() = _headerIndexes ?: emptyList()"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/tv/foundation/lazy/list/TvLazyListIntervalContent.kt"/>
- </issue>
-
- <issue
- id="PrimitiveInCollection"
- message="variable headersIndexes with type List<Integer>: replace with IntList"
- errorLine1=" val headersIndexes = _headerIndexes ?: mutableListOf<Int>().also {"
- errorLine2=" ^">
- <location
- file="src/main/java/androidx/tv/foundation/lazy/list/TvLazyListIntervalContent.kt"/>
- </issue>
-
- <issue
- id="PrimitiveInCollection"
- message="variable vard554f0be with type List<Integer>: replace with IntList"
- errorLine1=" val headersIndexes = _headerIndexes ?: mutableListOf<Int>().also {"
- errorLine2=" ^">
- <location
- file="src/main/java/androidx/tv/foundation/lazy/list/TvLazyListIntervalContent.kt"/>
- </issue>
-
</issues>
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGrid.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGrid.kt
index b40f49c..3b7cefa 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGrid.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGrid.kt
@@ -50,7 +50,6 @@
import androidx.tv.foundation.lazy.list.calculateLazyLayoutPinnedIndices
import androidx.tv.foundation.scrollableWithPivot
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@OptIn(ExperimentalFoundationApi::class, ExperimentalTvFoundationApi::class)
@Composable
internal fun LazyGrid(
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridItemProvider.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridItemProvider.kt
index 09d6569..37a9d01 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridItemProvider.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridItemProvider.kt
@@ -27,7 +27,6 @@
import androidx.tv.foundation.lazy.layout.LazyLayoutKeyIndexMap
import androidx.tv.foundation.lazy.layout.NearestRangeKeyIndexMap
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@ExperimentalFoundationApi
internal interface LazyGridItemProvider : LazyLayoutItemProvider {
val keyIndexMap: LazyLayoutKeyIndexMap
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt
index 6431eaf..39fb88e 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt
@@ -41,7 +41,6 @@
* Measures and calculates the positions for the currently visible items. The result is produced
* as a [TvLazyGridMeasureResult] which contains all the calculations.
*/
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@OptIn(ExperimentalFoundationApi::class)
internal fun measureLazyGrid(
itemsCount: Int,
@@ -304,7 +303,6 @@
}
}
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@ExperimentalFoundationApi
private inline fun calculateExtraItems(
pinnedItems: List<Int>,
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridScrollPosition.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridScrollPosition.kt
index c9ba570f..444c0bf 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridScrollPosition.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridScrollPosition.kt
@@ -27,7 +27,6 @@
* Contains the current scroll position represented by the first visible item index and the first
* visible item scroll offset.
*/
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@OptIn(ExperimentalFoundationApi::class)
internal class LazyGridScrollPosition(
initialIndex: Int = 0,
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridSpan.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridSpan.kt
index 73e06cc..90df57b 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridSpan.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridSpan.kt
@@ -24,7 +24,6 @@
*/
@Immutable
@kotlin.jvm.JvmInline
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
value class TvGridItemSpan internal constructor(private val packedValue: Long) {
/**
* The span of the item on the current line. This will be the horizontal span for items of
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
index a6726f3..2a38162 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
@@ -23,7 +23,6 @@
import kotlin.math.min
import kotlin.math.sqrt
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@OptIn(ExperimentalFoundationApi::class)
internal class LazyGridSpanLayoutProvider(private val gridContent: LazyGridIntervalContent) {
class LineConfiguration(val firstItemIndex: Int, val spans: List<TvGridItemSpan>)
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridItemScope.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridItemScope.kt
index 7d277d1..2713efe 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridItemScope.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridItemScope.kt
@@ -28,7 +28,6 @@
/**
* Receiver scope being used by the item content parameter of [TvLazyVerticalGrid].
*/
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@Stable
@TvLazyGridScopeMarker
sealed interface TvLazyGridItemScope {
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridItemScopeImpl.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridItemScopeImpl.kt
index 82ddfdb..64b1e2a 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridItemScopeImpl.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridItemScopeImpl.kt
@@ -26,7 +26,6 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@OptIn(ExperimentalFoundationApi::class)
internal object TvLazyGridItemScopeImpl : TvLazyGridItemScope {
@ExperimentalFoundationApi
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt
index fdabf2f..9c8d941 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt
@@ -52,7 +52,6 @@
* Implementation of [LazyLayoutKeyIndexMap] indexing over given [IntRange] of items.
* Items outside of given range are considered unknown, with null returned as the index.
*/
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@ExperimentalFoundationApi
internal class NearestRangeKeyIndexMap(
nearestRange: IntRange,
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyList.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyList.kt
index 82f4199c..ee1d8a4 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyList.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyList.kt
@@ -47,7 +47,6 @@
import androidx.tv.foundation.lazy.layout.lazyLayoutSemantics
import androidx.tv.foundation.scrollableWithPivot
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@OptIn(ExperimentalFoundationApi::class, ExperimentalTvFoundationApi::class)
@Composable
internal fun LazyList(
@@ -139,7 +138,6 @@
}
/** Extracted to minimize the recomposition scope */
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@ExperimentalFoundationApi
@Composable
private fun ScrollPositionUpdater(
@@ -153,7 +151,6 @@
}
@OptIn(ExperimentalTvFoundationApi::class)
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@ExperimentalFoundationApi
@Composable
private fun rememberLazyListMeasurePolicy(
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListItemProvider.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListItemProvider.kt
index 92c4589..977196d 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListItemProvider.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListItemProvider.kt
@@ -27,7 +27,6 @@
import androidx.tv.foundation.lazy.layout.LazyLayoutKeyIndexMap
import androidx.tv.foundation.lazy.layout.NearestRangeKeyIndexMap
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@ExperimentalFoundationApi
internal interface LazyListItemProvider : LazyLayoutItemProvider {
val keyIndexMap: LazyLayoutKeyIndexMap
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt
index 951fa43..bab45c6 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt
@@ -40,7 +40,6 @@
* Measures and calculates the positions for the requested items. The result is produced
* as a [LazyListMeasureResult] which contains all the calculations.
*/
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@OptIn(ExperimentalFoundationApi::class)
internal fun measureLazyList(
itemsCount: Int,
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListScrollPosition.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListScrollPosition.kt
index eac05cd..31a5023 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListScrollPosition.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListScrollPosition.kt
@@ -27,7 +27,6 @@
* Contains the current scroll position represented by the first visible item index and the first
* visible item scroll offset.
*/
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
internal class LazyListScrollPosition(
initialIndex: Int = 0,
initialScrollOffset: Int = 0
@@ -89,7 +88,6 @@
* there were items added or removed before our current first visible item and keep this item
* as the first visible one even given that its index has been changed.
*/
- @Suppress("IllegalExperimentalApiUsage") // TODO(b/233188423): Address before moving to beta
@ExperimentalFoundationApi
fun updateScrollPositionIfTheFirstItemWasMoved(
itemProvider: LazyListItemProvider,
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazySemantics.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazySemantics.kt
index 1e54faf..45a92c1 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazySemantics.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazySemantics.kt
@@ -21,8 +21,7 @@
import androidx.compose.runtime.remember
import androidx.tv.foundation.lazy.layout.LazyLayoutSemanticState
-// TODO (b/233188423): Address IllegalExperimentalApiUsage before moving to beta
-@Suppress("ComposableModifierFactory", "IllegalExperimentalApiUsage")
+@Suppress("ComposableModifierFactory")
@ExperimentalFoundationApi
@Composable
internal fun rememberLazyListSemanticState(
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/TvLazyListIntervalContent.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/TvLazyListIntervalContent.kt
index 01ef422..9afe89c 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/TvLazyListIntervalContent.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/TvLazyListIntervalContent.kt
@@ -22,7 +22,6 @@
import androidx.compose.runtime.Composable
import androidx.tv.foundation.ExperimentalTvFoundationApi
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@OptIn(ExperimentalFoundationApi::class)
internal class TvLazyListIntervalContent(
content: TvLazyListScope.() -> Unit,
@@ -30,7 +29,9 @@
override val intervals: MutableIntervalList<TvLazyListInterval> = MutableIntervalList()
private var _headerIndexes: MutableList<Int>? = null
- val headerIndexes: List<Int> get() = _headerIndexes ?: emptyList()
+ val headerIndexes: List<Int>
+ @SuppressWarnings("PrimitiveInCollection") // List<Int>
+ get() = _headerIndexes ?: emptyList()
init {
apply(content)
@@ -67,6 +68,7 @@
)
}
+ @SuppressWarnings("PrimitiveInCollection") // mutableListOf<Int>
@ExperimentalTvFoundationApi
override fun stickyHeader(
key: Any?,
diff --git a/tv/tv-material/api/current.txt b/tv/tv-material/api/current.txt
index 736293f..c051ced 100644
--- a/tv/tv-material/api/current.txt
+++ b/tv/tv-material/api/current.txt
@@ -320,8 +320,6 @@
}
@SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public enum DrawerValue {
- method public static androidx.tv.material3.DrawerValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.tv.material3.DrawerValue[] values();
enum_constant public static final androidx.tv.material3.DrawerValue Closed;
enum_constant public static final androidx.tv.material3.DrawerValue Open;
}
diff --git a/tv/tv-material/api/restricted_current.txt b/tv/tv-material/api/restricted_current.txt
index 736293f..c051ced 100644
--- a/tv/tv-material/api/restricted_current.txt
+++ b/tv/tv-material/api/restricted_current.txt
@@ -320,8 +320,6 @@
}
@SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public enum DrawerValue {
- method public static androidx.tv.material3.DrawerValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.tv.material3.DrawerValue[] values();
enum_constant public static final androidx.tv.material3.DrawerValue Closed;
enum_constant public static final androidx.tv.material3.DrawerValue Open;
}
diff --git a/tv/tv-material/build.gradle b/tv/tv-material/build.gradle
index c055bc9..258c875 100644
--- a/tv/tv-material/build.gradle
+++ b/tv/tv-material/build.gradle
@@ -32,19 +32,14 @@
dependencies {
api(libs.kotlinStdlib)
-
- def composeVersion = '1.5.3'
- def composeBetaVersion = "1.5.0-beta01"
-
- api("androidx.compose.animation:animation:$composeVersion")
- api("androidx.compose.material:material-icons-core:$composeVersion")
+ api("androidx.compose.animation:animation:1.5.3")
+ api(project(":compose:foundation:foundation"))
+ api("androidx.compose.material:material-icons-core:1.5.3")
api(project(":tv:tv-foundation"))
- implementation(libs.kotlinStdlibCommon)
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
androidTestImplementation(libs.truth)
-
androidTestImplementation(project(":compose:ui:ui-test"))
androidTestImplementation(project(":compose:ui:ui-test-junit4"))
androidTestImplementation(project(":compose:test-utils"))
@@ -64,9 +59,8 @@
androidx {
name = "TV Material"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2022"
description = "build TV applications using controls that adhere to Material Design Language."
- targetsJavaConsumers = false
samples(project(":tv:tv-samples"))
}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt b/tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt
index b34a3a9..b9decd5 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt
@@ -27,7 +27,6 @@
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.debugInspectorInfo
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
@OptIn(ExperimentalFoundationApi::class)
internal fun Modifier.bringIntoViewIfChildrenAreFocused(): Modifier = composed(
inspectorInfo = debugInspectorInfo { name = "bringIntoViewIfChildrenAreFocused" },
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
index 1f059dc..1cc9e95 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
@@ -102,7 +102,6 @@
* @param carouselIndicator indicator showing the position of the current item among all items.
* @param content defines the items for a given index.
*/
-@Suppress("IllegalExperimentalApiUsage")
@OptIn(ExperimentalComposeUiApi::class)
@ExperimentalTvMaterial3Api
@Composable
@@ -210,7 +209,6 @@
return !accessibilityManager.isEnabled && !(carouselIsFocused || carouselHasFocus)
}
-@Suppress("IllegalExperimentalApiUsage")
@OptIn(ExperimentalAnimationApi::class)
private suspend fun AnimatedVisibilityScope.onAnimationCompletion(action: suspend () -> Unit) {
snapshotFlow { transition.currentState == transition.targetState }.first { it }
@@ -248,7 +246,6 @@
onAutoScrollChange(doAutoScroll)
}
-@Suppress("IllegalExperimentalApiUsage")
@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class)
private fun Modifier.handleKeyEvents(
carouselState: CarouselState,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt
index e10af5a..8ad4199 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt
@@ -56,7 +56,6 @@
* @param listAlignment Alignment of the List with respect to the Immersive List.
* @param list composable defining the list of items that has to be rendered.
*/
-@Suppress("IllegalExperimentalApiUsage")
@OptIn(ExperimentalComposeUiApi::class)
@ExperimentalTvMaterial3Api
@Composable
diff --git a/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/PreviewProgram.java b/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/PreviewProgram.java
index 9a6037a..2d03500 100644
--- a/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/PreviewProgram.java
+++ b/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/PreviewProgram.java
@@ -75,6 +75,7 @@
* null, null);
* </pre>
*/
+@SuppressWarnings("HiddenSuperclass")
public final class PreviewProgram extends BasePreviewProgram {
/**
* The projection for a {@link PreviewProgram} query.
@@ -198,6 +199,7 @@
/**
* This Builder class simplifies the creation of a {@link PreviewProgram} object.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder extends BasePreviewProgram.Builder<Builder> {
/**
diff --git a/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/Program.java b/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/Program.java
index 63b89b1..a9ba201 100644
--- a/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/Program.java
+++ b/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/Program.java
@@ -72,6 +72,7 @@
* null, null);
* </pre>
*/
+@SuppressWarnings("HiddenSuperclass")
public final class Program extends BaseProgram implements Comparable<Program> {
/**
*/
@@ -264,6 +265,7 @@
/**
* This Builder class simplifies the creation of a {@link Program} object.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static class Builder extends BaseProgram.Builder<Builder> {
/**
diff --git a/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/TvContractCompat.java b/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/TvContractCompat.java
index 2725306..4bf7e1f 100644
--- a/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/TvContractCompat.java
+++ b/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/TvContractCompat.java
@@ -2384,6 +2384,7 @@
* <p>By default, the query results will be sorted by
* {@link Programs#COLUMN_START_TIME_UTC_MILLIS} in ascending order.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class Programs implements BaseTvColumns, ProgramColumns {
/**
@@ -2734,6 +2735,7 @@
* <p>By default, the query results will be sorted by {@link #COLUMN_START_TIME_UTC_MILLIS} in
* ascending order.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class RecordedPrograms implements BaseTvColumns, ProgramColumns {
/**
@@ -2861,6 +2863,7 @@
/**
* Column definitions for the preview TV programs table.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class PreviewPrograms implements BaseTvColumns, ProgramColumns,
PreviewProgramColumns {
@@ -2910,6 +2913,7 @@
/**
* Column definitions for the "watch next" TV programs table.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class WatchNextPrograms implements BaseTvColumns, ProgramColumns,
PreviewProgramColumns {
diff --git a/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/WatchNextProgram.java b/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/WatchNextProgram.java
index 475aa87..ad00cd6 100644
--- a/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/WatchNextProgram.java
+++ b/tvprovider/tvprovider/src/main/java/androidx/tvprovider/media/tv/WatchNextProgram.java
@@ -78,6 +78,7 @@
* null, null);
* </pre>
*/
+@SuppressWarnings("HiddenSuperclass")
public final class WatchNextProgram extends BasePreviewProgram {
/**
* The projection for a {@link WatchNextProgram} query.
@@ -220,6 +221,7 @@
/**
* This Builder class simplifies the creation of a {@link WatchNextProgram} object.
*/
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder extends BasePreviewProgram.Builder<Builder> {
/**
diff --git a/versionedparcelable/versionedparcelable/api/restricted_current.ignore b/versionedparcelable/versionedparcelable/api/restricted_current.ignore
deleted file mode 100644
index 331211f..0000000
--- a/versionedparcelable/versionedparcelable/api/restricted_current.ignore
+++ /dev/null
@@ -1,15 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.versionedparcelable.VersionedParcelize#factory():
- Method androidx.versionedparcelable.VersionedParcelize.factory has changed return type from Class to Class<?>
-
-
-RemovedField: androidx.versionedparcelable.VersionedParcel#mParcelizerCache:
- Removed field androidx.versionedparcelable.VersionedParcel.mParcelizerCache
-RemovedField: androidx.versionedparcelable.VersionedParcel#mReadCache:
- Removed field androidx.versionedparcelable.VersionedParcel.mReadCache
-RemovedField: androidx.versionedparcelable.VersionedParcel#mWriteCache:
- Removed field androidx.versionedparcelable.VersionedParcel.mWriteCache
-
-
-RemovedMethod: androidx.versionedparcelable.VersionedParcel#VersionedParcel(androidx.collection.ArrayMap<java.lang.String,java.lang.reflect.Method>, androidx.collection.ArrayMap<java.lang.String,java.lang.reflect.Method>, androidx.collection.ArrayMap<java.lang.String,java.lang.Class>):
- Removed constructor androidx.versionedparcelable.VersionedParcel(androidx.collection.ArrayMap<java.lang.String,java.lang.reflect.Method>,androidx.collection.ArrayMap<java.lang.String,java.lang.reflect.Method>,androidx.collection.ArrayMap<java.lang.String,java.lang.Class>)
diff --git a/wear/OWNERS b/wear/OWNERS
index 6f75224..e457e5c 100644
--- a/wear/OWNERS
+++ b/wear/OWNERS
@@ -1,3 +1,5 @@
# Bug component: 188444
flerda@google.com
jnichol@google.com
+ashleyingram@google.com
+rwmyers@google.com
diff --git a/wear/compose/compose-foundation/api/current.txt b/wear/compose/compose-foundation/api/current.txt
index 7746e7e..9cd59ac 100644
--- a/wear/compose/compose-foundation/api/current.txt
+++ b/wear/compose/compose-foundation/api/current.txt
@@ -312,15 +312,11 @@
}
public enum SwipeToDismissKeys {
- method public static androidx.wear.compose.foundation.SwipeToDismissKeys valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.compose.foundation.SwipeToDismissKeys[] values();
enum_constant public static final androidx.wear.compose.foundation.SwipeToDismissKeys Background;
enum_constant public static final androidx.wear.compose.foundation.SwipeToDismissKeys Content;
}
public enum SwipeToDismissValue {
- method public static androidx.wear.compose.foundation.SwipeToDismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.compose.foundation.SwipeToDismissValue[] values();
enum_constant public static final androidx.wear.compose.foundation.SwipeToDismissValue Default;
enum_constant public static final androidx.wear.compose.foundation.SwipeToDismissValue Dismissed;
}
diff --git a/wear/compose/compose-foundation/api/restricted_current.txt b/wear/compose/compose-foundation/api/restricted_current.txt
index 7746e7e..9cd59ac 100644
--- a/wear/compose/compose-foundation/api/restricted_current.txt
+++ b/wear/compose/compose-foundation/api/restricted_current.txt
@@ -312,15 +312,11 @@
}
public enum SwipeToDismissKeys {
- method public static androidx.wear.compose.foundation.SwipeToDismissKeys valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.compose.foundation.SwipeToDismissKeys[] values();
enum_constant public static final androidx.wear.compose.foundation.SwipeToDismissKeys Background;
enum_constant public static final androidx.wear.compose.foundation.SwipeToDismissKeys Content;
}
public enum SwipeToDismissValue {
- method public static androidx.wear.compose.foundation.SwipeToDismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.compose.foundation.SwipeToDismissValue[] values();
enum_constant public static final androidx.wear.compose.foundation.SwipeToDismissValue Default;
enum_constant public static final androidx.wear.compose.foundation.SwipeToDismissValue Dismissed;
}
diff --git a/wear/compose/compose-foundation/build.gradle b/wear/compose/compose-foundation/build.gradle
index d54bbb7..9d1b092 100644
--- a/wear/compose/compose-foundation/build.gradle
+++ b/wear/compose/compose-foundation/build.gradle
@@ -32,14 +32,14 @@
}
dependencies {
- api(project(":compose:foundation:foundation"))
- api(project(":compose:ui:ui"))
- api(project(":compose:ui:ui-text"))
- api(project(":compose:runtime:runtime"))
+ api("androidx.compose.foundation:foundation:1.6.0")
+ api("androidx.compose.ui:ui:1.6.0")
+ api("androidx.compose.ui:ui-text:1.6.0")
+ api("androidx.compose.runtime:runtime:1.6.0")
implementation(libs.kotlinStdlib)
- implementation(project(":compose:foundation:foundation-layout"))
- implementation(project(":compose:ui:ui-util"))
+ implementation("androidx.compose.foundation:foundation-layout:1.6.0")
+ implementation("androidx.compose.ui:ui-util:1.6.0")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
implementation("androidx.core:core:1.12.0")
implementation("androidx.profileinstaller:profileinstaller:1.3.0")
@@ -47,6 +47,8 @@
testImplementation(libs.testRules)
testImplementation(libs.testRunner)
testImplementation(libs.junit)
+ testImplementation(libs.truth)
+ testImplementation(libs.kotlinTest)
androidTestImplementation(project(":compose:ui:ui-test"))
androidTestImplementation(project(":compose:ui:ui-test-junit4"))
@@ -73,12 +75,11 @@
androidx {
name = "Android Wear Compose Foundation"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2021"
description = "WearOS Compose Foundation Library. This library makes it easier for developers" +
"to write Jetpack Compose applications for Wearable devices by providing " +
"functionality to support wearable specific devices sizes, shapes and navigation " +
"gestures. It builds upon the Jetpack Compose libraries."
- targetsJavaConsumers = false
samples(project(":wear:compose:compose-foundation-samples"))
}
diff --git a/wear/compose/compose-foundation/lint-baseline.xml b/wear/compose/compose-foundation/lint-baseline.xml
index 864c40b..8c488fd 100644
--- a/wear/compose/compose-foundation/lint-baseline.xml
+++ b/wear/compose/compose-foundation/lint-baseline.xml
@@ -1,5 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
+
+ <issue
+ id="ReturnFromAwaitPointerEventScope"
+ message="Returning from awaitPointerEventScope may cause some input events to be dropped"
+ errorLine1=" awaitPointerEventScope {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/wear/compose/foundation/BasicSwipeToDismissBox.kt"/>
+ </issue>
<issue
id="PrimitiveInCollection"
diff --git a/wear/compose/compose-foundation/samples/build.gradle b/wear/compose/compose-foundation/samples/build.gradle
index ffe6c86..45d036b 100644
--- a/wear/compose/compose-foundation/samples/build.gradle
+++ b/wear/compose/compose-foundation/samples/build.gradle
@@ -31,19 +31,18 @@
}
dependencies {
-
implementation(libs.kotlinStdlib)
compileOnly(project(":annotation:annotation-sampled"))
- implementation(project(":activity:activity-compose"))
- implementation(project(":compose:foundation:foundation"))
- implementation(project(":compose:foundation:foundation-layout"))
- implementation(project(":compose:material:material-icons-core"))
- implementation(project(":compose:runtime:runtime"))
- implementation(project(":compose:ui:ui"))
- implementation(project(":compose:ui:ui-text"))
- implementation(project(":compose:ui:ui-tooling"))
- implementation(project(":compose:ui:ui-tooling-preview"))
+ implementation("androidx.activity:activity-compose:1.8.2")
+ implementation("androidx.compose.foundation:foundation:1.6.0")
+ implementation("androidx.compose.foundation:foundation-layout:1.6.0")
+ implementation("androidx.compose.material:material-icons-core:1.6.0")
+ implementation("androidx.compose.runtime:runtime:1.6.0")
+ implementation("androidx.compose.ui:ui:1.6.0")
+ implementation("androidx.compose.ui:ui-text:1.6.0")
+ implementation("androidx.compose.ui:ui-tooling:1.6.0")
+ implementation("androidx.compose.ui:ui-tooling-preview:1.6.0")
implementation(project(":wear:compose:compose-foundation"))
implementation(project(":wear:compose:compose-material"))
implementation(project(":wear:compose:compose-ui-tooling"))
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Haptics.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Haptics.kt
new file mode 100644
index 0000000..373784a
--- /dev/null
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Haptics.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.foundation.rotary
+
+/**
+ * Handles haptics for rotary usage
+ */
+internal interface RotaryHapticHandler {
+
+ /**
+ * Handles haptics when scroll is used
+ */
+ fun handleScrollHaptic(event: UnifiedRotaryEvent)
+
+ /**
+ * Handles haptics when scroll with snap is used
+ */
+ fun handleSnapHaptic(event: UnifiedRotaryEvent)
+
+ /**
+ * Handles haptics when edge of the list is reached
+ */
+ fun handleLimitHaptic(event: UnifiedRotaryEvent, isStart: Boolean)
+}
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Rotary.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Rotary.kt
index 6b340ed..a82086d 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Rotary.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Rotary.kt
@@ -14,22 +14,234 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalWearFoundationApi::class)
+
package androidx.wear.compose.foundation.rotary
+import android.view.ViewConfiguration
+import androidx.compose.animation.core.AnimationState
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.Easing
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.animateTo
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.rotary.RotaryInputModifierNode
import androidx.compose.ui.input.rotary.RotaryScrollEvent
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.util.fastSumBy
+import androidx.compose.ui.util.lerp
+import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.inverseLerp
+import kotlin.math.abs
+import kotlin.math.absoluteValue
+import kotlin.math.sign
+import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
/**
+ * An adapter which connects scrollableState to a rotary input for snapping scroll actions.
+ *
+ * This interface defines the essential properties and methods required for a scrollable
+ * to be controlled by rotary input and perform a snap action.
+ *
+ */
+@ExperimentalWearFoundationApi
+// TODO(b/278705775): make it public once haptics and other code is merged.
+/* public */ internal interface RotaryScrollAdapter {
+
+ /**
+ * The scrollable state used for performing scroll actions in response to rotary events.
+ */
+ val scrollableState: ScrollableState
+
+ /**
+ * Calculates the average size of an item within the scrollable. This is used to
+ * estimate scrolling distances for snapping when responding to rotary input.
+ *
+ * @return The average item size in pixels.
+ */
+ fun averageItemSize(): Float
+
+ /**
+ * Returns the index of the item that is closest to the center.
+ */
+ fun currentItemIndex(): Int
+
+ /**
+ * Returns the offset of the currently centered item from its centered position.
+ * This value can be positive or negative.
+ *
+ * @return The offset of the current item in pixels.
+ */
+ fun currentItemOffset(): Float
+
+ // TODO(b/326239879) Investigate and test whether this method can be removed.
+ /**
+ * The total number of items within the scrollable in [scrollableState]
+ */
+ fun totalItemsCount(): Int
+}
+
+/**
+ * Defaults for rotary modifiers
+ */
+@ExperimentalWearFoundationApi
+// TODO(b/278705775): make it public once haptics and other code is merged.
+/* public */ internal object RotaryDefaults {
+
+ // These values represent the timeframe for a fling event. A bigger value is assigned
+ // to low-res input due to the lower frequency of low-res rotary events.
+ internal const val lowResFlingTimeframe: Long = 100L
+ internal const val highResFlingTimeframe: Long = 30L
+}
+
+/**
+ * An implementation of rotary scroll adapter for ScalingLazyColumn
+ */
+@OptIn(ExperimentalWearFoundationApi::class)
+internal class ScalingLazyColumnRotaryScrollAdapter(
+ override val scrollableState: ScalingLazyListState
+) : RotaryScrollAdapter {
+
+ /**
+ * Calculates the average item height by averaging the height of visible items.
+ */
+ override fun averageItemSize(): Float {
+ val visibleItems = scrollableState.layoutInfo.visibleItemsInfo
+ return (visibleItems.fastSumBy { it.unadjustedSize } / visibleItems.size).toFloat()
+ }
+
+ /**
+ * Current (centered) item index
+ */
+ override fun currentItemIndex(): Int = scrollableState.centerItemIndex
+
+ /**
+ * The offset from the item center.
+ */
+ override fun currentItemOffset(): Float = scrollableState.centerItemScrollOffset.toFloat()
+
+ /**
+ * The total count of items in ScalingLazyColumn
+ */
+ override fun totalItemsCount(): Int = scrollableState.layoutInfo.totalItemsCount
+}
+
+/**
+ * Handles scroll with fling.
+ *
+ * @return An scroll with fling implementation of [RotaryHandler] which is suitable
+ * for both low-res and high-res inputs.
+ *
+ * @param scrollableState Scrollable state which will be scrolled while receiving rotary events
+ * @param flingBehavior Logic describing Fling behavior. If null - fling will not happen
+ * @param isLowRes Whether the input is Low-res (a bezel) or high-res(a crown/rsb)
+ * @param viewConfiguration [ViewConfiguration] for accessing default fling values
+ */
+private fun flingHandler(
+ scrollableState: ScrollableState,
+ rotaryHaptics: RotaryHapticHandler,
+ flingBehavior: FlingBehavior? = null,
+ isLowRes: Boolean,
+ viewConfiguration: ViewConfiguration
+): RotaryHandler {
+
+ fun rotaryFlingBehavior() = flingBehavior?.run {
+ RotaryFlingBehavior(
+ scrollableState,
+ flingBehavior,
+ viewConfiguration,
+ flingTimeframe = if (isLowRes) RotaryDefaults.lowResFlingTimeframe
+ else RotaryDefaults.highResFlingTimeframe
+ )
+ }
+
+ fun scrollBehavior() = RotaryScrollBehavior(scrollableState)
+
+ return RotaryScrollHandler(
+ isLowRes,
+ rotaryHaptics,
+ rotaryFlingBehaviorFactory = { rotaryFlingBehavior() },
+ scrollBehaviorFactory = { scrollBehavior() }
+ )
+}
+
+/**
+ * Handles scroll with snap.
+ *
+ * @return A snap implementation of [RotaryHandler] which is either suitable for low-res or
+ * high-res input.
+ *
+ * @param rotaryScrollAdapter Implementation of [RotaryScrollAdapter], which connects
+ * scrollableState to a rotary input for snapping scroll actions.
+ * @param rotaryHaptics Implementation of [RotaryHapticHandler] which handles haptics
+ * for rotary usage
+ * @param snapOffset An offset to be applied when snapping the item. After the snap the
+ * snapped items offset will be [snapOffset].
+ * @param maxThresholdDivider Factor to divide item size when calculating threshold.
+ * @param scrollDistanceDivider A value which is used to slow down or
+ * speed up the scroll before snap happens. The higher the value the slower the scroll.
+ * @param isLowRes Whether the input is Low-res (a bezel) or high-res(a crown/rsb)
+ */
+private fun snapHandler(
+ rotaryScrollAdapter: RotaryScrollAdapter,
+ rotaryHaptics: RotaryHapticHandler,
+ snapOffset: Int,
+ maxThresholdDivider: Float,
+ scrollDistanceDivider: Float,
+ isLowRes: Boolean
+): RotaryHandler {
+ return if (isLowRes) {
+ LowResRotarySnapHandler(
+ rotaryHaptics = rotaryHaptics,
+ snapBehaviourFactory = {
+ RotarySnapHelper(
+ rotaryScrollAdapter,
+ snapOffset,
+ )
+ }
+ )
+ } else {
+ HighResRotarySnapHandler(
+ rotaryHaptics = rotaryHaptics,
+ scrollDistanceDivider = scrollDistanceDivider,
+ thresholdBehaviorFactory = {
+ ThresholdBehavior(
+ maxThresholdDivider,
+ averageItemSize = { rotaryScrollAdapter.averageItemSize() }
+ )
+ },
+ snapBehaviorFactory = {
+ RotarySnapHelper(
+ rotaryScrollAdapter,
+ snapOffset,
+ )
+ },
+ scrollBehaviorFactory = {
+ RotaryScrollBehavior(rotaryScrollAdapter.scrollableState)
+ }
+ )
+ }
+}
+
+/**
* An abstract class for handling scroll events
*/
internal abstract class RotaryHandler {
@@ -66,9 +278,226 @@
)
/**
+ * This class does a smooth animation when the scroll by N pixels is done.
+ * This animation works well on Rsb(high-res) and Bezel(low-res) devices.
+ */
+internal class RotaryScrollBehavior(
+ private val scrollableState: ScrollableState
+) {
+ private var sequentialAnimation = false
+ private var scrollAnimation = AnimationState(0f)
+ private var prevPosition = 0f
+ private var scrollJob: Job = CompletableDeferred<Unit>()
+
+ /**
+ * Produces scroll to [targetValue]
+ */
+ fun scrollToTarget(coroutineScope: CoroutineScope, targetValue: Float) {
+ cancelScrollIfActive()
+
+ scrollJob = coroutineScope.async {
+ scrollTo(targetValue)
+ }
+ }
+
+ fun cancelScrollIfActive() {
+ if (scrollJob.isActive) scrollJob.cancel()
+ }
+
+ private suspend fun scrollTo(targetValue: Float) {
+ scrollableState.scroll(MutatePriority.UserInput) {
+ debugLog { "ScrollAnimation value before start: ${scrollAnimation.value}" }
+
+ scrollAnimation.animateTo(
+ targetValue,
+ animationSpec = spring(),
+ sequentialAnimation = sequentialAnimation
+ ) {
+ val delta = value - prevPosition
+ debugLog { "Animated by $delta, value: $value" }
+ scrollBy(delta)
+ prevPosition = value
+ sequentialAnimation = value != this.targetValue
+ }
+ }
+ }
+}
+
+/**
+ * A helper class for snapping with rotary.
+ */
+internal class RotarySnapHelper(
+ private val rotaryScrollAdapter: RotaryScrollAdapter,
+ private val snapOffset: Int,
+) {
+ private var snapTarget: Int = rotaryScrollAdapter.currentItemIndex()
+ private var sequentialSnap: Boolean = false
+
+ private var anim = AnimationState(0f)
+ private var expectedDistance = 0f
+
+ private val defaultStiffness = 200f
+ private var snapTargetUpdated = true
+
+ /**
+ * Updating snapping target. This method should be called before [snapToTargetItem].
+ *
+ * Snapping is done for current + [moveForElements] items.
+ *
+ * If [sequentialSnap] is true, items are summed up together.
+ * For example, if [updateSnapTarget] is called with
+ * [moveForElements] = 2, 3, 5 -> then the snapping will happen to current + 10 items
+ *
+ * If [sequentialSnap] is false, then [moveForElements] are not summed up together.
+ */
+ fun updateSnapTarget(moveForElements: Int, sequentialSnap: Boolean) {
+ this.sequentialSnap = sequentialSnap
+ if (sequentialSnap) {
+ snapTarget += moveForElements
+ } else {
+ snapTarget = rotaryScrollAdapter.currentItemIndex() + moveForElements
+ }
+ snapTargetUpdated = true
+ snapTarget = snapTarget.coerceIn(0 until rotaryScrollAdapter.totalItemsCount())
+ }
+
+ /**
+ * Performs snapping to the closest item.
+ */
+ suspend fun snapToClosestItem() {
+ // Perform the snapping animation
+ rotaryScrollAdapter.scrollableState.scroll(MutatePriority.UserInput) {
+ debugLog { "snap to the closest item" }
+ var prevPosition = 0f
+
+ // Create and execute the snap animation
+ AnimationState(0f).animateTo(
+ targetValue = -rotaryScrollAdapter.currentItemOffset(),
+ animationSpec = tween(durationMillis = 100, easing = FastOutSlowInEasing)
+ ) {
+ val animDelta = value - prevPosition
+ scrollBy(animDelta)
+ prevPosition = value
+ }
+ // Update the snap target to ensure consistency
+ snapTarget = rotaryScrollAdapter.currentItemIndex()
+ }
+ }
+
+ /**
+ * Returns true if top edge was reached
+ */
+ fun topEdgeReached(): Boolean = snapTarget <= 0
+
+ /**
+ * Returns true if bottom edge was reached
+ */
+ fun bottomEdgeReached(): Boolean =
+ snapTarget >= rotaryScrollAdapter.totalItemsCount() - 1
+
+ /**
+ * Performs snapping to the specified in [updateSnapTarget] element
+ */
+ suspend fun snapToTargetItem() {
+ if (!sequentialSnap) anim = AnimationState(0f)
+
+ rotaryScrollAdapter.scrollableState.scroll(MutatePriority.UserInput) {
+ // If snapTargetUpdated is true -means the target was updated so we
+ // need to do snap animation again
+ while (snapTargetUpdated) {
+ snapTargetUpdated = false
+ var latestCenterItem: Int
+ var continueFirstScroll = true
+ debugLog { "snapTarget $snapTarget" }
+
+ // First part of animation. Performing it until the target element centered.
+ while (continueFirstScroll) {
+ latestCenterItem = rotaryScrollAdapter.currentItemIndex()
+ expectedDistance = expectedDistanceTo(snapTarget, snapOffset)
+ debugLog {
+ "expectedDistance = $expectedDistance, " +
+ "scrollableState.centerItemScrollOffset " +
+ "${rotaryScrollAdapter.currentItemOffset()}"
+ }
+
+ continueFirstScroll = false
+ var prevPosition = anim.value
+ anim.animateTo(
+ prevPosition + expectedDistance,
+ animationSpec = spring(
+ stiffness = defaultStiffness,
+ visibilityThreshold = 0.1f
+ ),
+ sequentialAnimation = (anim.velocity != 0f)
+ ) {
+ // Exit animation if snap target was updated
+ if (snapTargetUpdated) cancelAnimation()
+
+ val animDelta = value - prevPosition
+ debugLog {
+ "First animation, value:$value, velocity:$velocity, " +
+ "animDelta:$animDelta"
+ }
+ scrollBy(animDelta)
+ prevPosition = value
+
+ if (latestCenterItem != rotaryScrollAdapter.currentItemIndex()) {
+ continueFirstScroll = true
+ cancelAnimation()
+ return@animateTo
+ }
+
+ debugLog { "centerItemIndex = ${rotaryScrollAdapter.currentItemIndex()}" }
+ if (rotaryScrollAdapter.currentItemIndex() == snapTarget) {
+ debugLog { "Target is near the centre. Cancelling first animation" }
+ debugLog {
+ "scrollableState.centerItemScrollOffset " +
+ "${rotaryScrollAdapter.currentItemOffset()}"
+ }
+ expectedDistance = -rotaryScrollAdapter.currentItemOffset()
+ continueFirstScroll = false
+ cancelAnimation()
+ return@animateTo
+ }
+ }
+ }
+ // Exit animation if snap target was updated
+ if (snapTargetUpdated) continue
+
+ // Second part of Animation - animating to the centre of target element.
+ var prevPosition = anim.value
+ anim.animateTo(
+ prevPosition + expectedDistance,
+ animationSpec = SpringSpec(
+ stiffness = defaultStiffness,
+ visibilityThreshold = 0.1f
+ ),
+ sequentialAnimation = (anim.velocity != 0f)
+ ) {
+ // Exit animation if snap target was updated
+ if (snapTargetUpdated) cancelAnimation()
+
+ val animDelta = value - prevPosition
+ debugLog { "Final animation. velocity:$velocity, animDelta:$animDelta" }
+ scrollBy(animDelta)
+ prevPosition = value
+ }
+ }
+ }
+ }
+
+ private fun expectedDistanceTo(index: Int, targetScrollOffset: Int): Float {
+ val averageSize = rotaryScrollAdapter.averageItemSize()
+ val indexesDiff = index - rotaryScrollAdapter.currentItemIndex()
+ debugLog { "Average size $averageSize" }
+ return (averageSize * indexesDiff) +
+ targetScrollOffset - rotaryScrollAdapter.currentItemOffset()
+ }
+}
+
+/**
* A modifier which handles rotary events.
- * It accepts ScrollHandler as the input - a class where main logic about how
- * scroll should be handled is lying
+ * It accepts ScrollHandler as the input - a class that handles the main scroll logic.
*/
internal fun Modifier.rotaryHandler(
rotaryScrollHandler: RotaryHandler,
@@ -84,6 +513,515 @@
inspectorInfo
)
+/**
+ * Class responsible for Fling behaviour with rotary.
+ * It tracks rotary events and produces fling when necessary.
+ * @param flingTimeframe represents a time interval (in milliseconds) used to determine
+ * whether a rotary input should trigger a fling. If no new events come during this interval,
+ * then the fling is triggered.
+ */
+internal class RotaryFlingBehavior(
+ private val scrollableState: ScrollableState,
+ private val flingBehavior: FlingBehavior,
+ viewConfiguration: ViewConfiguration,
+ private val flingTimeframe: Long
+) {
+ private var flingJob: Job = CompletableDeferred<Unit>()
+
+ // A time range during which the fling is valid.
+ // For simplicity it's twice as long as [flingTimeframe]
+ private val timeRangeToFling = flingTimeframe * 2
+
+ // A default fling factor for making fling slower
+ private val flingScaleFactor = 0.7f
+
+ private var previousVelocity = 0f
+
+ private val rotaryVelocityTracker = RotaryVelocityTracker()
+
+ private val minFlingSpeed = viewConfiguration.scaledMinimumFlingVelocity.toFloat()
+ private val maxFlingSpeed = viewConfiguration.scaledMaximumFlingVelocity.toFloat()
+ private var latestEventTimestamp: Long = 0
+
+ private var flingVelocity: Float = 0f
+ private var flingTimestamp: Long = 0
+
+ /**
+ * Starts a new fling tracking session
+ * with specified timestamp
+ */
+ fun startFlingTracking(timestamp: Long) {
+ rotaryVelocityTracker.start(timestamp)
+ latestEventTimestamp = timestamp
+ previousVelocity = 0f
+ }
+
+ fun cancelFlingIfActive() {
+ if (flingJob.isActive) flingJob.cancel()
+ }
+
+ /**
+ * Observing new event within a fling tracking session with new timestamp and delta
+ */
+ fun observeEvent(timestamp: Long, delta: Float) {
+ rotaryVelocityTracker.move(timestamp, delta)
+ latestEventTimestamp = timestamp
+ }
+
+ fun performFlingIfRequired(
+ coroutineScope: CoroutineScope,
+ beforeFling: () -> Unit,
+ edgeReached: (velocity: Float) -> Unit
+ ) {
+ cancelFlingIfActive()
+
+ flingJob = coroutineScope.async {
+ trackFling(beforeFling, edgeReached)
+ }
+ }
+
+ /**
+ * Performing fling if necessary and calling [beforeFling] lambda before it is triggered.
+ * [edgeReached] is called when the scroll reaches the end of the list and can't scroll further
+ */
+ private suspend fun trackFling(
+ beforeFling: () -> Unit,
+ edgeReached: (velocity: Float) -> Unit
+ ) {
+ val currentVelocity = rotaryVelocityTracker.velocity
+ debugLog { "currentVelocity: $currentVelocity" }
+
+ if (abs(currentVelocity) >= abs(previousVelocity)) {
+ flingTimestamp = latestEventTimestamp
+ flingVelocity = currentVelocity * flingScaleFactor
+ }
+ previousVelocity = currentVelocity
+
+ // Waiting for a fixed amount of time before checking the fling
+ delay(flingTimeframe)
+
+ // For making a fling 2 criteria should be met:
+ // 1) no more than
+ // `timeRangeToFling` ms should pass between last fling detection
+ // and the time of last motion event
+ // 2) flingVelocity should exceed the minFlingSpeed
+ debugLog {
+ "Check fling: flingVelocity: $flingVelocity " +
+ "minFlingSpeed: $minFlingSpeed, maxFlingSpeed: $maxFlingSpeed"
+ }
+ if (latestEventTimestamp - flingTimestamp < timeRangeToFling &&
+ abs(flingVelocity) > minFlingSpeed
+ ) {
+ // Call beforeFling because a fling will be performed
+ beforeFling()
+ val velocity = flingVelocity.coerceIn(-maxFlingSpeed, maxFlingSpeed)
+ scrollableState.scroll(MutatePriority.UserInput) {
+ with(flingBehavior) {
+ debugLog { "Flinging with velocity $velocity" }
+ val remainedVelocity = performFling(velocity)
+ debugLog { "-- Velocity after fling: $remainedVelocity" }
+ if (remainedVelocity != 0.0f) {
+ edgeReached(remainedVelocity)
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * A scroll handler for scrolling without snapping and with or without fling.
+ * A list is scrolled by the number of pixels received from the rotary device.
+ *
+ * For a high-res input it has a filtering for events which are coming
+ * with an opposite sign (this might happen to devices with rsb,
+ * especially at the end of the scroll )
+ *
+ * This scroll handler supports fling. It can be set with [RotaryFlingBehavior].
+ */
+internal class RotaryScrollHandler(
+ private val isLowRes: Boolean,
+ private val rotaryHaptics: RotaryHapticHandler,
+ private val rotaryFlingBehaviorFactory: () -> RotaryFlingBehavior?,
+ private val scrollBehaviorFactory: () -> RotaryScrollBehavior,
+) : RotaryHandler() {
+ private var rotaryScrollDistance = 0f
+
+ private var rotaryFlingBehavior: RotaryFlingBehavior? = rotaryFlingBehaviorFactory()
+ private var scrollBehavior: RotaryScrollBehavior = scrollBehaviorFactory()
+
+ override suspend fun handleScrollEvent(
+ coroutineScope: CoroutineScope,
+ event: UnifiedRotaryEvent,
+ ) {
+ val time = event.timestamp
+ debugLog { "RotaryScrollHandler: handleScrollEvent" }
+
+ if (isNewScrollEvent(time)) {
+ debugLog { "New scroll event" }
+ resetScrolling()
+ resetFlingTracking(time)
+ } else {
+ // Due to the physics of high-res Rotary side button, some events might come
+ // with an opposite axis value - either at the start or at the end of the motion.
+ // We don't want to use these values for fling calculations.
+ if (isLowRes || !isOppositeValueAfterScroll(event.deltaInPixels)) {
+ rotaryFlingBehavior?.observeEvent(time, event.deltaInPixels)
+ } else {
+ debugLog { "Opposite value after scroll :${event.deltaInPixels}" }
+ }
+ }
+
+ rotaryScrollDistance += event.deltaInPixels
+ debugLog { "Rotary scroll distance: $rotaryScrollDistance" }
+
+ rotaryHaptics.handleScrollHaptic(event)
+
+ previousScrollEventTime = time
+ scrollBehavior.scrollToTarget(coroutineScope, rotaryScrollDistance)
+
+ rotaryFlingBehavior?.performFlingIfRequired(
+ coroutineScope,
+ beforeFling = {
+ debugLog { "Calling beforeFling section" }
+ resetScrolling()
+ },
+ edgeReached = { velocity ->
+ rotaryHaptics.handleLimitHaptic(event, velocity > 0f)
+ }
+ )
+ }
+
+ private fun resetScrolling() {
+ scrollBehavior.cancelScrollIfActive()
+ scrollBehavior = scrollBehaviorFactory()
+ rotaryScrollDistance = 0f
+ }
+
+ private fun resetFlingTracking(timestamp: Long) {
+ rotaryFlingBehavior?.cancelFlingIfActive()
+ rotaryFlingBehavior = rotaryFlingBehaviorFactory()
+ rotaryFlingBehavior?.startFlingTracking(timestamp)
+ }
+
+ private fun isOppositeValueAfterScroll(delta: Float): Boolean =
+ rotaryScrollDistance * delta < 0f &&
+ (abs(delta) < abs(rotaryScrollDistance))
+}
+
+/**
+ * A scroll handler for RSB(high-res) input with snapping and without fling.
+ *
+ * Threshold for snapping is set dynamically in ThresholdBehavior, which depends
+ * on the scroll speed and the average size of the items.
+ *
+ * This scroll handler doesn't support fling.
+ */
+internal class HighResRotarySnapHandler(
+ private val rotaryHaptics: RotaryHapticHandler,
+ private val scrollDistanceDivider: Float,
+ private val thresholdBehaviorFactory: () -> ThresholdBehavior,
+ private val snapBehaviorFactory: () -> RotarySnapHelper,
+ private val scrollBehaviorFactory: () -> RotaryScrollBehavior
+) : RotaryHandler() {
+ private val snapDelay = 100L
+
+ // This parameter limits number of snaps which can happen during single event.
+ private val maxSnapsPerEvent = 2
+
+ private var snapJob: Job = CompletableDeferred<Unit>()
+
+ private var accumulatedSnapDelta = 0f
+ private var rotaryScrollDistance = 0f
+
+ private var snapBehaviour = snapBehaviorFactory()
+ private var scrollBehavior = scrollBehaviorFactory()
+ private var thresholdBehavior = thresholdBehaviorFactory()
+
+ private val scrollProximityEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.5f, 1.0f)
+
+ override suspend fun handleScrollEvent(
+ coroutineScope: CoroutineScope,
+ event: UnifiedRotaryEvent,
+ ) {
+ val time = event.timestamp
+ debugLog { "HighResSnapHandler: handleScrollEvent" }
+
+ if (isNewScrollEvent(time)) {
+ debugLog { "New scroll event" }
+ resetScrolling()
+ resetSnapping()
+ resetThresholdTracking(time)
+ }
+
+ if (!isOppositeValueAfterScroll(event.deltaInPixels)) {
+ thresholdBehavior.updateTracking(time, event.deltaInPixels)
+ } else {
+ debugLog { "Opposite value after scroll :${event.deltaInPixels}" }
+ }
+
+ val snapThreshold = thresholdBehavior.calculateSnapThreshold()
+ debugLog { "snapThreshold: $snapThreshold" }
+
+ if (!snapJob.isActive) {
+ val proximityFactor = calculateProximityFactor(snapThreshold)
+ rotaryScrollDistance += event.deltaInPixels * proximityFactor
+ }
+ debugLog { "Rotary scroll distance: $rotaryScrollDistance" }
+
+ accumulatedSnapDelta += event.deltaInPixels
+ debugLog { "Accumulated snap delta: $accumulatedSnapDelta" }
+
+ previousScrollEventTime = time
+
+ if (abs(accumulatedSnapDelta) > snapThreshold) {
+ resetScrolling()
+
+ // We limit a number of handled snap items per event to [maxSnapsPerEvent],
+ // as otherwise the snap might happen too quickly
+ val snapDistanceInItems = (accumulatedSnapDelta / snapThreshold).toInt()
+ .coerceIn(-maxSnapsPerEvent..maxSnapsPerEvent)
+ accumulatedSnapDelta -= snapThreshold * snapDistanceInItems
+ //
+ val sequentialSnap = snapJob.isActive
+
+ debugLog {
+ "Snap threshold reached: snapDistanceInItems:$snapDistanceInItems, " +
+ "sequentialSnap: $sequentialSnap, " +
+ "Accumulated snap delta: $accumulatedSnapDelta"
+ }
+ if (edgeNotReached(snapDistanceInItems)) {
+ rotaryHaptics.handleSnapHaptic(event)
+ }
+
+ snapBehaviour.updateSnapTarget(snapDistanceInItems, sequentialSnap)
+ if (!snapJob.isActive) {
+ snapJob.cancel()
+ snapJob = coroutineScope.async {
+ debugLog { "Snap started" }
+ try {
+ snapBehaviour.snapToTargetItem()
+ } finally {
+ debugLog { "Snap called finally" }
+ }
+ }
+ }
+ rotaryScrollDistance = 0f
+ } else {
+ if (!snapJob.isActive) {
+ val distanceWithDivider = rotaryScrollDistance / scrollDistanceDivider
+ debugLog { "Scrolling for $distanceWithDivider px" }
+
+ scrollBehavior.scrollToTarget(coroutineScope, distanceWithDivider)
+ delay(snapDelay)
+
+ resetScrolling()
+ accumulatedSnapDelta = 0f
+ snapBehaviour.updateSnapTarget(0, false)
+
+ snapJob.cancel()
+ snapJob = coroutineScope.async {
+ snapBehaviour.snapToClosestItem()
+ }
+ }
+ }
+ }
+
+ /**
+ * Calculates a value based on the rotaryScrollDistance and size of snapThreshold.
+ * The closer rotaryScrollDistance to snapThreshold, the lower the value.
+ */
+ private fun calculateProximityFactor(snapThreshold: Float): Float =
+ 1 - scrollProximityEasing
+ .transform(rotaryScrollDistance.absoluteValue / snapThreshold)
+
+ private fun edgeNotReached(snapDistanceInItems: Int): Boolean =
+ (!snapBehaviour.topEdgeReached() && snapDistanceInItems < 0) ||
+ (!snapBehaviour.bottomEdgeReached() && snapDistanceInItems > 0)
+
+ private fun resetScrolling() {
+ scrollBehavior.cancelScrollIfActive()
+ scrollBehavior = scrollBehaviorFactory()
+ rotaryScrollDistance = 0f
+ }
+
+ private fun resetSnapping() {
+ snapJob.cancel()
+ snapBehaviour = snapBehaviorFactory()
+ accumulatedSnapDelta = 0f
+ }
+
+ private fun resetThresholdTracking(time: Long) {
+ thresholdBehavior = thresholdBehaviorFactory()
+ thresholdBehavior.startThresholdTracking(time)
+ }
+
+ private fun isOppositeValueAfterScroll(delta: Float): Boolean =
+ rotaryScrollDistance * delta < 0f &&
+ (abs(delta) < abs(rotaryScrollDistance))
+}
+
+/**
+ * A scroll handler for Bezel(low-res) input with snapping and without fling
+ *
+ * This scroll handler doesn't support fling.
+ */
+internal class LowResRotarySnapHandler(
+ private val rotaryHaptics: RotaryHapticHandler,
+ private val snapBehaviourFactory: () -> RotarySnapHelper
+) : RotaryHandler() {
+
+ private var snapJob: Job = CompletableDeferred<Unit>()
+
+ private var accumulatedSnapDelta = 0f
+
+ private var snapBehaviour = snapBehaviourFactory()
+
+ override suspend fun handleScrollEvent(
+ coroutineScope: CoroutineScope,
+ event: UnifiedRotaryEvent,
+ ) {
+ val time = event.timestamp
+ debugLog { "LowResSnapHandler: handleScrollEvent" }
+
+ if (isNewScrollEvent(time)) {
+ debugLog { "New scroll event" }
+ resetSnapping()
+ }
+
+ accumulatedSnapDelta += event.deltaInPixels
+
+ debugLog { "Accumulated snap delta: $accumulatedSnapDelta" }
+
+ previousScrollEventTime = time
+
+ if (abs(accumulatedSnapDelta) > 1f) {
+
+ val snapDistanceInItems = sign(accumulatedSnapDelta).toInt()
+ rotaryHaptics.handleSnapHaptic(event)
+ val sequentialSnap = snapJob.isActive
+ debugLog {
+ "Snap threshold reached: snapDistanceInItems:$snapDistanceInItems, " +
+ "sequentialSnap: $sequentialSnap, " +
+ "Accumulated snap delta: $accumulatedSnapDelta"
+ }
+
+ snapBehaviour.updateSnapTarget(snapDistanceInItems, sequentialSnap)
+ if (!snapJob.isActive) {
+ snapJob.cancel()
+ snapJob = coroutineScope.async {
+ debugLog { "Snap started" }
+ try {
+ snapBehaviour.snapToTargetItem()
+ } finally {
+ debugLog { "Snap called finally" }
+ }
+ }
+ }
+ accumulatedSnapDelta = 0f
+ }
+ }
+
+ private fun resetSnapping() {
+ snapJob.cancel()
+ snapBehaviour = snapBehaviourFactory()
+ accumulatedSnapDelta = 0f
+ }
+}
+
+/**
+ * This class is responsible for determining the dynamic 'snapping' threshold.
+ * The threshold dictates how much rotary input is required to trigger a snapping action.
+ *
+ * The threshold is calculated dynamically based on the user's scroll input velocity.
+ * Faster scrolling results in a lower threshold, making snapping easier to achieve.
+ * An exponential smoothing is also applied to the velocity readings to reduce noise
+ * and provide more consistent threshold calculations.
+ */
+internal class ThresholdBehavior(
+ // Factor to divide item size when calculating threshold.
+ // Depending on the speed, it dynamically varies in range 1..maxThresholdDivider
+ private val maxThresholdDivider: Float,
+ // Min velocity for threshold calculation
+ private val minVelocity: Float = 300f,
+ // Max velocity for threshold calculation
+ private val maxVelocity: Float = 3000f,
+ // Smoothing factor for velocity readings
+ private val smoothingConstant: Float = 0.4f,
+ private val averageItemSize: () -> Float
+ ) {
+ private val thresholdDividerEasing: Easing = CubicBezierEasing(0.5f, 0.0f, 0.5f, 1.0f)
+
+ private val rotaryVelocityTracker = RotaryVelocityTracker()
+
+ private var smoothedVelocity = 0f
+
+ /**
+ * Resets tracking state in preparation for a new scroll event.
+ * Initiates the velocity tracker and resets smoothed velocity.
+ */
+ fun startThresholdTracking(time: Long) {
+ rotaryVelocityTracker.start(time)
+ smoothedVelocity = 0f
+ }
+
+ /**
+ * Updates the velocity tracker with the latest rotary input data.
+ */
+ fun updateTracking(timestamp: Long, delta: Float) {
+ rotaryVelocityTracker.move(timestamp, delta)
+ applySmoothing()
+ }
+
+ /**
+ * Calculates the dynamic snapping threshold based on the current smoothed velocity.
+ *
+ * @return The threshold, in pixels, required to trigger a snapping action.
+ */
+ fun calculateSnapThreshold(): Float {
+ // Calculate a divider fraction based on the smoothedVelocity within the defined range.
+ val thresholdDividerFraction =
+ thresholdDividerEasing.transform(
+ inverseLerp(
+ minVelocity,
+ maxVelocity,
+ smoothedVelocity
+ )
+ )
+ // Calculate the final threshold size by dividing the average item size by a dynamically
+ // adjusted threshold divider.
+ return averageItemSize() / lerp(
+ 1f,
+ maxThresholdDivider,
+ thresholdDividerFraction
+ )
+ }
+
+ /**
+ * Applies exponential smoothing to the tracked velocity to reduce noise
+ * and provide more consistent threshold calculations.
+ */
+ private fun applySmoothing() {
+ if (rotaryVelocityTracker.velocity != 0.0f) {
+ // smooth the velocity
+ smoothedVelocity = exponentialSmoothing(
+ currentVelocity = rotaryVelocityTracker.velocity.absoluteValue,
+ prevVelocity = smoothedVelocity,
+ smoothingConstant = smoothingConstant
+ )
+ }
+ debugLog { "rotaryVelocityTracker velocity: ${rotaryVelocityTracker.velocity}" }
+ debugLog { "SmoothedVelocity: $smoothedVelocity" }
+ }
+
+ private fun exponentialSmoothing(
+ currentVelocity: Float,
+ prevVelocity: Float,
+ smoothingConstant: Float
+ ): Float =
+ smoothingConstant * currentVelocity + (1 - smoothingConstant) * prevVelocity
+}
+
private data class RotaryHandlerElement(
private val rotaryScrollHandler: RotaryHandler,
private val reverseDirection: Boolean,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryVelocityTracker.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryVelocityTracker.kt
new file mode 100644
index 0000000..d9bf1f9
--- /dev/null
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/RotaryVelocityTracker.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.foundation.rotary
+
+import androidx.compose.ui.input.pointer.util.VelocityTracker1D
+
+/**
+ * A wrapper around VelocityTracker1D to provide support for rotary input.
+ */
+internal class RotaryVelocityTracker {
+ private var velocityTracker: VelocityTracker1D = VelocityTracker1D(true)
+
+ /**
+ * Retrieve the last computed velocity.
+ */
+ val velocity: Float
+ get() = velocityTracker.calculateVelocity()
+
+ /**
+ * Start tracking motion.
+ */
+ fun start(currentTime: Long) {
+ velocityTracker.resetTracking()
+ velocityTracker.addDataPoint(currentTime, 0f)
+ }
+
+ /**
+ * Continue tracking motion as the input rotates.
+ */
+ fun move(currentTime: Long, delta: Float) {
+ velocityTracker.addDataPoint(currentTime, delta)
+ }
+
+ /**
+ * Stop tracking motion.
+ */
+ fun end() {
+ velocityTracker.resetTracking()
+ }
+}
diff --git a/wear/compose/compose-foundation/src/test/kotlin/androidx/wear/compose/foundation/rotary/RotaryTest.kt b/wear/compose/compose-foundation/src/test/kotlin/androidx/wear/compose/foundation/rotary/RotaryTest.kt
new file mode 100644
index 0000000..94115fd
--- /dev/null
+++ b/wear/compose/compose-foundation/src/test/kotlin/androidx/wear/compose/foundation/rotary/RotaryTest.kt
@@ -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.
+ */
+
+package androidx.wear.compose.foundation.rotary
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ThresholdBehaviorTest {
+
+ @Test
+ fun testMinVelocityThreshold() {
+ val itemHeight = 100f
+ val thresholdBehavior = ThresholdBehavior(
+ 2.0f,
+ averageItemSize = { itemHeight })
+
+ thresholdBehavior.startThresholdTracking(0L)
+ // Simulate very slow scroll
+ thresholdBehavior.updateTracking(100L, 1f)
+ val result = thresholdBehavior.calculateSnapThreshold()
+
+ // Threshold should be equal to the height of an item
+ assertEquals(itemHeight, result, 0.01f)
+ }
+
+ @Test
+ fun testMaxVelocityThreshold() {
+ val itemHeight = 100f
+ val thresholdDivider = 2.0f
+ val thresholdBehavior = ThresholdBehavior(
+ thresholdDivider,
+ averageItemSize = { itemHeight })
+
+ thresholdBehavior.startThresholdTracking(0L)
+ // Simulate very fast scroll
+ thresholdBehavior.updateTracking(1L, 100f)
+ val result = thresholdBehavior.calculateSnapThreshold()
+
+ // Threshold should be equal to the height of an item divided by threshold
+ assertEquals(itemHeight / thresholdDivider, result, 0.01f)
+ }
+}
diff --git a/wear/compose/compose-material-core/build.gradle b/wear/compose/compose-material-core/build.gradle
index d11b75d..921a918 100644
--- a/wear/compose/compose-material-core/build.gradle
+++ b/wear/compose/compose-material-core/build.gradle
@@ -34,15 +34,15 @@
dependencies {
api(project(":compose:foundation:foundation"))
- api(project(":compose:ui:ui"))
- api(project(":compose:ui:ui-text"))
- api(project(":compose:runtime:runtime"))
+ api("androidx.compose.ui:ui:1.6.0")
+ api("androidx.compose.ui:ui-text:1.6.0")
+ api("androidx.compose.runtime:runtime:1.6.0")
implementation(libs.kotlinStdlib)
- implementation(project(":compose:animation:animation"))
- implementation(project(":compose:material:material-icons-core"))
- implementation(project(":compose:material:material-ripple"))
- implementation(project(":compose:ui:ui-util"))
+ implementation("androidx.compose.animation:animation:1.6.0")
+ implementation("androidx.compose.material:material-icons-core:1.6.0")
+ implementation("androidx.compose.material:material-ripple:1.6.0")
+ implementation("androidx.compose.ui:ui-util:1.6.0")
implementation(project(":wear:compose:compose-foundation"))
implementation("androidx.profileinstaller:profileinstaller:1.3.0")
@@ -75,12 +75,11 @@
androidx {
name = "Android Wear Compose Material Core"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2022"
description = "WearOS Compose Material Core Library. This library contains themeless " +
"components that are shared between different WearOS Compose Material libraries. It " +
"builds upon the Jetpack Compose libraries."
- targetsJavaConsumers = false
metalavaK2UastEnabled = true
}
diff --git a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt
index 75d5229..d1c11d8 100644
--- a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt
+++ b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt
@@ -51,12 +51,15 @@
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.assertTouchHeightIsEqualTo
import androidx.compose.ui.test.assertTouchWidthIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.isSelectable
import androidx.compose.ui.test.isToggleable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onChildAt
@@ -466,6 +469,19 @@
}
@Test
+ fun toggle_button_is_selectable() {
+ rule.setContent {
+ ToggleButtonWithDefaults(
+ toggleControl = null,
+ selectionControl = { TestImage() },
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule.onNode(isSelectable()).assertExists()
+ }
+
+ @Test
fun toggle_button_is_correctly_disabled() {
rule.setContent {
ToggleButtonWithDefaults(
@@ -514,6 +530,20 @@
}
@Test
+ fun toggle_button_is_selected_correctly() {
+ rule.setContent {
+ ToggleButtonWithDefaults(
+ checked = true,
+ toggleControl = null,
+ selectionControl = { TestImage() },
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertIsSelected()
+ }
+
+ @Test
fun toggle_button_responds_to_toggle_on() {
rule.setContent {
val (checked, onCheckedChange) = remember { mutableStateOf(false) }
@@ -552,6 +582,27 @@
}
@Test
+ fun toggle_button_responds_to_selection() {
+ rule.setContent {
+ val (checked, onCheckedChange) = remember { mutableStateOf(false) }
+ ToggleButtonWithDefaults(
+ checked = checked,
+ onCheckedChange = onCheckedChange,
+ toggleControl = null,
+ selectionControl = { TestImage() },
+ enabled = true,
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .assertIsNotSelected()
+ .performClick()
+ .assertIsSelected()
+ }
+
+ @Test
fun toggle_button_does_not_toggle_when_disabled() {
rule.setContent {
val (checked, onCheckedChange) = remember { mutableStateOf(false) }
@@ -1037,7 +1088,8 @@
text = "Label"
)
},
- selectionControl: @Composable () -> Unit = { TestImage() },
+ toggleControl: (@Composable () -> Unit)? = { TestImage() },
+ selectionControl: (@Composable () -> Unit)? = null,
icon: @Composable (BoxScope.() -> Unit)? = null,
secondaryLabel: @Composable (RowScope.() -> Unit)? = null,
background: @Composable (enabled: Boolean, checked: Boolean) -> Modifier = { _, _ ->
@@ -1058,7 +1110,8 @@
checked = checked,
onCheckedChange = onCheckedChange,
label = label,
- toggleControl = selectionControl,
+ toggleControl = toggleControl,
+ selectionControl = selectionControl,
modifier = modifier,
icon = icon,
secondaryLabel = secondaryLabel,
@@ -1083,7 +1136,8 @@
)
},
onClick: () -> Unit = {},
- selectionControl: @Composable BoxScope.() -> Unit = { TestImage() },
+ toggleControl: (@Composable BoxScope.() -> Unit)? = { TestImage() },
+ selectionControl: (@Composable BoxScope.() -> Unit)? = null,
secondaryLabel: @Composable (RowScope.() -> Unit)? = null,
backgroundColor: @Composable (enabled: Boolean, checked: Boolean) -> State<Color> = { _, _ ->
remember { mutableStateOf(BACKGROUND_COLOR) }
@@ -1107,7 +1161,8 @@
onCheckedChange = onCheckedChange,
label = label,
onClick = onClick,
- toggleControl = selectionControl,
+ toggleControl = toggleControl,
+ selectionControl = selectionControl,
modifier = modifier,
secondaryLabel = secondaryLabel,
backgroundColor = backgroundColor,
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 d2696f3..6381c51 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
@@ -16,10 +16,12 @@
package androidx.wear.compose.materialcore
+import android.text.format.DateFormat
import androidx.annotation.RestrictTo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
@@ -40,3 +42,10 @@
configuration.isScreenRound
}
}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Composable
+fun is24HourFormat(): Boolean = DateFormat.is24HourFormat(LocalContext.current)
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun currentTimeMillis(): Long = System.currentTimeMillis()
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
index 83d139d..397cd10 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
@@ -41,6 +41,7 @@
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.toggleable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
@@ -141,8 +142,10 @@
* @param onCheckedChange Callback to be invoked when this buttons checked status is
* @param label A slot for providing the ToggleButton's main label. The contents are expected
* to be text which is "start" aligned.
- * @param toggleControl A slot for providing the ToggleButton's toggle control.
- * Three built-in types of toggle control are supported.
+ * @param toggleControl A slot for providing a toggle control - one and only one of toggleControl
+ * and selectionControl must be provided.
+ * @param selectionControl A slot for providing a selection control - one and only one of
+ * toggleControl and selectionControl must be provided.
* @param modifier Modifier to be applied to the ToggleButton. Pass Modifier.height(height)
* or Modifier.defaultMinSize(minHeight = minHeight) to set a fixed height or a minimum height
* for the button respectively.
@@ -173,7 +176,8 @@
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
label: @Composable RowScope.() -> Unit,
- toggleControl: @Composable () -> Unit,
+ toggleControl: (@Composable () -> Unit)?,
+ selectionControl: (@Composable () -> Unit)?,
modifier: Modifier,
icon: @Composable (BoxScope.() -> Unit)?,
secondaryLabel: @Composable (RowScope.() -> Unit)?,
@@ -186,18 +190,35 @@
toggleControlHeight: Dp,
ripple: Indication
) {
+ // One and only one of toggleControl and selectionControl should be provided.
+ require((toggleControl != null) xor (selectionControl != null)) {
+ "Provide exactly one of toggleControl/selectionControl"
+ }
+
// Stadium/Chip shaped toggle button
Row(
modifier = modifier
.clip(shape = shape)
.width(IntrinsicSize.Max)
.then(background(enabled, checked))
- .toggleable(
- enabled = enabled,
- value = checked,
- onValueChange = onCheckedChange,
- indication = ripple,
- interactionSource = interactionSource
+ .then(
+ if (toggleControl != null) {
+ Modifier.toggleable(
+ enabled = enabled,
+ value = checked,
+ onValueChange = onCheckedChange,
+ indication = ripple,
+ interactionSource = interactionSource
+ )
+ } else {
+ Modifier.selectable(
+ enabled = enabled,
+ selected = checked,
+ onClick = { onCheckedChange(true) },
+ indication = ripple,
+ interactionSource = interactionSource
+ )
+ }
)
.padding(contentPadding),
verticalAlignment = Alignment.CenterVertically
@@ -215,7 +236,7 @@
ToggleControl(
width = toggleControlWidth,
height = toggleControlHeight,
- content = toggleControl
+ content = toggleControl ?: selectionControl!!
)
}
}
@@ -245,7 +266,10 @@
* The contents are expected to be text which is "start" aligned.
* @param onClick Click listener called when the user clicks the main body of the
* SplitToggleButton, the area behind the labels.
- * @param toggleControl A slot for providing the SplitToggleButton's toggle control.
+ * @param toggleControl A slot for providing a toggle control - one and only one of toggleControl
+ * and selectionControl must be provided.
+ * @param selectionControl A slot for providing a selection control - one and only one of toggleControl
+ * and selectionControl must be provided.
* @param modifier Modifier to be applied to the SplitToggleButton
* @param secondaryLabel A slot for providing the SplitToggleButton's secondary label.
* The contents are expected to be "start" or "center" aligned. label and secondaryLabel
@@ -276,7 +300,8 @@
onCheckedChange: (Boolean) -> Unit,
label: @Composable RowScope.() -> Unit,
onClick: () -> Unit,
- toggleControl: @Composable BoxScope.() -> Unit,
+ toggleControl: (@Composable BoxScope.() -> Unit)?,
+ selectionControl: (@Composable BoxScope.() -> Unit)?,
modifier: Modifier,
secondaryLabel: @Composable (RowScope.() -> Unit)?,
backgroundColor: @Composable (enabled: Boolean, checked: Boolean) -> State<Color>,
@@ -328,15 +353,29 @@
checked,
).value
- Box(
- modifier = Modifier
- .toggleable(
+ val boxModifier =
+ if (toggleControl != null) {
+ Modifier
+ .toggleable(
+ enabled = enabled,
+ value = checked,
+ onValueChange = onCheckedChange,
+ indication = ripple,
+ interactionSource = checkedInteractionSource
+ )
+ } else {
+ Modifier.selectable(
enabled = enabled,
- value = checked,
- onValueChange = onCheckedChange,
+ selected = checked,
+ onClick = { onCheckedChange(true) },
indication = ripple,
interactionSource = checkedInteractionSource
)
+ }
+
+ Box(
+ modifier =
+ boxModifier
.fillMaxHeight()
.drawWithCache {
onDrawWithContent {
@@ -349,7 +388,7 @@
.wrapContentHeight(align = Alignment.CenterVertically)
.wrapContentWidth(align = Alignment.End)
.then(endPadding),
- content = toggleControl
+ content = toggleControl ?: selectionControl!!
)
}
}
diff --git a/wear/compose/compose-material/api/current.txt b/wear/compose/compose-material/api/current.txt
index 460b7f6..94cdf76 100644
--- a/wear/compose/compose-material/api/current.txt
+++ b/wear/compose/compose-material/api/current.txt
@@ -703,15 +703,11 @@
}
public enum SwipeToDismissKeys {
- method public static androidx.wear.compose.material.SwipeToDismissKeys valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.compose.material.SwipeToDismissKeys[] values();
enum_constant public static final androidx.wear.compose.material.SwipeToDismissKeys Background;
enum_constant public static final androidx.wear.compose.material.SwipeToDismissKeys Content;
}
@Deprecated public enum SwipeToDismissValue {
- method @Deprecated public static androidx.wear.compose.material.SwipeToDismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.wear.compose.material.SwipeToDismissValue[] values();
enum_constant @Deprecated public static final androidx.wear.compose.material.SwipeToDismissValue Default;
enum_constant @Deprecated public static final androidx.wear.compose.material.SwipeToDismissValue Dismissed;
}
@@ -896,7 +892,9 @@
}
public final class ToggleChipKt {
+ method @androidx.compose.runtime.Composable public static void SplitToggleChip(boolean selected, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onSelect, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.SplitToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? selectionInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource? clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> selectionControl);
method @androidx.compose.runtime.Composable public static void SplitToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> toggleControl, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.SplitToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? checkedInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource? clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void ToggleChip(boolean selected, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onSelect, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? appIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.ToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function0<kotlin.Unit> selectionControl);
method @androidx.compose.runtime.Composable public static void ToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? appIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.ToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
}
diff --git a/wear/compose/compose-material/api/restricted_current.txt b/wear/compose/compose-material/api/restricted_current.txt
index 460b7f6..94cdf76 100644
--- a/wear/compose/compose-material/api/restricted_current.txt
+++ b/wear/compose/compose-material/api/restricted_current.txt
@@ -703,15 +703,11 @@
}
public enum SwipeToDismissKeys {
- method public static androidx.wear.compose.material.SwipeToDismissKeys valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.compose.material.SwipeToDismissKeys[] values();
enum_constant public static final androidx.wear.compose.material.SwipeToDismissKeys Background;
enum_constant public static final androidx.wear.compose.material.SwipeToDismissKeys Content;
}
@Deprecated public enum SwipeToDismissValue {
- method @Deprecated public static androidx.wear.compose.material.SwipeToDismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method @Deprecated public static androidx.wear.compose.material.SwipeToDismissValue[] values();
enum_constant @Deprecated public static final androidx.wear.compose.material.SwipeToDismissValue Default;
enum_constant @Deprecated public static final androidx.wear.compose.material.SwipeToDismissValue Dismissed;
}
@@ -896,7 +892,9 @@
}
public final class ToggleChipKt {
+ method @androidx.compose.runtime.Composable public static void SplitToggleChip(boolean selected, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onSelect, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.SplitToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? selectionInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource? clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> selectionControl);
method @androidx.compose.runtime.Composable public static void SplitToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> toggleControl, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.SplitToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? checkedInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource? clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void ToggleChip(boolean selected, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onSelect, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? appIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.ToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional kotlin.jvm.functions.Function0<kotlin.Unit> selectionControl);
method @androidx.compose.runtime.Composable public static void ToggleChip(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, kotlin.jvm.functions.Function0<kotlin.Unit> toggleControl, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? appIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, optional androidx.wear.compose.material.ToggleChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape);
}
diff --git a/wear/compose/compose-material/build.gradle b/wear/compose/compose-material/build.gradle
index 9073f97..924b9f2 100644
--- a/wear/compose/compose-material/build.gradle
+++ b/wear/compose/compose-material/build.gradle
@@ -31,17 +31,17 @@
}
dependencies {
- api(project(":compose:foundation:foundation"))
- api(project(":compose:ui:ui"))
- api(project(":compose:ui:ui-text"))
- api(project(":compose:runtime:runtime"))
+ api("androidx.compose.foundation:foundation:1.6.0")
+ api("androidx.compose.ui:ui:1.6.0")
+ api("androidx.compose.ui:ui-text:1.6.0")
+ api("androidx.compose.runtime:runtime:1.6.0")
api(project(":wear:compose:compose-foundation"))
implementation(libs.kotlinStdlib)
- implementation(project(":compose:animation:animation"))
- implementation(project(":compose:material:material-icons-core"))
+ implementation("androidx.compose.animation:animation:1.6.0")
+ implementation("androidx.compose.material:material-icons-core:1.6.0")
implementation(project(":compose:material:material-ripple"))
- implementation(project(":compose:ui:ui-util"))
+ implementation("androidx.compose.ui:ui-util:1.6.0")
implementation(project(":wear:compose:compose-material-core"))
implementation("androidx.profileinstaller:profileinstaller:1.3.0")
implementation("androidx.lifecycle:lifecycle-common:2.7.0")
@@ -49,7 +49,6 @@
androidTestImplementation(project(":compose:ui:ui-test"))
androidTestImplementation(project(":compose:ui:ui-test-junit4"))
androidTestImplementation(project(":compose:test-utils"))
-
androidTestImplementation(project(":test:screenshot:screenshot"))
androidTestImplementation(libs.testRunner)
androidTestImplementation(libs.truth)
@@ -78,12 +77,11 @@
androidx {
name = "Android Wear Compose Material"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2021"
description = "WearOS Compose Material Library. This library makes it easier for developers " +
"to write Jetpack Compose applications for Wearable devices that implement Wear " +
"Material Design UX guidelines and specifications. It builds upon the Jetpack Compose" +
" libraries."
- targetsJavaConsumers = false
samples(project(":wear:compose:compose-material-samples"))
}
diff --git a/wear/compose/compose-material/samples/build.gradle b/wear/compose/compose-material/samples/build.gradle
index fb672d1..9d65dcb 100644
--- a/wear/compose/compose-material/samples/build.gradle
+++ b/wear/compose/compose-material/samples/build.gradle
@@ -31,21 +31,20 @@
}
dependencies {
-
implementation(libs.kotlinStdlib)
compileOnly(project(":annotation:annotation-sampled"))
- implementation(project(":activity:activity-compose"))
- implementation(project(":compose:animation:animation-graphics"))
- implementation(project(":compose:foundation:foundation"))
- implementation(project(":compose:foundation:foundation-layout"))
- implementation(project(":compose:runtime:runtime"))
- implementation(project(":compose:ui:ui"))
- implementation(project(":compose:ui:ui-util"))
- implementation(project(":compose:ui:ui-text"))
- implementation(project(":compose:ui:ui-tooling"))
- implementation(project(":compose:ui:ui-tooling-preview"))
- implementation(project(":compose:material:material-icons-core"))
+ implementation("androidx.activity:activity-compose:1.8.2")
+ implementation("androidx.compose.animation:animation-graphics:1.6.0")
+ implementation("androidx.compose.foundation:foundation:1.6.0")
+ implementation("androidx.compose.foundation:foundation-layout:1.6.0")
+ implementation("androidx.compose.runtime:runtime:1.6.0")
+ implementation("androidx.compose.ui:ui:1.6.0")
+ implementation("androidx.compose.ui:ui-util:1.6.0")
+ implementation("androidx.compose.ui:ui-text:1.6.0")
+ implementation("androidx.compose.ui:ui-tooling:1.6.0")
+ implementation("androidx.compose.ui:ui-tooling-preview:1.6.0")
+ implementation("androidx.compose.material:material-icons-core:1.6.0")
implementation(project(":wear:compose:compose-material"))
implementation(project(":wear:compose:compose-foundation"))
implementation(project(":wear:compose:compose-ui-tooling"))
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/CardSample.kt b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/CardSample.kt
index 49b3b78..f95ab3c 100644
--- a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/CardSample.kt
+++ b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/CardSample.kt
@@ -17,12 +17,23 @@
package androidx.wear.compose.material.samples
import androidx.annotation.Sampled
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
+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.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.AppCard
import androidx.wear.compose.material.CardDefaults
import androidx.wear.compose.material.Icon
@@ -40,7 +51,8 @@
Icon(
painter = painterResource(id = R.drawable.ic_airplanemode_active_24px),
contentDescription = "airplane",
- modifier = Modifier.size(CardDefaults.AppImageSize)
+ modifier = Modifier
+ .size(CardDefaults.AppImageSize)
.wrapContentSize(align = Alignment.Center),
)
},
@@ -54,6 +66,42 @@
@Sampled
@Composable
+fun AppCardWithImage() {
+ AppCard(
+ onClick = {},
+ appName = { Text("App name") },
+ appImage = {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_airplanemode_active_24px),
+ contentDescription = "airplane",
+ modifier = Modifier
+ .size(CardDefaults.AppImageSize)
+ .wrapContentSize(align = Alignment.Center),
+ )
+ },
+ title = {
+ Text(
+ text = "Title with maximum two lines",
+ maxLines = 2,
+ )
+ },
+ time = { Text("now") },
+ ) {
+ Spacer(Modifier.height(6.dp))
+ Image(
+ modifier = Modifier
+ .padding(end = 28.dp)
+ .aspectRatio(16f / 9f)
+ .clip(RoundedCornerShape(16.dp)),
+ painter = painterResource(R.drawable.card_background),
+ contentScale = ContentScale.Crop,
+ contentDescription = null
+ )
+ }
+}
+
+@Sampled
+@Composable
fun TitleCardStandard() {
TitleCard(
onClick = {},
@@ -67,7 +115,7 @@
@Sampled
@Composable
-fun TitleCardWithImage() {
+fun TitleCardWithImageBackground() {
TitleCard(
onClick = { /* Do something */ },
title = { Text("TitleCard With an ImageBackground") },
@@ -77,6 +125,14 @@
contentColor = MaterialTheme.colors.onSurface,
titleColor = MaterialTheme.colors.onSurface,
) {
- Text("Text coloured to stand out on the image")
+ // Apply 24.dp padding in bottom for TitleCard with an ImageBackground.
+ // Already 12.dp padding exists. Ref - [CardDefaults.ContentPadding]
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(bottom = 12.dp),
+ ) {
+ Text("Text coloured to stand out on the image")
+ }
}
}
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ToggleChipSample.kt b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ToggleChipSample.kt
index f19cacd..0ea2802 100644
--- a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ToggleChipSample.kt
+++ b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ToggleChipSample.kt
@@ -31,7 +31,6 @@
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Checkbox
import androidx.wear.compose.material.Icon
-import androidx.wear.compose.material.RadioButton
import androidx.wear.compose.material.SplitToggleChip
import androidx.wear.compose.material.Switch
import androidx.wear.compose.material.Text
@@ -89,14 +88,8 @@
secondaryLabel = {
Text("With secondary label", maxLines = 2, overflow = TextOverflow.Ellipsis)
},
- checked = selected,
- toggleControl = {
- RadioButton(
- selected = selected,
- enabled = true,
- )
- },
- onCheckedChange = { selected = it },
+ selected = selected,
+ onSelect = { selected = it },
appIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_airplanemode_active_24px),
@@ -132,3 +125,22 @@
enabled = true,
)
}
+
+@Sampled
+@Composable
+fun SplitToggleChipWithRadioButton() {
+ var selected by remember { mutableStateOf(true) }
+ // The primary label should have a maximum 3 lines of text
+ // and the secondary label should have max 2 lines of text.
+ SplitToggleChip(
+ label = {
+ Text("Split with RadioButton", maxLines = 3, overflow = TextOverflow.Ellipsis)
+ },
+ selected = selected,
+ onSelect = { selected = it },
+ onClick = {
+ /* Do something */
+ },
+ enabled = true,
+ )
+}
diff --git a/wear/compose/compose-material/samples/src/main/res/drawable/card_background.png b/wear/compose/compose-material/samples/src/main/res/drawable/card_background.png
new file mode 100644
index 0000000..1e2c603
--- /dev/null
+++ b/wear/compose/compose-material/samples/src/main/res/drawable/card_background.png
Binary files differ
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ToggleChipTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ToggleChipTest.kt
index 41cec4b..88af89f 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ToggleChipTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ToggleChipTest.kt
@@ -36,9 +36,12 @@
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.isSelectable
import androidx.compose.ui.test.isToggleable
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
@@ -72,6 +75,21 @@
}
@Test
+ fun selection_control_supports_testtag() {
+ rule.setContentWithTheme {
+ ToggleChip(
+ selected = true,
+ onSelect = {},
+ label = { Text("Label") },
+ selectionControl = { TestImage() },
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+
+ @Test
fun split_chip_supports_testtag() {
rule.setContentWithTheme {
SplitToggleChip(
@@ -88,6 +106,21 @@
}
@Test
+ fun split_chip_selection_control_supports_testtag() {
+ rule.setContentWithTheme {
+ SplitToggleChip(
+ selected = true,
+ onSelect = {},
+ label = { Text("Label") },
+ selectionControl = { TestImage() },
+ onClick = {},
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+ @Test
fun has_clickaction_when_enabled() {
rule.setContentWithTheme {
ToggleChip(
@@ -185,6 +218,37 @@
}
@Test
+ fun selection_control_is_selectable() {
+ rule.setContentWithTheme {
+ ToggleChip(
+ selected = true,
+ onSelect = {},
+ label = { Text("Label") },
+ modifier = Modifier.testTag(TEST_TAG),
+ selectionControl = { TestImage() },
+ )
+ }
+
+ rule.onNode(isSelectable()).assertExists()
+ }
+
+ @Test
+ fun split_chip_selection_control_is_selectable() {
+ rule.setContentWithTheme {
+ SplitToggleChip(
+ selected = true,
+ onSelect = {},
+ label = { Text("Label") },
+ onClick = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ selectionControl = { TestImage() },
+ )
+ }
+
+ rule.onNode(isSelectable()).assertExists()
+ }
+
+ @Test
fun split_chip_is_clickable() {
rule.setContentWithTheme {
SplitToggleChip(
@@ -297,6 +361,37 @@
}
@Test
+ fun is_selected_correctly() {
+ rule.setContentWithTheme {
+ ToggleChip(
+ selected = true,
+ onSelect = {},
+ label = { Text("Label") },
+ modifier = Modifier.testTag(TEST_TAG),
+ selectionControl = { TestImage() },
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertIsSelected()
+ }
+
+ @Test
+ fun split_chip_is_selected_correctly() {
+ rule.setContentWithTheme {
+ SplitToggleChip(
+ selected = true,
+ onSelect = {},
+ label = { Text("Label") },
+ onClick = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ selectionControl = { TestImage() },
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG).onChildAt(1).assertIsSelected()
+ }
+
+ @Test
fun is_off_when_unchecked() {
rule.setContentWithTheme {
ToggleChip(
@@ -328,6 +423,37 @@
}
@Test
+ fun is_unselected_correctly() {
+ rule.setContentWithTheme {
+ ToggleChip(
+ selected = false,
+ onSelect = {},
+ label = { Text("Label") },
+ modifier = Modifier.testTag(TEST_TAG),
+ selectionControl = { TestImage() },
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertIsNotSelected()
+ }
+
+ @Test
+ fun split_chip_is_unselected_correctly() {
+ rule.setContentWithTheme {
+ SplitToggleChip(
+ selected = false,
+ onSelect = {},
+ label = { Text("Label") },
+ onClick = {},
+ modifier = Modifier.testTag(TEST_TAG),
+ selectionControl = { TestImage() },
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG).onChildAt(1).assertIsNotSelected()
+ }
+
+ @Test
fun responds_to_toggle_on() {
rule.setContentWithTheme {
val (checked, onCheckedChange) = remember { mutableStateOf(false) }
@@ -372,6 +498,50 @@
}
@Test
+ fun responds_to_selection() {
+ rule.setContentWithTheme {
+ val (selected, onSelected) = remember { mutableStateOf(false) }
+ ToggleChip(
+ selected = selected,
+ onSelect = onSelected,
+ label = { Text("Label") },
+ enabled = true,
+ modifier = Modifier.testTag(TEST_TAG),
+ selectionControl = { TestImage() },
+ )
+ }
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .assertIsNotSelected()
+ .performClick()
+ .assertIsSelected()
+ }
+
+ @Test
+ fun split_chip_responds_to_selection() {
+ rule.setContentWithTheme {
+ val (selected, onSelected) = remember { mutableStateOf(false) }
+ SplitToggleChip(
+ selected = selected,
+ onSelect = onSelected,
+ label = { Text("Label") },
+ selectionControl = { TestImage() },
+ enabled = true,
+ onClick = {},
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .onChildAt(1)
+ .assertIsNotSelected()
+ .performClick()
+ .assertIsSelected()
+ }
+
+ @Test
fun responds_to_toggle_off() {
rule.setContentWithTheme {
val (checked, onCheckedChange) = remember { mutableStateOf(true) }
@@ -460,6 +630,50 @@
}
@Test
+ fun does_not_select_when_disabled() {
+ rule.setContentWithTheme {
+ val (selected, onSelected) = remember { mutableStateOf(false) }
+ ToggleChip(
+ selected = selected,
+ onSelect = onSelected,
+ label = { Text("Label") },
+ selectionControl = { TestImage() },
+ enabled = false,
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .assertIsNotSelected()
+ .performClick()
+ .assertIsNotSelected()
+ }
+
+ @Test
+ fun split_chip_does_not_select_when_disabled() {
+ rule.setContentWithTheme {
+ val (selected, onSelected) = remember { mutableStateOf(false) }
+ SplitToggleChip(
+ selected = selected,
+ onSelect = onSelected,
+ label = { Text("Label") },
+ selectionControl = { TestImage() },
+ enabled = false,
+ onClick = {},
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .onChildAt(1)
+ .assertIsNotSelected()
+ .performClick()
+ .assertIsNotSelected()
+ }
+
+ @Test
fun split_chip_clickable_has_role_button() {
rule.setContentWithTheme {
SplitToggleChip(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
index b75c291..fe84508 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
@@ -151,6 +151,9 @@
* Example of an [AppCard] with icon, title, time and two lines of body text:
* @sample androidx.wear.compose.material.samples.AppCardWithIcon
*
+ * Example of an [AppCard] with image content:
+ * @sample androidx.wear.compose.material.samples.AppCardWithImage
+ *
* For more information, see the
* [Cards](https://developer.android.com/training/wearables/components/cards)
* guide.
@@ -264,7 +267,7 @@
* @sample androidx.wear.compose.material.samples.TitleCardStandard
*
* Example of a title card with a background image:
- * @sample androidx.wear.compose.material.samples.TitleCardWithImage
+ * @sample androidx.wear.compose.material.samples.TitleCardWithImageBackground
*
* For more information, see the
* [Cards](https://developer.android.com/training/wearables/components/cards)
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Resources.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Resources.kt
index 5569667..0498508 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Resources.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Resources.kt
@@ -17,7 +17,6 @@
package androidx.wear.compose.material
import android.provider.Settings
-import android.text.format.DateFormat
import android.view.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@@ -44,11 +43,6 @@
)
@Composable
-internal fun is24HourFormat(): Boolean = DateFormat.is24HourFormat(LocalContext.current)
-
-internal fun currentTimeMillis(): Long = System.currentTimeMillis()
-
-@Composable
internal fun isLeftyModeEnabled(): Boolean {
val context = LocalContext.current
return remember(context) {
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TimeText.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TimeText.kt
index a437877..a37d956 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TimeText.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TimeText.kt
@@ -56,6 +56,8 @@
import androidx.wear.compose.material.TimeTextDefaults.CurvedTextSeparator
import androidx.wear.compose.material.TimeTextDefaults.TextSeparator
import androidx.wear.compose.material.TimeTextDefaults.timeFormat
+import androidx.wear.compose.materialcore.currentTimeMillis
+import androidx.wear.compose.materialcore.is24HourFormat
import androidx.wear.compose.materialcore.isRoundDevice
import java.util.Calendar
import java.util.Locale
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt
index bd6193c..3c52ce4 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt
@@ -48,7 +48,10 @@
/**
* A [ToggleChip] is a specialized type of [Chip] that includes a slot for a bi-state toggle control
- * such as a radio button, toggle or checkbox.
+ * such as a toggle or checkbox. This overload provides suitable accessibility semantics
+ * for a toggleable control like [Checkbox] and [Switch]. For selectable controls
+ * like [RadioButton], an alternative overload is available with a selectionControl parameter
+ * instead of the toggleControl parameter.
*
* The Wear Material [ToggleChip] offers four slots and a specific layout for an application icon, a
* label, a secondaryLabel and toggle control. The application icon and secondaryLabel are optional.
@@ -76,12 +79,10 @@
* @param onCheckedChange Callback to be invoked when this buttons checked/selected status changes
* @param label A slot for providing the chip's main label. The contents are expected to be text
* which is "start" aligned.
- * @param toggleControl A slot for providing the chip's toggle controls(s). The contents are
- * expected to be a horizontally and vertically centre aligned icon of size
- * [ToggleChipDefaults.IconSize]. Three built-in types of toggle control are supported and
- * [ImageVector]s can be obtained from [ToggleChipDefaults.switchIcon], [ToggleChipDefaults.radioIcon] and
- * [ToggleChipDefaults.checkboxIcon]. In order to correctly render when the Chip is not enabled the
- * icon must set its alpha value to [LocalContentAlpha].
+ * @param toggleControl A slot for providing the chip's toggle control. Two built-in types
+ * of toggle control are supported - [Checkbox] and [Switch]. For [RadioButton],
+ * use the alternative overload with the selectionControl parameter (instead of toggleControl),
+ * in order to provide the correct semantics for accessibility.
* @param modifier Modifier to be applied to the chip
* @param appIcon An optional slot for providing an icon to indicate the purpose of the chip. The
* contents are expected to be a horizontally and vertically centre aligned icon of size
@@ -130,6 +131,7 @@
contentColor = colors.toggleControlColor(enabled, checked),
content = toggleControl
),
+ selectionControl = null,
modifier = modifier
.defaultMinSize(minHeight = ToggleChipDefaults.Height)
.height(IntrinsicSize.Min),
@@ -160,10 +162,132 @@
)
/**
+ * A [ToggleChip] is a specialized type of [Chip] that includes a slot for a bi-state
+ * selection control such as a radio button. This overload provides suitable accessibility semantics
+ * for a selectable control like [RadioButton]. For toggleable controls like [Checkbox]
+ * and [Switch], an alternative overload is available with a toggleControl parameter instead of the
+ * selectionControl parameter.
+ *
+ * The Wear Material [ToggleChip] offers four slots and a specific layout for an application icon, a
+ * label, a secondaryLabel and selection control. The application icon and secondaryLabel are
+ * optional. The items are laid out in a row with the optional icon at the start,
+ * a column containing the two label slots in the middle and a slot for the selection control
+ * at the end.
+ *
+ * The [ToggleChip] is Stadium shaped and has a max height designed to take no more than
+ * two lines of text of [Typography.button] style.
+ * With localisation and/or large font sizes, the [ToggleChip] height adjusts to
+ * accommodate the contents. The label and secondary label should be consistently aligned.
+ *
+ * The recommended set of [ToggleChipColors] can be obtained from
+ * [ToggleChipDefaults], e.g. [ToggleChipDefaults.toggleChipColors].
+ *
+ * Chips can be enabled or disabled. A disabled chip will not respond to click events.
+ *
+ * Example of a [ToggleChip] with an icon, label and secondary label (defaults to radio button):
+ * @sample androidx.wear.compose.material.samples.ToggleChipWithRadioButton
+ *
+ * For more information, see the
+ * [Toggle Chips](https://developer.android.com/training/wearables/components/toggle-chips)
+ * guide.
+ *
+ * @param selected Boolean flag indicating whether this button is currently selected.
+ * @param onSelect Callback to be invoked when this button is selected.
+ * @param label A slot for providing the chip's main label. The contents are expected to be text
+ * which is "start" aligned.
+ * @param modifier Modifier to be applied to the chip
+ * @param appIcon An optional slot for providing an icon to indicate the purpose of the chip. The
+ * contents are expected to be a horizontally and vertically centre aligned icon of size
+ * [ToggleChipDefaults.IconSize]. In order to correctly render when the Chip is not enabled the
+ * icon must set its alpha value to [LocalContentAlpha].
+ * @param secondaryLabel A slot for providing the chip's secondary label. The contents are expected
+ * to be text which is "start" aligned if there is an icon preset and "start" or "center" aligned if
+ * not. label and secondaryLabel contents should be consistently aligned.
+ * @param colors [ToggleChipColors] that will be used to resolve the background and
+ * content color for this chip in different states, see
+ * [ToggleChipDefaults.toggleChipColors].
+ * @param enabled Controls the enabled state of the chip. When `false`, this chip will not
+ * be clickable
+ * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
+ * emitting [Interaction]s for this chip's "selectable" tap area. You can use this to change the
+ * chip's appearance or preview the chip in different states. Note that if `null` is provided,
+ * interactions will still happen internally.
+ * @param contentPadding The spacing values to apply internally between the container and the
+ * content
+ * @param shape Defines the chip's shape. It is strongly recommended to use the default as this
+ * shape is a key characteristic of the Wear Material Theme
+ * @param selectionControl A slot for providing the chip's selection control. One built-in
+ * type of selection control is supported, see [RadioButton]. For [Checkbox] and [Switch],
+ * use the alternative overload with the toggleControl parameter in order to provide the
+ * correct semantics for accessibility.
+ */
+@Composable
+public fun ToggleChip(
+ selected: Boolean,
+ onSelect: (Boolean) -> Unit,
+ label: @Composable RowScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ appIcon: @Composable (BoxScope.() -> Unit)? = null,
+ secondaryLabel: @Composable (RowScope.() -> Unit)? = null,
+ colors: ToggleChipColors = ToggleChipDefaults.toggleChipColors(),
+ enabled: Boolean = true,
+ interactionSource: MutableInteractionSource? = null,
+ contentPadding: PaddingValues = ToggleChipDefaults.ContentPadding,
+ shape: Shape = MaterialTheme.shapes.large,
+ selectionControl: @Composable () -> Unit = {
+ RadioButton(selected = selected, enabled = enabled)
+ }
+) = androidx.wear.compose.materialcore.ToggleButton(
+ checked = selected,
+ onCheckedChange = onSelect,
+ label = provideScopeContent(
+ contentColor = colors.contentColor(enabled = enabled, selected),
+ textStyle = MaterialTheme.typography.button,
+ content = label
+ ),
+ toggleControl = null,
+ selectionControl = provideContent(
+ contentColor = colors.toggleControlColor(enabled, selected),
+ content = selectionControl
+ ),
+ modifier = modifier
+ .defaultMinSize(minHeight = ToggleChipDefaults.Height)
+ .height(IntrinsicSize.Min),
+ icon = provideNullableScopeContent(
+ contentColor = colors.contentColor(enabled = enabled, checked = selected),
+ content = appIcon
+ ),
+ secondaryLabel = provideNullableScopeContent(
+ contentColor = colors.secondaryContentColor(enabled = enabled, selected),
+ textStyle = MaterialTheme.typography.caption2,
+ content = secondaryLabel
+ ),
+ background = { isEnabled, isChecked ->
+ val painter = colors.background(
+ enabled = isEnabled,
+ checked = isChecked
+ ).value
+
+ Modifier.paint(painter = painter, contentScale = ContentScale.Crop)
+ },
+ enabled = enabled,
+ interactionSource = interactionSource,
+ contentPadding = contentPadding,
+ shape = shape,
+ toggleControlHeight = TOGGLE_CONTROL_HEIGHT,
+ toggleControlWidth = TOGGLE_CONTROL_WIDTH,
+ ripple = rippleOrFallbackImplementation()
+)
+
+/**
* A [SplitToggleChip] is a specialized type of [Chip] that includes a slot for a toggle control,
- * such as a radio button, toggle or checkbox. The [SplitToggleChip] differs from the
+ * such as a toggle or checkbox. The [SplitToggleChip] differs from the
* [ToggleChip] by having two "tappable" areas, one clickable and one toggleable.
*
+ * This overload provides suitable accessibility semantics for a toggleable control like
+ * [Checkbox] and [Switch]. For selectable controls like [RadioButton], an alternative
+ * overload is available with a selectionControl parameter instead of the toggleControl parameter.
+ *
* The Wear Material [SplitToggleChip] offers three slots and a specific layout for a label,
* secondaryLabel and toggle control. The secondaryLabel is optional. The items are laid out
* with a column containing the two label slots and a slot for the toggle control at the
@@ -200,9 +324,10 @@
* which is "start" aligned.
* @param onClick Click listener called when the user clicks the main body of the chip, the area
* behind the labels.
- * @param toggleControl A slot for providing the chip's toggle controls(s). The contents are
- * expected to be a horizontally and vertically centre aligned icon of size
- * [ToggleChipDefaults.IconSize]. Three built-in types of toggle control are supported and
+ * @param toggleControl A slot for providing the chip's toggle controls(s). Two built-in types
+ * of toggle control are supported, see [Checkbox] and [Switch]. For [RadioButton],
+ * use the alternative overload with the selectionControl parameter in order to provide the
+ * correct semantics for accessibility.
* [ImageVector]s can be obtained from [ToggleChipDefaults.switchIcon], [ToggleChipDefaults.radioIcon]
* and [ToggleChipDefaults.checkboxIcon]. In order to correctly render when the Chip is not enabled the
* icon must set its alpha value to [LocalContentAlpha].
@@ -256,6 +381,7 @@
contentColor = colors.toggleControlColor(enabled = enabled, checked = checked),
content = toggleControl
),
+ selectionControl = null,
modifier = modifier
.defaultMinSize(minHeight = ToggleChipDefaults.Height)
.height(IntrinsicSize.Min),
@@ -280,6 +406,129 @@
)
/**
+ * A [SplitToggleChip] is a specialized type of [Chip] that includes a slot for a selection control,
+ * such as a radio button. The [SplitToggleChip] differs from the
+ * [ToggleChip] by having two "tappable" areas, one clickable and one selectable.
+ *
+ * This overload provides suitable accessibility semantics for a selectable control like
+ * [RadioButton]. For toggleable controls like [Checkbox] and [Switch], an alternative
+ * overload is available with a toggleControl parameter instead of the selectionControl parameter.
+ *
+ * The Wear Material [SplitToggleChip] offers three slots and a specific layout for a label,
+ * secondaryLabel and toggle control. The secondaryLabel is optional. The items are laid out
+ * with a column containing the two label slots and a slot for the toggle control at the
+ * end.
+ *
+ * A [SplitToggleChip] has two tappable areas, one tap area for the labels and another for the
+ * toggle control. The [onClick] listener will be associated with the main body of the split toggle
+ * chip with the [onSelect] listener associated with the selection control area only.
+ *
+ * For a split toggle chip the background of the tappable background area behind the toggle control
+ * will have a visual effect applied to provide a "divider" between the two tappable areas.
+ *
+ * The [SplitToggleChip] is Stadium shaped and has a max height designed to take no more than
+ * two lines of text of [Typography.button] style.
+ * With localisation and/or large font sizes, the [SplitToggleChip] height adjusts
+ * to accommodate the contents. The label and secondary label should be consistently aligned.
+ *
+ * The recommended set of [SplitToggleChipColors] can be obtained from
+ * [ToggleChipDefaults], e.g. [ToggleChipDefaults.splitToggleChipColors].
+ *
+ * Chips can be enabled or disabled. A disabled chip will not respond to click events.
+ *
+ * Example of a [SplitToggleChip] with a label and the radio button selection control:
+ * @sample androidx.wear.compose.material.samples.SplitToggleChipWithRadioButton
+ *
+ * For more information, see the
+ * [Toggle Chips](https://developer.android.com/training/wearables/components/toggle-chips)
+ * guide.
+ *
+ * @param selected Boolean flag indicating whether this button is currently selected.
+ * @param onSelect Callback to be invoked when this button is selected.
+ * @param label A slot for providing the chip's main label. The contents are expected to be text
+ * which is "start" aligned.
+ * @param onClick Click listener called when the user clicks the main body of the chip, the area
+ * behind the labels.
+ * @param modifier Modifier to be applied to the chip
+ * @param secondaryLabel A slot for providing the chip's secondary label. The contents are expected
+ * to be "start" or "center" aligned. label and secondaryLabel contents should be consistently
+ * aligned.
+ * @param colors [SplitToggleChipColors] that will be used to resolve the background and
+ * content color for this chip in different states, see
+ * [ToggleChipDefaults.splitToggleChipColors].
+ * @param enabled Controls the enabled state of the chip. When `false`, this chip will not
+ * be clickable
+ * @param selectionInteractionSource an optional hoisted [MutableInteractionSource] for observing and
+ * emitting [Interaction]s for this chip's "selectable" tap area. You can use this to change the
+ * chip's appearance or preview the chip in different states. Note that if `null` is provided,
+ * interactions will still happen internally.
+ * @param clickInteractionSource an optional hoisted [MutableInteractionSource] for observing and
+ * emitting [Interaction]s for this chip's "clickable" tap area. You can use this to change the
+ * chip's appearance or preview the chip in different states. Note that if `null` is provided,
+ * interactions will still happen internally.
+ * @param contentPadding The spacing values to apply internally between the container and the
+ * content
+ * @param shape Defines the chip's shape. It is strongly recommended to use the default as this
+ * shape is a key characteristic of the Wear Material Theme
+ * @param selectionControl A slot for providing the chip's selection control. One built-in
+ * selection control is provided, see [RadioButton]. For [Checkbox] and [Switch], see the
+ * alternative overload with the toggleControl parameter.
+ */
+@Composable
+public fun SplitToggleChip(
+ selected: Boolean,
+ onSelect: (Boolean) -> Unit,
+ label: @Composable RowScope.() -> Unit,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ secondaryLabel: @Composable (RowScope.() -> Unit)? = null,
+ colors: SplitToggleChipColors = ToggleChipDefaults.splitToggleChipColors(),
+ enabled: Boolean = true,
+ selectionInteractionSource: MutableInteractionSource? = null,
+ clickInteractionSource: MutableInteractionSource? = null,
+ contentPadding: PaddingValues = ToggleChipDefaults.ContentPadding,
+ shape: Shape = MaterialTheme.shapes.large,
+ selectionControl: @Composable BoxScope.() -> Unit = {
+ RadioButton(selected = selected, enabled = enabled)
+ },
+) = androidx.wear.compose.materialcore.SplitToggleButton(
+ checked = selected,
+ onCheckedChange = onSelect,
+ label = provideScopeContent(
+ contentColor = colors.contentColor(enabled = enabled),
+ textStyle = MaterialTheme.typography.button,
+ content = label
+ ),
+ onClick = onClick,
+ toggleControl = null,
+ selectionControl = provideScopeContent(
+ contentColor = colors.toggleControlColor(enabled = enabled, checked = selected),
+ content = selectionControl
+ ),
+ modifier = modifier
+ .defaultMinSize(minHeight = ToggleChipDefaults.Height)
+ .height(IntrinsicSize.Min),
+ secondaryLabel = provideNullableScopeContent(
+ contentColor = colors.secondaryContentColor(enabled = enabled),
+ textStyle = MaterialTheme.typography.caption2,
+ content = secondaryLabel
+ ),
+ backgroundColor = { isEnabled, _ -> colors.backgroundColor(enabled = isEnabled) },
+ splitBackgroundColor = { isEnabled, isChecked ->
+ colors.splitBackgroundOverlay(
+ enabled = isEnabled,
+ checked = isChecked
+ )
+ },
+ enabled = enabled,
+ checkedInteractionSource = selectionInteractionSource,
+ clickInteractionSource = clickInteractionSource,
+ contentPadding = contentPadding,
+ shape = shape,
+ ripple = rippleOrFallbackImplementation()
+)
+
+/**
* Represents the background and content colors used in [ToggleChip]s
* in different states.
*/
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleControl.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleControl.kt
index b0b1b93..f229b2f 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleControl.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleControl.kt
@@ -177,7 +177,7 @@
* @param onClick Callback to be invoked when RadioButton is clicked. If null, then this is
* passive and relies entirely on a higher-level component to control the state
* (such as [ToggleChip] or [SplitToggleChip]).
- * @param interactionSource When also providing [onCheckedChange], an optional hoisted
+ * @param interactionSource When also providing [onClick], an optional hoisted
* [MutableInteractionSource] for observing and emitting [Interaction]s for this radio button.
* You can use this to change the radio button's appearance or preview the radio button in
* different states. Note that if `null` is provided, interactions will still happen internally.
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 3479482..5d8af35 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -346,8 +346,6 @@
@androidx.compose.runtime.Immutable public final class RadioButtonColors {
ctor public RadioButtonColors(long selectedContainerColor, long selectedContentColor, long selectedSecondaryContentColor, long selectedIconColor, long unselectedContainerColor, long unselectedContentColor, long unselectedSecondaryContentColor, long unselectedIconColor, long disabledSelectedContainerColor, long disabledSelectedContentColor, long disabledSelectedSecondaryContentColor, long disabledSelectedIconColor, long disabledUnselectedContainerColor, long disabledUnselectedContentColor, long disabledUnselectedSecondaryContentColor, long disabledUnselectedIconColor);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> containerColor(boolean enabled, boolean selected);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> contentColor(boolean enabled, boolean selected);
method public long getDisabledSelectedContainerColor();
method public long getDisabledSelectedContentColor();
method public long getDisabledSelectedIconColor();
@@ -364,8 +362,6 @@
method public long getUnselectedContentColor();
method public long getUnselectedIconColor();
method public long getUnselectedSecondaryContentColor();
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> iconColor(boolean enabled, boolean selected);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> secondaryContentColor(boolean enabled, boolean selected);
property public final long disabledSelectedContainerColor;
property public final long disabledSelectedContentColor;
property public final long disabledSelectedIconColor;
@@ -386,15 +382,17 @@
public final class RadioButtonDefaults {
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.RadioButtonColors radioButtonColors(optional long selectedContainerColor, optional long selectedContentColor, optional long selectedSecondaryContentColor, optional long selectedIconColor, optional long unselectedContainerColor, optional long unselectedContentColor, optional long unselectedSecondaryContentColor, optional long unselectedIconColor);
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SplitRadioButtonColors splitRadioButtonColors(optional long selectedContainerColor, optional long selectedContentColor, optional long selectedSecondaryContentColor, optional long selectedSplitContainerColor, optional long unselectedContainerColor, optional long unselectedContentColor, optional long unselectedSecondaryContentColor, optional long unselectedSplitContainerColor);
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.RadioButtonColors radioButtonColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.RadioButtonColors radioButtonColors(optional long selectedContainerColor, optional long selectedContentColor, optional long selectedSecondaryContentColor, optional long selectedIconColor, optional long unselectedContainerColor, optional long unselectedContentColor, optional long unselectedSecondaryContentColor, optional long unselectedIconColor, optional long disabledSelectedContainerColor, optional long disabledSelectedContentColor, optional long disabledSelectedSecondaryContentColor, optional long disabledSelectedIconColor, optional long disabledUnselectedContainerColor, optional long disabledUnselectedContentColor, optional long disabledUnselectedSecondaryContentColor, optional long disabledUnselectedIconColor);
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SplitRadioButtonColors splitRadioButtonColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SplitRadioButtonColors splitRadioButtonColors(optional long selectedContainerColor, optional long selectedContentColor, optional long selectedSecondaryContentColor, optional long selectedSplitContainerColor, optional long unselectedContainerColor, optional long unselectedContentColor, optional long unselectedSecondaryContentColor, optional long unselectedSplitContainerColor, optional long disabledSelectedContainerColor, optional long disabledSelectedContentColor, optional long disabledSelectedSecondaryContentColor, optional long disabledSelectedSplitContainerColor, optional long disabledUnselectedContainerColor, optional long disabledUnselectedContentColor, optional long disabledUnselectedSecondaryContentColor, optional long disabledUnselectedSplitContainerColor);
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
field public static final androidx.wear.compose.material3.RadioButtonDefaults INSTANCE;
}
public final class RadioButtonKt {
- method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.SelectionControlScope,kotlin.Unit> selectionControl, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.RadioButtonColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label);
- method @androidx.compose.runtime.Composable public static void SplitRadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.SelectionControlScope,kotlin.Unit> selectionControl, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.SplitRadioButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? selectionInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource? clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label);
+ method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelect, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.SelectionControlScope,kotlin.Unit> selectionControl, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.RadioButtonColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label);
+ method @androidx.compose.runtime.Composable public static void SplitRadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelect, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.SelectionControlScope,kotlin.Unit> selectionControl, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.SplitRadioButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? selectionInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource? clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label);
}
@androidx.compose.runtime.Immutable public final class RadioColors {
@@ -489,7 +487,6 @@
method public long getUnselectedContentColor();
method public long getUnselectedSecondaryContentColor();
method public long getUnselectedSplitContainerColor();
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> splitContainerColor(boolean enabled, boolean selected);
property public final long disabledSelectedContainerColor;
property public final long disabledSelectedContentColor;
property public final long disabledSelectedSecondaryContentColor;
@@ -649,6 +646,34 @@
property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
}
+ public interface TimeSource {
+ method @androidx.compose.runtime.Composable public String getCurrentTime();
+ property @androidx.compose.runtime.Composable public abstract String currentTime;
+ }
+
+ public final class TimeTextDefaults {
+ method public void CurvedTextSeparator(androidx.wear.compose.foundation.CurvedScope, optional androidx.wear.compose.foundation.CurvedTextStyle? curvedTextStyle, optional androidx.wear.compose.foundation.ArcPaddingValues contentArcPadding);
+ method @androidx.compose.runtime.Composable public void TextSeparator(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
+ method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues contentPadding();
+ method @androidx.compose.runtime.Composable public String timeFormat();
+ method public androidx.wear.compose.material3.TimeSource timeSource(String timeFormat);
+ method @androidx.compose.runtime.Composable public androidx.compose.ui.text.TextStyle timeTextStyle(optional long background, optional long color, optional long fontSize);
+ field public static final androidx.wear.compose.material3.TimeTextDefaults INSTANCE;
+ field public static final String TimeFormat12Hours = "h:mm";
+ field public static final String TimeFormat24Hours = "HH:mm";
+ }
+
+ public final class TimeTextKt {
+ method @androidx.compose.runtime.Composable public static void TimeText(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.CurvedModifier curvedModifier, optional androidx.wear.compose.material3.TimeSource timeSource, optional androidx.compose.ui.text.TextStyle timeTextStyle, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.TimeTextScope,kotlin.Unit> content);
+ }
+
+ public abstract sealed class TimeTextScope {
+ method public abstract void composable(kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method public abstract void separator(optional androidx.compose.ui.text.TextStyle? style);
+ method public abstract void text(String text, optional androidx.compose.ui.text.TextStyle? style);
+ 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);
ctor public ToggleButtonColors(long checkedContainerColor, long checkedContentColor, long checkedSecondaryContentColor, long checkedIconColor, long uncheckedContainerColor, long uncheckedContentColor, long uncheckedSecondaryContentColor, long uncheckedIconColor, long disabledCheckedContainerColor, long disabledCheckedContentColor, long disabledCheckedSecondaryContentColor, long disabledCheckedIconColor, long disabledUncheckedContainerColor, long disabledUncheckedContentColor, long disabledUncheckedSecondaryContentColor, long disabledUncheckedIconColor);
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 3479482..5d8af35 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -346,8 +346,6 @@
@androidx.compose.runtime.Immutable public final class RadioButtonColors {
ctor public RadioButtonColors(long selectedContainerColor, long selectedContentColor, long selectedSecondaryContentColor, long selectedIconColor, long unselectedContainerColor, long unselectedContentColor, long unselectedSecondaryContentColor, long unselectedIconColor, long disabledSelectedContainerColor, long disabledSelectedContentColor, long disabledSelectedSecondaryContentColor, long disabledSelectedIconColor, long disabledUnselectedContainerColor, long disabledUnselectedContentColor, long disabledUnselectedSecondaryContentColor, long disabledUnselectedIconColor);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> containerColor(boolean enabled, boolean selected);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> contentColor(boolean enabled, boolean selected);
method public long getDisabledSelectedContainerColor();
method public long getDisabledSelectedContentColor();
method public long getDisabledSelectedIconColor();
@@ -364,8 +362,6 @@
method public long getUnselectedContentColor();
method public long getUnselectedIconColor();
method public long getUnselectedSecondaryContentColor();
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> iconColor(boolean enabled, boolean selected);
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> secondaryContentColor(boolean enabled, boolean selected);
property public final long disabledSelectedContainerColor;
property public final long disabledSelectedContentColor;
property public final long disabledSelectedIconColor;
@@ -386,15 +382,17 @@
public final class RadioButtonDefaults {
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.RadioButtonColors radioButtonColors(optional long selectedContainerColor, optional long selectedContentColor, optional long selectedSecondaryContentColor, optional long selectedIconColor, optional long unselectedContainerColor, optional long unselectedContentColor, optional long unselectedSecondaryContentColor, optional long unselectedIconColor);
- method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SplitRadioButtonColors splitRadioButtonColors(optional long selectedContainerColor, optional long selectedContentColor, optional long selectedSecondaryContentColor, optional long selectedSplitContainerColor, optional long unselectedContainerColor, optional long unselectedContentColor, optional long unselectedSecondaryContentColor, optional long unselectedSplitContainerColor);
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.RadioButtonColors radioButtonColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.RadioButtonColors radioButtonColors(optional long selectedContainerColor, optional long selectedContentColor, optional long selectedSecondaryContentColor, optional long selectedIconColor, optional long unselectedContainerColor, optional long unselectedContentColor, optional long unselectedSecondaryContentColor, optional long unselectedIconColor, optional long disabledSelectedContainerColor, optional long disabledSelectedContentColor, optional long disabledSelectedSecondaryContentColor, optional long disabledSelectedIconColor, optional long disabledUnselectedContainerColor, optional long disabledUnselectedContentColor, optional long disabledUnselectedSecondaryContentColor, optional long disabledUnselectedIconColor);
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SplitRadioButtonColors splitRadioButtonColors();
+ method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SplitRadioButtonColors splitRadioButtonColors(optional long selectedContainerColor, optional long selectedContentColor, optional long selectedSecondaryContentColor, optional long selectedSplitContainerColor, optional long unselectedContainerColor, optional long unselectedContentColor, optional long unselectedSecondaryContentColor, optional long unselectedSplitContainerColor, optional long disabledSelectedContainerColor, optional long disabledSelectedContentColor, optional long disabledSelectedSecondaryContentColor, optional long disabledSelectedSplitContainerColor, optional long disabledUnselectedContainerColor, optional long disabledUnselectedContentColor, optional long disabledUnselectedSecondaryContentColor, optional long disabledUnselectedSplitContainerColor);
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
field public static final androidx.wear.compose.material3.RadioButtonDefaults INSTANCE;
}
public final class RadioButtonKt {
- method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.SelectionControlScope,kotlin.Unit> selectionControl, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.RadioButtonColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label);
- method @androidx.compose.runtime.Composable public static void SplitRadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.SelectionControlScope,kotlin.Unit> selectionControl, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.SplitRadioButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? selectionInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource? clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label);
+ method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelect, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.SelectionControlScope,kotlin.Unit> selectionControl, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.RadioButtonColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label);
+ method @androidx.compose.runtime.Composable public static void SplitRadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelect, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.SelectionControlScope,kotlin.Unit> selectionControl, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material3.SplitRadioButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? selectionInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource? clickInteractionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? secondaryLabel, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label);
}
@androidx.compose.runtime.Immutable public final class RadioColors {
@@ -489,7 +487,6 @@
method public long getUnselectedContentColor();
method public long getUnselectedSecondaryContentColor();
method public long getUnselectedSplitContainerColor();
- method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> splitContainerColor(boolean enabled, boolean selected);
property public final long disabledSelectedContainerColor;
property public final long disabledSelectedContentColor;
property public final long disabledSelectedSecondaryContentColor;
@@ -649,6 +646,34 @@
property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
}
+ public interface TimeSource {
+ method @androidx.compose.runtime.Composable public String getCurrentTime();
+ property @androidx.compose.runtime.Composable public abstract String currentTime;
+ }
+
+ public final class TimeTextDefaults {
+ method public void CurvedTextSeparator(androidx.wear.compose.foundation.CurvedScope, optional androidx.wear.compose.foundation.CurvedTextStyle? curvedTextStyle, optional androidx.wear.compose.foundation.ArcPaddingValues contentArcPadding);
+ method @androidx.compose.runtime.Composable public void TextSeparator(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
+ method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues contentPadding();
+ method @androidx.compose.runtime.Composable public String timeFormat();
+ method public androidx.wear.compose.material3.TimeSource timeSource(String timeFormat);
+ method @androidx.compose.runtime.Composable public androidx.compose.ui.text.TextStyle timeTextStyle(optional long background, optional long color, optional long fontSize);
+ field public static final androidx.wear.compose.material3.TimeTextDefaults INSTANCE;
+ field public static final String TimeFormat12Hours = "h:mm";
+ field public static final String TimeFormat24Hours = "HH:mm";
+ }
+
+ public final class TimeTextKt {
+ method @androidx.compose.runtime.Composable public static void TimeText(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.CurvedModifier curvedModifier, optional androidx.wear.compose.material3.TimeSource timeSource, optional androidx.compose.ui.text.TextStyle timeTextStyle, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.TimeTextScope,kotlin.Unit> content);
+ }
+
+ public abstract sealed class TimeTextScope {
+ method public abstract void composable(kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method public abstract void separator(optional androidx.compose.ui.text.TextStyle? style);
+ method public abstract void text(String text, optional androidx.compose.ui.text.TextStyle? style);
+ 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);
ctor public ToggleButtonColors(long checkedContainerColor, long checkedContentColor, long checkedSecondaryContentColor, long checkedIconColor, long uncheckedContainerColor, long uncheckedContentColor, long uncheckedSecondaryContentColor, long uncheckedIconColor, long disabledCheckedContainerColor, long disabledCheckedContentColor, long disabledCheckedSecondaryContentColor, long disabledCheckedIconColor, long disabledUncheckedContainerColor, long disabledUncheckedContentColor, long disabledUncheckedSecondaryContentColor, long disabledUncheckedIconColor);
diff --git a/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/RadioButtonBenchmark.kt b/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/RadioButtonBenchmark.kt
new file mode 100644
index 0000000..6320e99
--- /dev/null
+++ b/wear/compose/compose-material3/benchmark/src/androidTest/java/androidx/wear/compose/material3/benchmark/RadioButtonBenchmark.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.wear.compose.material3.benchmark
+
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.RadioButton
+import androidx.wear.compose.material3.SplitRadioButton
+import androidx.wear.compose.material3.Text
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class RadioButtonBenchmark {
+
+ @get:Rule
+ val benchmarkRule = ComposeBenchmarkRule()
+
+ private val radioButtonTestCaseFactory = { RadioButtonTestCase(RadioButtonType.RadioButton) }
+ private val splitRadioButtonTestCaseFactory = {
+ RadioButtonTestCase(RadioButtonType.SplitRadioButton)
+ }
+
+ @Test
+ fun radio_button_first_pixel() {
+ benchmarkRule.benchmarkToFirstPixel(radioButtonTestCaseFactory)
+ }
+
+ @Test
+ fun split_radio_button_first_pixel() {
+ benchmarkRule.benchmarkToFirstPixel(splitRadioButtonTestCaseFactory)
+ }
+}
+
+internal class RadioButtonTestCase(
+ private val type: RadioButtonType
+) : LayeredComposeTestCase() {
+ @Composable
+ override fun MeasuredContent() {
+ if (type == RadioButtonType.RadioButton) {
+ RadioButton(selected = true, onSelect = { /* do something*/ }) {
+ Text(text = "RadioButton")
+ }
+ } else {
+ SplitRadioButton(
+ selected = true,
+ onSelect = { /* do something */ },
+ onClick = { /* do something */ }) {
+ Text(text = "SplitRadioButton")
+ }
+ }
+ }
+
+ @Composable
+ override fun ContentWrappers(content: @Composable () -> Unit) {
+ MaterialTheme {
+ content()
+ }
+ }
+}
+
+enum class RadioButtonType {
+ RadioButton, SplitRadioButton
+}
diff --git a/wear/compose/compose-material3/build.gradle b/wear/compose/compose-material3/build.gradle
index ed1edab..6ba5202 100644
--- a/wear/compose/compose-material3/build.gradle
+++ b/wear/compose/compose-material3/build.gradle
@@ -32,24 +32,23 @@
}
dependencies {
- api(project(":compose:foundation:foundation"))
- api(project(":compose:ui:ui"))
- api(project(":compose:ui:ui-text"))
- api(project(":compose:runtime:runtime"))
+ api("androidx.compose.foundation:foundation:1.6.0")
+ api("androidx.compose.ui:ui:1.6.0")
+ api("androidx.compose.ui:ui-text:1.6.0")
+ api("androidx.compose.runtime:runtime:1.6.0")
api(project(":wear:compose:compose-foundation"))
implementation(libs.kotlinStdlib)
- implementation(project(":compose:animation:animation"))
- implementation(project(":compose:material:material-icons-core"))
+ implementation("androidx.compose.animation:animation:1.6.0")
+ implementation("androidx.compose.material:material-icons-core:1.6.0")
implementation(project(":compose:material:material-ripple"))
- implementation(project(":compose:ui:ui-util"))
+ implementation("androidx.compose.ui:ui-util:1.6.0")
implementation(project(":wear:compose:compose-material-core"))
implementation("androidx.profileinstaller:profileinstaller:1.3.0")
androidTestImplementation(project(":compose:ui:ui-test"))
androidTestImplementation(project(":compose:ui:ui-test-junit4"))
androidTestImplementation(project(":compose:test-utils"))
-
androidTestImplementation(project(":test:screenshot:screenshot"))
androidTestImplementation(libs.testRunner)
androidTestImplementation(libs.truth)
@@ -79,14 +78,13 @@
androidx {
name = "Android Wear Compose Material 3"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
mavenVersion = LibraryVersions.WEAR_COMPOSE_MATERIAL3
inceptionYear = "2022"
description = "WearOS Compose Material 3 Library. This library makes it easier for " +
"developers to write Jetpack Compose applications for Wearable devices that " +
"implement Wear Material 3 Design UX guidelines and specifications. It builds upon " +
"the Jetpack Compose libraries."
- targetsJavaConsumers = false
samples(project(":wear:compose:compose-material-samples"))
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/RadioButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/RadioButtonDemo.kt
index 20ecaa5..fd546275 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/RadioButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/RadioButtonDemo.kt
@@ -152,7 +152,7 @@
}
},
selected = selected,
- onSelected = onSelected,
+ onSelect = onSelected,
enabled = enabled,
)
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SelectionControlsDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SelectionControlsDemo.kt
index 7f1b01c..0f8bf1b 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SelectionControlsDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SelectionControlsDemo.kt
@@ -77,7 +77,7 @@
Text("Primary label", maxLines = 1, overflow = TextOverflow.Ellipsis)
},
selected = selected,
- onSelected = onSelected,
+ onSelect = onSelected,
enabled = enabled,
)
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitRadioButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitRadioButtonDemo.kt
index e778194..b34aba8 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitRadioButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitRadioButtonDemo.kt
@@ -126,7 +126,7 @@
}
},
selected = selected,
- onSelected = onSelected,
+ onSelect = onSelected,
onClick = {
val toastText = if (selected) "Checked" else "Not Checked"
Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show()
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt
new file mode 100644
index 0000000..0bf6885
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.demos
+
+import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.material3.samples.TimeTextClockOnly
+import androidx.wear.compose.material3.samples.TimeTextWithIcon
+import androidx.wear.compose.material3.samples.TimeTextWithStatus
+
+val TimeTextDemos =
+ listOf(
+ ComposableDemo("Clock only") { TimeTextClockOnly() },
+ ComposableDemo("Clock with Status") { TimeTextWithStatus() },
+ ComposableDemo("Clock with Icon") { TimeTextWithIcon() },
+ )
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 cf38532..854da28 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
@@ -62,6 +62,10 @@
ListHeaderDemo()
}
},
+ DemoCategory(
+ "Time Text",
+ TimeTextDemos
+ ),
ComposableDemo("Card") {
CardDemo()
},
diff --git a/wear/compose/compose-material3/samples/build.gradle b/wear/compose/compose-material3/samples/build.gradle
index 94ca565..f9cf5a8 100644
--- a/wear/compose/compose-material3/samples/build.gradle
+++ b/wear/compose/compose-material3/samples/build.gradle
@@ -33,11 +33,11 @@
dependencies {
compileOnly(project(":annotation:annotation-sampled"))
- implementation(project(":activity:activity-compose"))
- implementation(project(":compose:material:material-icons-core"))
- implementation(project(":compose:runtime:runtime"))
- implementation(project(":compose:ui:ui-tooling"))
- implementation(project(":compose:ui:ui-tooling-preview"))
+ implementation("androidx.activity:activity-compose:1.8.2")
+ implementation("androidx.compose.material:material-icons-core:1.6.0")
+ implementation("androidx.compose.runtime:runtime:1.6.0")
+ implementation("androidx.compose.ui:ui-tooling:1.6.0")
+ implementation("androidx.compose.ui:ui-tooling-preview:1.6.0")
implementation(project(":wear:compose:compose-foundation"))
implementation(project(":wear:compose:compose-material3"))
implementation(project(":wear:compose:compose-ui-tooling"))
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/RadioButtonSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/RadioButtonSample.kt
index 5bad56d..20bb596 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/RadioButtonSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/RadioButtonSample.kt
@@ -50,7 +50,7 @@
Text("With secondary label", maxLines = 2, overflow = TextOverflow.Ellipsis)
},
selected = selectedButton == 0,
- onSelected = { selectedButton = 0 },
+ onSelect = { selectedButton = 0 },
icon = {
Icon(
Icons.Filled.Favorite,
@@ -68,7 +68,7 @@
Text("With secondary label", maxLines = 3, overflow = TextOverflow.Ellipsis)
},
selected = selectedButton == 1,
- onSelected = { selectedButton = 1 },
+ onSelect = { selectedButton = 1 },
icon = {
Icon(
Icons.Filled.Favorite,
@@ -91,7 +91,7 @@
Text("First Radio Button", maxLines = 3, overflow = TextOverflow.Ellipsis)
},
selected = selectedButton == 0,
- onSelected = { selectedButton = 0 },
+ onSelect = { selectedButton = 0 },
onClick = {
/* Do something */
},
@@ -103,7 +103,7 @@
Text("Second Radio Button", maxLines = 3, overflow = TextOverflow.Ellipsis)
},
selected = selectedButton == 1,
- onSelected = { selectedButton = 1 },
+ onSelect = { selectedButton = 1 },
onClick = {
/* Do something */
},
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimeTextSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimeTextSample.kt
new file mode 100644
index 0000000..ba85e50
--- /dev/null
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimeTextSample.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.TimeText
+import androidx.wear.compose.material3.TimeTextDefaults
+
+@Sampled
+@Composable
+fun TimeTextClockOnly() {
+ TimeText {
+ time()
+ }
+}
+
+@Sampled
+@Composable
+fun TimeTextWithStatus() {
+ val leadingTextStyle = TimeTextDefaults.timeTextStyle(
+ color = Color.Green
+ )
+ TimeText {
+ text("ETA 12:48", leadingTextStyle)
+ separator()
+ time()
+ }
+}
+
+@Sampled
+@Composable
+fun TimeTextWithIcon() {
+ TimeText {
+ time()
+ separator()
+ composable {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = "Favorite",
+ modifier = Modifier.size(13.dp)
+ )
+ }
+ }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
index 18a3a2c..f1ce6bc 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
@@ -43,6 +43,7 @@
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertHeightIsEqualTo
@@ -52,6 +53,10 @@
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpRect
import androidx.compose.ui.unit.LayoutDirection
@@ -128,6 +133,15 @@
return onNodeWithTag("containerForSizeAssertion", useUnmergedTree)
}
+fun ComposeContentTestRule.textStyleOf(text: String): TextStyle {
+ val textLayoutResults = mutableListOf<TextLayoutResult>()
+ onNodeWithText(text, useUnmergedTree = true)
+ .performSemanticsAction(SemanticsActions.GetTextLayoutResult) {
+ it(textLayoutResults)
+ }
+ return textLayoutResults[0].layoutInput.style
+}
+
fun ComposeContentTestRule.setContentWithTheme(
modifier: Modifier = Modifier,
composable: @Composable BoxScope.() -> Unit
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonScreenshotTest.kt
index c32ef33..228eb9c 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonScreenshotTest.kt
@@ -194,7 +194,7 @@
selected = selected,
enabled = enabled,
selectionControl = selectionControl,
- onSelected = {},
+ onSelect = {},
modifier = Modifier.testTag(TEST_TAG),
)
}
@@ -217,7 +217,7 @@
selectionControl = {
selectionControl()
},
- onSelected = {},
+ onSelect = {},
onClick = {},
modifier = Modifier.testTag(TEST_TAG),
)
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
index 0df3716..f5c3ef0 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
@@ -643,7 +643,7 @@
selected = selected,
enabled = enabled,
colors = colors,
- onSelected = onSelected,
+ onSelect = onSelected,
label = label,
secondaryLabel = secondaryLabel,
icon = icon,
@@ -668,7 +668,7 @@
colors = colors,
selected = selected,
enabled = enabled,
- onSelected = onSelected,
+ onSelect = onSelected,
label = label,
secondaryLabel = secondaryLabel,
onClick = onClick,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextTest.kt
new file mode 100644
index 0000000..d57dcba
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextTest.kt
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.os.Build
+import android.text.format.DateFormat
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.DeviceConfigurationOverride
+import androidx.compose.ui.test.RoundScreen
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.SdkSuppress
+import java.util.Calendar
+import java.util.Locale
+import java.util.TimeZone
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+
+class TimeTextTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun supports_testtag() {
+ rule.setContentWithTheme {
+ TimeText(
+ modifier = Modifier.testTag(TEST_TAG)
+ ) {
+ time()
+ }
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+
+ @Test
+ fun updates_clock_when_source_changes_on_non_round_device() {
+ val timeState = mutableStateOf("Unchanged")
+
+ rule.setContentWithTheme {
+ DeviceConfigurationOverride(
+ DeviceConfigurationOverride.RoundScreen(false)
+ ) {
+ TimeText(
+ modifier = Modifier.testTag(TEST_TAG),
+ timeSource = object : TimeSource {
+ override val currentTime: String
+ @Composable
+ get() = timeState.value
+ },
+ ) {
+ time()
+ }
+ }
+ }
+ timeState.value = "Changed"
+ rule.onNodeWithText("Changed").assertIsDisplayed()
+ }
+
+ @Test
+ fun updates_clock_when_source_changes_on_round_device() {
+ val timeState = mutableStateOf("Unchanged")
+
+ rule.setContentWithTheme {
+ DeviceConfigurationOverride(
+ DeviceConfigurationOverride.RoundScreen(true)
+ ) {
+ TimeText(
+ modifier = Modifier.testTag(TEST_TAG),
+ timeSource = object : TimeSource {
+ override val currentTime: String
+ @Composable
+ get() = timeState.value
+ },
+ ) {
+ time()
+ }
+ }
+ }
+ timeState.value = "Changed"
+ rule.waitForIdle()
+ rule.onNodeWithContentDescription("Changed").assertIsDisplayed()
+ }
+
+ @Test
+ fun checks_status_displayed_on_non_round_device() {
+ val statusText = "Status"
+
+ rule.setContentWithTheme {
+ DeviceConfigurationOverride(
+ DeviceConfigurationOverride.RoundScreen(false)
+ ) {
+ TimeText {
+ text(statusText)
+ separator()
+ time()
+ }
+ }
+ }
+
+ rule.onNodeWithText(statusText).assertIsDisplayed()
+ }
+
+ @Test
+ fun checks_status_displayed_on_round_device() {
+ val statusText = "Status"
+
+ rule.setContentWithTheme {
+ DeviceConfigurationOverride(
+ DeviceConfigurationOverride.RoundScreen(true)
+ ) {
+ TimeText {
+ text(statusText)
+ separator()
+ time()
+ }
+ }
+ }
+
+ rule.onNodeWithContentDescription(statusText).assertIsDisplayed()
+ }
+
+ @Test
+ fun checks_separator_displayed_on_non_round_device() {
+ val statusText = "Status"
+ val separatorText = "·"
+
+ rule.setContentWithTheme {
+ DeviceConfigurationOverride(
+ DeviceConfigurationOverride.RoundScreen(false)
+ ) {
+ TimeText {
+ text(statusText)
+ separator()
+ time()
+ }
+ }
+ }
+
+ rule.onNodeWithText(separatorText).assertIsDisplayed()
+ }
+
+ @Test
+ fun checks_separator_displayed_on_round_device() {
+ val statusText = "Status"
+ val separatorText = "·"
+
+ rule.setContentWithTheme {
+ DeviceConfigurationOverride(
+ DeviceConfigurationOverride.RoundScreen(true)
+ ) {
+ TimeText {
+ text(statusText)
+ separator()
+ time()
+ }
+ }
+ }
+
+ rule.onNodeWithContentDescription(separatorText).assertIsDisplayed()
+ }
+
+ @Test
+ fun checks_composable_displayed_on_non_round_device() {
+ rule.setContentWithTheme {
+ DeviceConfigurationOverride(
+ DeviceConfigurationOverride.RoundScreen(false)
+ ) {
+ TimeText {
+ time()
+ separator()
+ composable {
+ Text(
+ modifier = Modifier.testTag(TEST_TAG),
+ text = "Compose",
+ )
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertIsDisplayed()
+ }
+
+ @Test
+ fun checks_composable_displayed_on_round_device() {
+ rule.setContentWithTheme {
+ DeviceConfigurationOverride(
+ DeviceConfigurationOverride.RoundScreen(true)
+ ) {
+ TimeText {
+ time()
+ separator()
+ composable {
+ Text(
+ modifier = Modifier.testTag(TEST_TAG),
+ text = "Compose",
+ )
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertIsDisplayed()
+ }
+
+ @Test
+ fun changes_timeTextStyle_on_non_round_device() {
+ val timeText = "testTime"
+
+ val testTextStyle = TextStyle(
+ color = Color.Green,
+ background = Color.Black,
+ fontSize = 20.sp
+ )
+ rule.setContentWithTheme {
+ DeviceConfigurationOverride(
+ DeviceConfigurationOverride.RoundScreen(false)
+ ) {
+ TimeText(
+ timeSource = object : TimeSource {
+ override val currentTime: String
+ @Composable
+ get() = timeText
+ },
+ timeTextStyle = testTextStyle
+ ) {
+ time()
+ }
+ }
+ }
+ val actualStyle = rule.textStyleOf(timeText)
+ Assert.assertEquals(testTextStyle.color, actualStyle.color)
+ Assert.assertEquals(testTextStyle.background, actualStyle.background)
+ Assert.assertEquals(testTextStyle.fontSize, actualStyle.fontSize)
+ }
+
+ @Test
+ fun changes_material_theme_on_non_round_device() {
+ val timeText = "testTime"
+
+ val testTextStyle = TextStyle(
+ color = Color.Green,
+ background = Color.Black,
+ fontStyle = FontStyle.Italic,
+ fontSize = 25.sp,
+ fontFamily = FontFamily.SansSerif
+ )
+ rule.setContent {
+ MaterialTheme(
+ typography = MaterialTheme.typography.copy(
+ labelSmall = testTextStyle
+ )
+ ) {
+ DeviceConfigurationOverride(
+ DeviceConfigurationOverride.RoundScreen(false)
+ ) {
+ TimeText(
+ timeSource = object : TimeSource {
+ override val currentTime: String
+ @Composable
+ get() = timeText
+ }
+ ) {
+ time()
+ }
+ }
+ }
+ }
+ val actualStyle = rule.textStyleOf(timeText)
+ Assert.assertEquals(testTextStyle.color, actualStyle.color)
+ Assert.assertEquals(testTextStyle.background, actualStyle.background)
+ Assert.assertEquals(testTextStyle.fontSize, actualStyle.fontSize)
+ Assert.assertEquals(testTextStyle.fontStyle, actualStyle.fontStyle)
+ Assert.assertEquals(testTextStyle.fontFamily, actualStyle.fontFamily)
+ }
+
+ @Test
+ fun formats_current_time() {
+ val currentTimeInMillis = 1631544258000L // 2021-09-13 14:44:18
+ val format = "HH:mm:ss"
+ val currentCalendar = Calendar.getInstance().apply { timeInMillis = currentTimeInMillis }
+ val convertedTime = DateFormat.format(format, currentCalendar).toString()
+
+ var actualTime: String? = null
+ rule.setContentWithTheme {
+ actualTime = currentTime({ currentTimeInMillis }, format).value
+ }
+ Assert.assertEquals(convertedTime, actualTime)
+ }
+
+ @Test
+ fun formats_current_time_12H() {
+ val currentTimeInMillis = 1631544258000L // 2021-09-13 14:44:18
+ val expectedTime = "2:44"
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
+
+ var actualTime: String? = null
+ rule.setContentWithTheme {
+ actualTime = currentTime(
+ { currentTimeInMillis },
+ TimeTextDefaults.TimeFormat12Hours
+ ).value
+ }
+ Assert.assertEquals(expectedTime, actualTime)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+ @Test
+ fun formats_current_time_12H_french_locale() {
+ val currentTimeInMillis = 1631544258000L // 2021-09-13 14:44:18
+ val expectedTime = "2 h 44"
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
+
+ var actualTime: String? = null
+ Locale.setDefault(Locale.CANADA_FRENCH)
+
+ rule.setContentWithTheme {
+ val format = TimeTextDefaults.timeFormat()
+ actualTime = currentTime({ currentTimeInMillis }, format).value
+ }
+ Assert.assertEquals(expectedTime, actualTime)
+ }
+}
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 5f95dc3..9b830dc 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
@@ -222,6 +222,10 @@
// Toggle Button
internal var defaultToggleButtonColorsCached: ToggleButtonColors? = null
internal var defaultSplitToggleButtonColorsCached: SplitToggleButtonColors? = null
+
+ // Radio Button
+ internal var defaultRadioButtonColorsCached: RadioButtonColors? = null
+ internal var defaultSplitRadioButtonColorsCached: SplitRadioButtonColors? = null
}
/**
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
index 00f7cec..be6073c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
@@ -52,12 +52,15 @@
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.tokens.MotionTokens
+import androidx.wear.compose.material3.tokens.RadioButtonTokens
+import androidx.wear.compose.material3.tokens.SplitRadioButtonTokens
import androidx.wear.compose.materialcore.animateSelectionColor
/**
@@ -85,7 +88,7 @@
* [RadioButtonDefaults], e.g. [RadioButtonDefaults.radioButtonColors].
*
* @param selected Boolean flag indicating whether this button is currently selected.
- * @param onSelected Callback to be invoked when this button has been selected by clicking.
+ * @param onSelect Callback to be invoked when this button has been selected by clicking.
* @param selectionControl A slot for the button's selection control.
* The [Radio] selection control is provided for this purpose.
* @param modifier Modifier to be applied to the [RadioButton].
@@ -103,8 +106,7 @@
* interactions will still happen internally.
* @param icon An optional slot for providing an icon to indicate the purpose of the button.
* The contents are expected to be center-aligned, both horizontally and vertically, and should be
- * an icon of size 24.dp. In order to correctly render when the Button is not enabled the
- * icon must set its alpha value to [LocalContentAlpha].
+ * an icon of size 24.dp.
* @param secondaryLabel A slot for providing the button's secondary label. The contents are
* expected to be text which is "start" aligned.
* @param label A slot for providing the button's main label. The contents are expected to be text
@@ -113,13 +115,13 @@
@Composable
fun RadioButton(
selected: Boolean,
- onSelected: () -> Unit,
+ onSelect: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
selectionControl: @Composable SelectionControlScope.() -> Unit = {
Radio()
},
- shape: Shape = MaterialTheme.shapes.large,
+ shape: Shape = RadioButtonTokens.Shape.value,
colors: RadioButtonColors = RadioButtonDefaults.radioButtonColors(),
contentPadding: PaddingValues = RadioButtonDefaults.ContentPadding,
interactionSource: MutableInteractionSource? = null,
@@ -138,7 +140,7 @@
.selectable(
enabled = enabled,
selected = selected,
- onClick = onSelected,
+ onClick = onSelect,
indication = rippleOrFallbackImplementation(),
interactionSource = interactionSource
)
@@ -158,12 +160,12 @@
Labels(
label = provideScopeContent(
contentColor = colors.contentColor(enabled = enabled, selected = selected),
- textStyle = MaterialTheme.typography.labelMedium,
+ textStyle = RadioButtonTokens.LabelFont.value,
content = label
),
secondaryLabel = provideNullableScopeContent(
contentColor = colors.secondaryContentColor(enabled = enabled, selected = selected),
- textStyle = MaterialTheme.typography.labelSmall,
+ textStyle = RadioButtonTokens.SecondaryLabelFont.value,
content = secondaryLabel
)
)
@@ -197,7 +199,7 @@
*
* A [SplitRadioButton] has two tappable areas, one tap area for the labels and another for the
* selection control. The [onClick] listener will be associated with the main body of the
- * split radio button with the [onSelected] listener associated with the selection control
+ * split radio button with the [onSelect] listener associated with the selection control
* area only.
*
* Samples:
@@ -214,7 +216,7 @@
* click events.
*
* @param selected Boolean flag indicating whether this button is currently selected.
- * @param onSelected Callback to be invoked when this button has been selected by clicking.
+ * @param onSelect Callback to be invoked when this button has been selected by clicking.
* @param onClick Click listener called when the user clicks the main body of the button, the area
* behind the labels.
* @param selectionControl A slot for providing the button's selection control.
@@ -244,14 +246,14 @@
@Composable
fun SplitRadioButton(
selected: Boolean,
- onSelected: () -> Unit,
+ onSelect: () -> Unit,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
selectionControl: @Composable SelectionControlScope.() -> Unit = {
Radio()
},
- shape: Shape = MaterialTheme.shapes.large,
+ shape: Shape = SplitRadioButtonTokens.Shape.value,
colors: SplitRadioButtonColors = RadioButtonDefaults.splitRadioButtonColors(),
selectionInteractionSource: MutableInteractionSource? = null,
clickInteractionSource: MutableInteractionSource? = null,
@@ -289,13 +291,13 @@
Labels(
label = provideScopeContent(
contentColor = colors.contentColor(enabled = enabled, selected = selected),
- textStyle = MaterialTheme.typography.labelMedium,
+ textStyle = SplitRadioButtonTokens.LabelFont.value,
content = label
),
secondaryLabel = provideNullableScopeContent(
contentColor =
colors.secondaryContentColor(enabled = enabled, selected = selected),
- textStyle = MaterialTheme.typography.labelSmall,
+ textStyle = SplitRadioButtonTokens.SecondaryLabelFont.value,
content = secondaryLabel
),
)
@@ -312,7 +314,7 @@
.selectable(
enabled = enabled,
selected = selected,
- onClick = onSelected,
+ onClick = onSelect,
indication = rippleOrFallbackImplementation(),
interactionSource = selectionInteractionSource
)
@@ -342,6 +344,12 @@
/**
* Creates a [RadioButtonColors] for use in a [RadioButton].
+ */
+ @Composable
+ fun radioButtonColors() = MaterialTheme.colorScheme.defaultRadioButtonColors
+
+ /**
+ * Creates a [RadioButtonColors] for use in a [RadioButton].
*
* @param selectedContainerColor The container color of the [RadioButton]
* when enabled and selected.
@@ -359,21 +367,42 @@
* when enabled and not selected, used for secondaryLabel content
* @param unselectedIconColor The icon color of the [RadioButton]
* when enabled and not selected.
+ * @param disabledSelectedContainerColor The container color of the [RadioButton] when disabled
+ * and selected.
+ * @param disabledSelectedContentColor The content color of the [RadioButton] when disabled
+ * and selected.
+ * @param disabledSelectedSecondaryContentColor The secondary content color of the [RadioButton]
+ * when disabled and selected, used for the secondary content.
+ * @param disabledSelectedIconColor The icon color of the [RadioButton] when disabled and
+ * selected.
+ * @param disabledUnselectedContainerColor The container color of the [RadioButton] when
+ * disabled and not selected.
+ * @param disabledUnselectedContentColor The content color of the [RadioButton] when disabled
+ * and not selected.
+ * @param disabledUnselectedSecondaryContentColor The secondary content color of the
+ * [RadioButton] when disabled and not selected, used for secondary label content.
+ * @param disabledUnselectedIconColor The icon color of the [RadioButton] when disabled and
+ * not selected.
*/
@Composable
fun radioButtonColors(
- selectedContainerColor: Color = MaterialTheme.colorScheme.primaryContainer,
- selectedContentColor: Color = MaterialTheme.colorScheme.onPrimaryContainer,
- selectedSecondaryContentColor: Color = MaterialTheme.colorScheme.onPrimaryContainer.copy(
- alpha = 0.8f
- ),
- selectedIconColor: Color = MaterialTheme.colorScheme.onPrimaryContainer,
- unselectedContainerColor: Color = MaterialTheme.colorScheme.surface,
- unselectedContentColor: Color = MaterialTheme.colorScheme.onSurface,
- unselectedSecondaryContentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
- unselectedIconColor: Color = MaterialTheme.colorScheme.primary,
- ) =
- RadioButtonColors(
+ selectedContainerColor: Color = Color.Unspecified,
+ selectedContentColor: Color = Color.Unspecified,
+ selectedSecondaryContentColor: Color = Color.Unspecified,
+ selectedIconColor: Color = Color.Unspecified,
+ unselectedContainerColor: Color = Color.Unspecified,
+ unselectedContentColor: Color = Color.Unspecified,
+ unselectedSecondaryContentColor: Color = Color.Unspecified,
+ unselectedIconColor: Color = Color.Unspecified,
+ disabledSelectedContainerColor: Color = Color.Unspecified,
+ disabledSelectedContentColor: Color = Color.Unspecified,
+ disabledSelectedSecondaryContentColor: Color = Color.Unspecified,
+ disabledSelectedIconColor: Color = Color.Unspecified,
+ disabledUnselectedContainerColor: Color = Color.Unspecified,
+ disabledUnselectedContentColor: Color = Color.Unspecified,
+ disabledUnselectedSecondaryContentColor: Color = Color.Unspecified,
+ disabledUnselectedIconColor: Color = Color.Unspecified
+ ) = MaterialTheme.colorScheme.defaultRadioButtonColors.copy(
selectedContainerColor = selectedContainerColor,
selectedContentColor = selectedContentColor,
selectedSecondaryContentColor = selectedSecondaryContentColor,
@@ -382,19 +411,24 @@
unselectedContentColor = unselectedContentColor,
unselectedSecondaryContentColor = unselectedSecondaryContentColor,
unselectedIconColor = unselectedIconColor,
- disabledSelectedContainerColor = selectedContainerColor.toDisabledColor(),
- disabledSelectedContentColor = selectedContentColor.toDisabledColor(),
- disabledSelectedSecondaryContentColor = selectedSecondaryContentColor.toDisabledColor(),
- disabledSelectedIconColor = selectedIconColor.toDisabledColor(),
- disabledUnselectedContainerColor = unselectedContainerColor.toDisabledColor(),
- disabledUnselectedContentColor = unselectedContentColor.toDisabledColor(),
- disabledUnselectedSecondaryContentColor =
- unselectedSecondaryContentColor.toDisabledColor(),
- disabledUnselectedIconColor = unselectedIconColor.toDisabledColor(),
+ disabledSelectedContainerColor = disabledSelectedContainerColor,
+ disabledSelectedContentColor = disabledSelectedContentColor,
+ disabledSelectedSecondaryContentColor = disabledSelectedSecondaryContentColor,
+ disabledSelectedIconColor = disabledSelectedIconColor,
+ disabledUnselectedContainerColor = disabledUnselectedContainerColor,
+ disabledUnselectedContentColor = disabledUnselectedContentColor,
+ disabledUnselectedSecondaryContentColor = disabledUnselectedSecondaryContentColor,
+ disabledUnselectedIconColor = disabledUnselectedIconColor,
)
/**
* Creates a [SplitRadioButtonColors] for use in a [SplitRadioButton].
+ */
+ @Composable
+ fun splitRadioButtonColors() = MaterialTheme.colorScheme.defaultSplitRadioButtonColors
+
+ /**
+ * Creates a [SplitRadioButtonColors] for use in a [SplitRadioButton].
*
* @param selectedContainerColor The container color of the [SplitRadioButton] when enabled and
* selected.
@@ -405,28 +439,49 @@
* @param selectedSplitContainerColor The split container color of the [SplitRadioButton]
* when enabled and selected.
* @param unselectedContainerColor The container color of the [SplitRadioButton] when enabled
- * and unselected.
+ * and not selected.
* @param unselectedContentColor The content color of the [SplitRadioButton] when enabled and
- * unselected.
+ * not selected.
* @param unselectedSecondaryContentColor The secondary content color of the [SplitRadioButton]
- * when enabled and unselected, used for secondaryLabel content.
+ * when enabled and not selected, used for secondaryLabel content.
* @param unselectedSplitContainerColor The split container color of the [SplitRadioButton] when
- * enabled and unselected.
+ * enabled and not selected.
+ * @param disabledSelectedContainerColor The container color of the [SplitRadioButton] when
+ * disabled and selected.
+ * @param disabledSelectedContentColor The content color of the [SplitRadioButton] when disabled
+ * and selected.
+ * @param disabledSelectedSecondaryContentColor The secondary content color of the
+ * [SplitRadioButton] when disabled and selected, used for secondaryLabel content.
+ * @param disabledSelectedSplitContainerColor The split container color of the
+ * [SplitRadioButton] when disabled and selected.
+ * @param disabledUnselectedContainerColor The container color of the [SplitRadioButton] when
+ * disabled and not selected.
+ * @param disabledUnselectedContentColor The content color of the [SplitRadioButton] when
+ * disabled and not selected.
+ * @param disabledUnselectedSecondaryContentColor The secondary content color of the
+ * [SplitRadioButton] when disabled and not selected, used for secondaryLabel content.
+ * @param disabledUnselectedSplitContainerColor The split container color of the
+ * [SplitRadioButton] when disabled and not selected.
*/
@Composable
fun splitRadioButtonColors(
- selectedContainerColor: Color = MaterialTheme.colorScheme.primaryContainer,
- selectedContentColor: Color = MaterialTheme.colorScheme.onPrimaryContainer,
- selectedSecondaryContentColor: Color = MaterialTheme.colorScheme.onPrimaryContainer.copy(
- alpha = 0.8f
- ),
- selectedSplitContainerColor: Color = MaterialTheme.colorScheme.primary.copy(.15f),
- unselectedContainerColor: Color = MaterialTheme.colorScheme.surface,
- unselectedContentColor: Color = MaterialTheme.colorScheme.onSurface,
- unselectedSecondaryContentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
- unselectedSplitContainerColor: Color = MaterialTheme.colorScheme.surfaceBright
- ) =
- SplitRadioButtonColors(
+ selectedContainerColor: Color = Color.Unspecified,
+ selectedContentColor: Color = Color.Unspecified,
+ selectedSecondaryContentColor: Color = Color.Unspecified,
+ selectedSplitContainerColor: Color = Color.Unspecified,
+ unselectedContainerColor: Color = Color.Unspecified,
+ unselectedContentColor: Color = Color.Unspecified,
+ unselectedSecondaryContentColor: Color = Color.Unspecified,
+ unselectedSplitContainerColor: Color = Color.Unspecified,
+ disabledSelectedContainerColor: Color = Color.Unspecified,
+ disabledSelectedContentColor: Color = Color.Unspecified,
+ disabledSelectedSecondaryContentColor: Color = Color.Unspecified,
+ disabledSelectedSplitContainerColor: Color = Color.Unspecified,
+ disabledUnselectedContainerColor: Color = Color.Unspecified,
+ disabledUnselectedContentColor: Color = Color.Unspecified,
+ disabledUnselectedSecondaryContentColor: Color = Color.Unspecified,
+ disabledUnselectedSplitContainerColor: Color = Color.Unspecified
+ ) = MaterialTheme.colorScheme.defaultSplitRadioButtonColors.copy(
selectedContainerColor = selectedContainerColor,
selectedContentColor = selectedContentColor,
selectedSecondaryContentColor = selectedSecondaryContentColor,
@@ -435,26 +490,117 @@
unselectedContentColor = unselectedContentColor,
unselectedSecondaryContentColor = unselectedSecondaryContentColor,
unselectedSplitContainerColor = unselectedSplitContainerColor,
- disabledSelectedContainerColor = selectedContainerColor.toDisabledColor(),
- disabledSelectedContentColor = selectedContentColor.toDisabledColor(),
- disabledSelectedSecondaryContentColor = selectedSecondaryContentColor.toDisabledColor(),
- disabledSelectedSplitContainerColor = selectedSplitContainerColor.toDisabledColor(),
- disabledUnselectedContainerColor = unselectedContainerColor.toDisabledColor(),
- disabledUnselectedContentColor = unselectedContentColor.toDisabledColor(),
- disabledUnselectedSecondaryContentColor =
- unselectedSecondaryContentColor.toDisabledColor(),
- disabledUnselectedSplitContainerColor = unselectedSplitContainerColor.toDisabledColor()
+ disabledSelectedContainerColor = disabledSelectedContainerColor,
+ disabledSelectedContentColor = disabledSelectedContentColor,
+ disabledSelectedSecondaryContentColor = disabledSelectedSecondaryContentColor,
+ disabledSelectedSplitContainerColor = disabledSelectedSplitContainerColor,
+ disabledUnselectedContainerColor = disabledUnselectedContainerColor,
+ disabledUnselectedContentColor = disabledUnselectedContentColor,
+ disabledUnselectedSecondaryContentColor = disabledUnselectedSecondaryContentColor,
+ disabledUnselectedSplitContainerColor = disabledUnselectedSplitContainerColor
)
private val HorizontalPadding = 14.dp
private val VerticalPadding = 6.dp
+ /**
+ * The default content padding used by [RadioButton]
+ */
val ContentPadding: PaddingValues = PaddingValues(
start = HorizontalPadding,
top = VerticalPadding,
end = HorizontalPadding,
bottom = VerticalPadding
)
+
+ private val ColorScheme.defaultRadioButtonColors: RadioButtonColors
+ get() {
+ return defaultRadioButtonColorsCached ?: RadioButtonColors(
+ selectedContainerColor = fromToken(RadioButtonTokens.SelectedContainerColor),
+ selectedContentColor = fromToken(RadioButtonTokens.SelectedContentColor),
+ selectedSecondaryContentColor =
+ fromToken(RadioButtonTokens.SelectedSecondaryLabelColor)
+ .copy(alpha = RadioButtonTokens.SelectedSecondaryLabelOpacity),
+ selectedIconColor = fromToken(RadioButtonTokens.SelectedIconColor),
+ unselectedContainerColor = fromToken(RadioButtonTokens.UnselectedContainerColor),
+ unselectedContentColor = fromToken(RadioButtonTokens.UnselectedContentColor),
+ unselectedSecondaryContentColor =
+ fromToken(RadioButtonTokens.UnselectedSecondaryLabelColor),
+ unselectedIconColor = fromToken(RadioButtonTokens.UnselectedIconColor),
+ disabledSelectedContainerColor =
+ fromToken(RadioButtonTokens.DisabledSelectedContainerColor)
+ .toDisabledColor(disabledAlpha = RadioButtonTokens.DisabledOpacity),
+ disabledSelectedContentColor =
+ fromToken(RadioButtonTokens.DisabledSelectedContentColor)
+ .toDisabledColor(disabledAlpha = RadioButtonTokens.DisabledOpacity),
+ disabledSelectedSecondaryContentColor =
+ fromToken(RadioButtonTokens.DisabledSelectedSecondaryLabelColor)
+ .copy(alpha = RadioButtonTokens.DisabledSelectedSecondaryLabelOpacity)
+ .toDisabledColor(disabledAlpha = RadioButtonTokens.DisabledOpacity),
+ disabledSelectedIconColor =
+ fromToken(RadioButtonTokens.DisabledSelectedIconColor)
+ .toDisabledColor(disabledAlpha = RadioButtonTokens.DisabledOpacity),
+ disabledUnselectedContainerColor =
+ fromToken(RadioButtonTokens.DisabledUnselectedContainerColor)
+ .toDisabledColor(disabledAlpha = RadioButtonTokens.DisabledOpacity),
+ disabledUnselectedContentColor =
+ fromToken(RadioButtonTokens.DisabledUnselectedContentColor)
+ .toDisabledColor(disabledAlpha = RadioButtonTokens.DisabledOpacity),
+ disabledUnselectedSecondaryContentColor =
+ fromToken(RadioButtonTokens.DisabledUnselectedSecondaryLabelColor)
+ .toDisabledColor(disabledAlpha = RadioButtonTokens.DisabledOpacity),
+ disabledUnselectedIconColor =
+ fromToken(RadioButtonTokens.DisabledUnselectedIconColor)
+ .toDisabledColor(disabledAlpha = RadioButtonTokens.DisabledOpacity)
+ ).also { defaultRadioButtonColorsCached = it }
+ }
+
+ private val ColorScheme.defaultSplitRadioButtonColors: SplitRadioButtonColors
+ get() {
+ return defaultSplitRadioButtonColorsCached ?: SplitRadioButtonColors(
+ selectedContainerColor = fromToken(SplitRadioButtonTokens.SelectedContainerColor),
+ selectedContentColor = fromToken(SplitRadioButtonTokens.SelectedContentColor),
+ selectedSecondaryContentColor =
+ fromToken(SplitRadioButtonTokens.SelectedSecondaryLabelColor)
+ .copy(alpha = SplitRadioButtonTokens.SelectedSecondaryLabelOpacity),
+ selectedSplitContainerColor =
+ fromToken(SplitRadioButtonTokens.SelectedSplitContainerColor)
+ .copy(alpha = SplitRadioButtonTokens.SelectedSplitContainerOpacity),
+ unselectedContainerColor =
+ fromToken(SplitRadioButtonTokens.UnselectedContainerColor),
+ unselectedContentColor = fromToken(SplitRadioButtonTokens.UnselectedContentColor),
+ unselectedSecondaryContentColor =
+ fromToken(SplitRadioButtonTokens.UnselectedSecondaryLabelColor),
+ unselectedSplitContainerColor =
+ fromToken(SplitRadioButtonTokens.UnselectedSplitContainerColor),
+ disabledSelectedContainerColor =
+ fromToken(SplitRadioButtonTokens.DisabledSelectedContainerColor)
+ .toDisabledColor(disabledAlpha = SplitRadioButtonTokens.DisabledOpacity),
+ disabledSelectedContentColor =
+ fromToken(SplitRadioButtonTokens.DisabledSelectedContentColor)
+ .toDisabledColor(disabledAlpha = SplitRadioButtonTokens.DisabledOpacity),
+ disabledSelectedSecondaryContentColor =
+ fromToken(SplitRadioButtonTokens.DisabledSelectedSecondaryLabelColor)
+ .copy(alpha = SplitRadioButtonTokens.DisabledSelectedSecondaryLabelOpacity)
+ .toDisabledColor(disabledAlpha = SplitRadioButtonTokens.DisabledOpacity),
+ disabledSelectedSplitContainerColor =
+ fromToken(SplitRadioButtonTokens.DisabledSelectedSplitContainerColor)
+ .copy(alpha = SplitRadioButtonTokens.DisabledSelectedSplitContainerOpacity)
+ .toDisabledColor(disabledAlpha = SplitRadioButtonTokens.DisabledOpacity),
+ disabledUnselectedContainerColor =
+ fromToken(SplitRadioButtonTokens.DisabledUnselectedContainerColor)
+ .toDisabledColor(disabledAlpha = SplitRadioButtonTokens.DisabledOpacity),
+ disabledUnselectedContentColor =
+ fromToken(SplitRadioButtonTokens.DisabledUnselectedContentColor)
+ .toDisabledColor(disabledAlpha = SplitRadioButtonTokens.DisabledOpacity),
+ disabledUnselectedSecondaryContentColor =
+ fromToken(SplitRadioButtonTokens.DisabledUnselectedSecondaryLabelColor)
+ .toDisabledColor(disabledAlpha = SplitRadioButtonTokens.DisabledOpacity),
+ disabledUnselectedSplitContainerColor =
+ fromToken(SplitRadioButtonTokens.DisabledUnselectedSplitContainerColor)
+ .toDisabledColor(disabledAlpha = SplitRadioButtonTokens.DisabledOpacity)
+ ).also { defaultSplitRadioButtonColorsCached = it }
+ }
}
/**
@@ -507,6 +653,54 @@
val disabledUnselectedSecondaryContentColor: Color,
val disabledUnselectedIconColor: Color,
) {
+
+ internal fun copy(
+ selectedContainerColor: Color,
+ selectedContentColor: Color,
+ selectedSecondaryContentColor: Color,
+ selectedIconColor: Color,
+ unselectedContainerColor: Color,
+ unselectedContentColor: Color,
+ unselectedSecondaryContentColor: Color,
+ unselectedIconColor: Color,
+ disabledSelectedContainerColor: Color,
+ disabledSelectedContentColor: Color,
+ disabledSelectedSecondaryContentColor: Color,
+ disabledSelectedIconColor: Color,
+ disabledUnselectedContainerColor: Color,
+ disabledUnselectedContentColor: Color,
+ disabledUnselectedSecondaryContentColor: Color,
+ disabledUnselectedIconColor: Color,
+ ): RadioButtonColors = RadioButtonColors(
+ selectedContainerColor = selectedContainerColor.takeOrElse { this.selectedContainerColor },
+ selectedContentColor = selectedContentColor.takeOrElse { this.selectedContentColor },
+ selectedSecondaryContentColor = selectedSecondaryContentColor
+ .takeOrElse { this.selectedSecondaryContentColor },
+ selectedIconColor = selectedIconColor.takeOrElse { this.selectedIconColor },
+ unselectedContainerColor = unselectedContainerColor
+ .takeOrElse { this.unselectedContainerColor },
+ unselectedContentColor = unselectedContentColor.takeOrElse { this.unselectedContentColor },
+ unselectedSecondaryContentColor = unselectedSecondaryContentColor
+ .takeOrElse { this.unselectedSecondaryContentColor },
+ unselectedIconColor = unselectedIconColor.takeOrElse { this.unselectedIconColor },
+ disabledSelectedContainerColor = disabledSelectedContainerColor
+ .takeOrElse { this.disabledSelectedContainerColor },
+ disabledSelectedContentColor = disabledSelectedContentColor
+ .takeOrElse { this.disabledSelectedContentColor },
+ disabledSelectedSecondaryContentColor = disabledSelectedSecondaryContentColor
+ .takeOrElse { this.disabledSelectedSecondaryContentColor },
+ disabledSelectedIconColor = disabledSelectedIconColor
+ .takeOrElse { this.disabledSelectedIconColor },
+ disabledUnselectedContainerColor = disabledUnselectedContainerColor
+ .takeOrElse { this.disabledUnselectedContainerColor },
+ disabledUnselectedContentColor = disabledUnselectedContentColor
+ .takeOrElse { this.disabledUnselectedContentColor },
+ disabledUnselectedSecondaryContentColor = disabledUnselectedSecondaryContentColor
+ .takeOrElse { this.disabledUnselectedSecondaryContentColor },
+ disabledUnselectedIconColor = disabledUnselectedIconColor
+ .takeOrElse { this.disabledUnselectedIconColor },
+ )
+
/**
* Determines the container color based on whether the radio button is [enabled]
* and [selected].
@@ -515,7 +709,7 @@
* @param selected Whether the radio button is checked
*/
@Composable
- fun containerColor(enabled: Boolean, selected: Boolean): State<Color> =
+ internal fun containerColor(enabled: Boolean, selected: Boolean): State<Color> =
animateSelectionColor(
enabled = enabled,
checked = selected,
@@ -534,7 +728,7 @@
* @param selected Whether the radio button is checked
*/
@Composable
- fun contentColor(enabled: Boolean, selected: Boolean): State<Color> =
+ internal fun contentColor(enabled: Boolean, selected: Boolean): State<Color> =
animateSelectionColor(
enabled = enabled,
checked = selected,
@@ -552,7 +746,7 @@
* @param selected Whether the RadioButton is currently selected or unselected.
*/
@Composable
- fun secondaryContentColor(enabled: Boolean, selected: Boolean): State<Color> =
+ internal fun secondaryContentColor(enabled: Boolean, selected: Boolean): State<Color> =
animateSelectionColor(
enabled = enabled,
checked = selected,
@@ -571,7 +765,7 @@
* @param selected Whether the RadioButton is currently selected or unselected.
*/
@Composable
- fun iconColor(enabled: Boolean, selected: Boolean): State<Color> =
+ internal fun iconColor(enabled: Boolean, selected: Boolean): State<Color> =
animateSelectionColor(
enabled = enabled,
checked = selected,
@@ -689,6 +883,56 @@
val disabledUnselectedSecondaryContentColor: Color,
val disabledUnselectedSplitContainerColor: Color,
) {
+
+ internal fun copy(
+ selectedContainerColor: Color,
+ selectedContentColor: Color,
+ selectedSecondaryContentColor: Color,
+ selectedSplitContainerColor: Color,
+ unselectedContainerColor: Color,
+ unselectedContentColor: Color,
+ unselectedSecondaryContentColor: Color,
+ unselectedSplitContainerColor: Color,
+ disabledSelectedContainerColor: Color,
+ disabledSelectedContentColor: Color,
+ disabledSelectedSecondaryContentColor: Color,
+ disabledSelectedSplitContainerColor: Color,
+ disabledUnselectedContainerColor: Color,
+ disabledUnselectedContentColor: Color,
+ disabledUnselectedSecondaryContentColor: Color,
+ disabledUnselectedSplitContainerColor: Color,
+ ): SplitRadioButtonColors = SplitRadioButtonColors(
+ selectedContainerColor = selectedContainerColor.takeOrElse { this.selectedContainerColor },
+ selectedContentColor = selectedContentColor.takeOrElse { this.selectedContentColor },
+ selectedSecondaryContentColor = selectedSecondaryContentColor
+ .takeOrElse { this.selectedSecondaryContentColor },
+ selectedSplitContainerColor = selectedSplitContainerColor
+ .takeOrElse { this.selectedSplitContainerColor },
+ unselectedContainerColor = unselectedContainerColor
+ .takeOrElse { this.unselectedContainerColor },
+ unselectedContentColor = unselectedContentColor.takeOrElse { this.unselectedContentColor },
+ unselectedSecondaryContentColor = unselectedSecondaryContentColor
+ .takeOrElse { this.unselectedSecondaryContentColor },
+ unselectedSplitContainerColor = unselectedSplitContainerColor
+ .takeOrElse { this.unselectedSplitContainerColor },
+ disabledSelectedContainerColor = disabledSelectedContainerColor
+ .takeOrElse { this.disabledSelectedContainerColor },
+ disabledSelectedContentColor = disabledSelectedContentColor
+ .takeOrElse { this.disabledSelectedContentColor },
+ disabledSelectedSecondaryContentColor = disabledSelectedSecondaryContentColor
+ .takeOrElse { this.disabledSelectedSecondaryContentColor },
+ disabledSelectedSplitContainerColor = disabledSelectedSplitContainerColor
+ .takeOrElse { this.disabledSelectedSplitContainerColor },
+ disabledUnselectedContainerColor = disabledUnselectedContainerColor
+ .takeOrElse { this.disabledUnselectedContainerColor },
+ disabledUnselectedContentColor = disabledUnselectedContentColor
+ .takeOrElse { this.disabledUnselectedContentColor },
+ disabledUnselectedSecondaryContentColor = disabledUnselectedSecondaryContentColor
+ .takeOrElse { this.disabledUnselectedSecondaryContentColor },
+ disabledUnselectedSplitContainerColor = disabledUnselectedSplitContainerColor
+ .takeOrElse { this.disabledUnselectedSplitContainerColor },
+ )
+
/**
* Determines the container color based on whether the [SplitRadioButton] is [enabled]
* and [selected].
@@ -754,7 +998,7 @@
* @param selected Whether the [SplitRadioButton] is currently selected.
*/
@Composable
- fun splitContainerColor(enabled: Boolean, selected: Boolean): State<Color> =
+ internal fun splitContainerColor(enabled: Boolean, selected: Boolean): State<Color> =
animateSelectionColor(
enabled = enabled,
checked = selected,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
new file mode 100644
index 0000000..55244b9
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
@@ -0,0 +1,459 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.text.format.DateFormat
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableLongStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+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.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import androidx.wear.compose.foundation.ArcPaddingValues
+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.curvedComposable
+import androidx.wear.compose.foundation.curvedRow
+import androidx.wear.compose.foundation.padding
+import androidx.wear.compose.foundation.sizeIn
+import androidx.wear.compose.material3.TimeTextDefaults.CurvedTextSeparator
+import androidx.wear.compose.material3.TimeTextDefaults.TextSeparator
+import androidx.wear.compose.material3.TimeTextDefaults.timeFormat
+import androidx.wear.compose.materialcore.currentTimeMillis
+import androidx.wear.compose.materialcore.is24HourFormat
+import androidx.wear.compose.materialcore.isRoundDevice
+import java.util.Calendar
+import java.util.Locale
+
+/**
+ * Layout to show the current time and a label at the top of the screen.
+ * If device has a round screen, then the time will be curved along the top edge of the screen,
+ * if rectangular - then the text and the time will be straight.
+ *
+ * Note that Wear Material UX guidance recommends that time text should not be larger than 90
+ * degrees of the screen edge on round devices, which is enforced by default.
+ * It is recommended that additional content, if any, is limited to short status messages before the
+ * [TimeTextScope.time] using the MaterialTheme.colors.primary color.
+ *
+ * For more information, see the
+ * [Curved Text](https://developer.android.com/training/wearables/components/curved-text)
+ * guide.
+ *
+ * Different components of [TimeText] can be added through methods of [TimeTextScope].
+ *
+ * A simple [TimeText] which shows the current time:
+ * @sample androidx.wear.compose.material3.samples.TimeTextClockOnly
+ *
+ * A [TimeText] with a short app status message shown:
+ * @sample androidx.wear.compose.material3.samples.TimeTextWithStatus
+ *
+ * An example of a [TimeText] with an icon along with the clock:
+ * @sample androidx.wear.compose.material3.samples.TimeTextWithIcon
+ *
+ * @param modifier The modifier to be applied to the component.
+ * @param curvedModifier The [CurvedModifier] used to restrict the arc in which [TimeText] is drawn.
+ * @param timeSource [TimeSource] which retrieves the current time and formats it.
+ * @param timeTextStyle [TextStyle] for the time text itself.
+ * @param contentPadding The spacing values between the container and the content.
+ * @param content The content of the [TimeText].
+ */
+@Composable
+public fun TimeText(
+ modifier: Modifier = Modifier,
+ curvedModifier: CurvedModifier = CurvedModifier.sizeIn(maxSweepDegrees = 90f),
+ timeSource: TimeSource = TimeTextDefaults.timeSource(timeFormat()),
+ timeTextStyle: TextStyle = TimeTextDefaults.timeTextStyle(),
+ contentPadding: PaddingValues = TimeTextDefaults.contentPadding(),
+ content: TimeTextScope.() -> Unit
+) {
+ val timeText = timeSource.currentTime
+
+ if (isRoundDevice()) {
+ CurvedLayout(modifier = modifier) {
+ curvedRow(modifier = curvedModifier.padding(contentPadding.toArcPadding())) {
+ CurvedTimeTextScope(this, timeText, timeTextStyle).content()
+ }
+ }
+ } else {
+ Row(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(contentPadding),
+ verticalAlignment = Alignment.Top,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ LinearTimeTextScope(timeText, timeTextStyle).apply {
+ content()
+ Show()
+ }
+ }
+ }
+}
+
+/**
+ * Receiver scope which is used by [TimeText].
+ */
+public sealed class TimeTextScope {
+ /**
+ * Adds a composable [Text] for non-round devices and [curvedText]
+ * for round devices to [TimeText] content.
+ *
+ * @param text The text to display.
+ * @param style configuration for the [text] such as color, font etc.
+ */
+ abstract fun text(text: String, style: TextStyle? = null)
+
+ /**
+ * Adds a text displaying current time.
+ */
+ abstract fun time()
+
+ /**
+ * Adds a separator in [TimeText].
+ *
+ * @param style configuration for the [separator] such as color, font etc.
+ */
+ abstract fun separator(style: TextStyle? = null)
+
+ /**
+ * Adds a composable in content of [TimeText].
+ * This can be used to display non-text information such as an icon.
+ *
+ * An example of a [TimeText] with an icon along with the clock:
+ * @sample androidx.wear.compose.material3.samples.TimeTextWithIcon
+ *
+ * @param content Slot for the [composable] to be displayed.
+ */
+ abstract fun composable(content: @Composable () -> Unit)
+}
+
+/**
+ * Contains the default values used by [TimeText].
+ */
+public object TimeTextDefaults {
+
+ /**
+ * By default, TimeText has 2.1% screen padding from the top.
+ */
+ private val PaddingMultiplier = 0.021f
+
+ /**
+ * Default format for 24h clock.
+ */
+ const val TimeFormat24Hours = "HH:mm"
+
+ /**
+ * Default format for 12h clock.
+ */
+ const val TimeFormat12Hours = "h:mm"
+
+ /**
+ * The default content padding used by [TimeText].
+ */
+ @Composable
+ public fun contentPadding(): PaddingValues {
+ val screenHeight = LocalConfiguration.current.screenHeightDp
+ val padding = screenHeight.times(PaddingMultiplier).dp
+ return PaddingValues(top = padding)
+ }
+
+ /**
+ * Retrieves default timeFormat for the device. Depending on settings, it can be either
+ * 12h or 24h format.
+ */
+ @Composable
+ public fun timeFormat(): String {
+ val format = if (is24HourFormat()) TimeFormat24Hours else TimeFormat12Hours
+ return DateFormat.getBestDateTimePattern(Locale.getDefault(), format)
+ .replace("a", "").trim()
+ }
+
+ /**
+ * Creates a [TextStyle] with default parameters used for showing time
+ * on square screens. By default a copy of MaterialTheme.typography.labelSmall style is created.
+ *
+ * @param background The background color.
+ * @param color The main color.
+ * @param fontSize The font size.
+ */
+ @Composable
+ public fun timeTextStyle(
+ background: Color = Color.Unspecified,
+ color: Color = Color.Unspecified,
+ fontSize: TextUnit = TextUnit.Unspecified,
+ ) = MaterialTheme.typography.labelSmall +
+ TextStyle(color = color, background = background, fontSize = fontSize)
+
+ /**
+ * A default implementation of Separator shown between any text/composable and the time
+ * on non-round screens.
+ * @param modifier A default modifier for the separator.
+ * @param textStyle A [TextStyle] for the separator.
+ * @param contentPadding The spacing values between the container and the separator.
+ */
+ @Composable
+ public fun TextSeparator(
+ modifier: Modifier = Modifier,
+ textStyle: TextStyle = timeTextStyle(),
+ contentPadding: PaddingValues = PaddingValues(horizontal = 4.dp)
+ ) {
+ Text(
+ text = "·",
+ style = textStyle,
+ modifier = modifier.padding(contentPadding)
+ )
+ }
+
+ /**
+ * A default implementation of Separator shown between any text/composable and the time
+ * on round screens.
+ * @param curvedTextStyle A [CurvedTextStyle] for the separator.
+ * @param contentArcPadding [ArcPaddingValues] for the separator text.
+ */
+ public fun CurvedScope.CurvedTextSeparator(
+ curvedTextStyle: CurvedTextStyle? = null,
+ contentArcPadding: ArcPaddingValues = ArcPaddingValues(angular = 4.dp)
+ ) {
+ curvedText(
+ text = "·",
+ style = curvedTextStyle,
+ modifier = CurvedModifier.padding(contentArcPadding)
+ )
+ }
+
+ /**
+ * A default implementation of [TimeSource].
+ * Once the system time changes, it triggers an update of the [TimeSource.currentTime]
+ * which is formatted using [timeFormat] param.
+ *
+ * Android implementation:
+ * [DefaultTimeSource] for Android uses [android.text.format.DateFormat]
+ * [timeFormat] should follow the standard
+ * [Date and Time patterns](https://developer.android.com/reference/java/text/SimpleDateFormat#date-and-time-patterns)
+ * Examples:
+ * "h:mm a" - 12:08 PM
+ * "yyyy.MM.dd HH:mm:ss" - 2021.11.01 14:08:56
+ * More examples can be found [here](https://developer.android.com/reference/java/text/SimpleDateFormat#examples).
+ *
+ * Desktop implementation: TBD.
+ *
+ * @param timeFormat Date and time string pattern.
+ */
+ public fun timeSource(timeFormat: String): TimeSource = DefaultTimeSource(timeFormat)
+}
+
+public interface TimeSource {
+
+ /**
+ * A method responsible for returning updated time string.
+ * @return Formatted time string.
+ */
+ val currentTime: String
+ @Composable get
+}
+
+/**
+ * Implementation of [TimeTextScope] for round devices.
+ */
+internal class CurvedTimeTextScope(
+ val scope: CurvedScope,
+ val timeText: String,
+ val timeTextStyle: TextStyle,
+) : TimeTextScope() {
+ override fun text(text: String, style: TextStyle?) {
+ scope.curvedText(text, style = style?.let { CurvedTextStyle(it) })
+ }
+
+ override fun time() {
+ scope.curvedText(timeText, style = CurvedTextStyle(timeTextStyle))
+ }
+
+ override fun separator(style: TextStyle?) {
+ scope.CurvedTextSeparator(style?.let { CurvedTextStyle(it) })
+ }
+
+ override fun composable(content: @Composable () -> Unit) {
+ scope.curvedComposable { content() }
+ }
+}
+
+/**
+ * Implementation of [TimeTextScope] for non-round devices.
+ */
+internal class LinearTimeTextScope(
+ val timeText: String,
+ val timeTextStyle: TextStyle,
+) : TimeTextScope() {
+ val pending = mutableListOf<@Composable () -> Unit>()
+ override fun text(text: String, style: TextStyle?) {
+ pending.add {
+ if (style == null) Text(text) else Text(text, style = style)
+ }
+ }
+
+ override fun time() {
+ pending.add {
+ Text(timeText, style = timeTextStyle)
+ }
+ }
+
+ override fun separator(style: TextStyle?) {
+ pending.add {
+ if (style == null) TextSeparator() else TextSeparator(textStyle = style)
+ }
+ }
+
+ override fun composable(content: @Composable () -> Unit) {
+ pending.add { content() }
+ }
+
+ @Composable
+ fun Show() {
+ pending.fastForEach { it() }
+ }
+}
+
+internal class DefaultTimeSource(timeFormat: String) : TimeSource {
+ private val _timeFormat = timeFormat
+
+ override val currentTime: String
+ @Composable
+ get() = currentTime({ currentTimeMillis() }, _timeFormat).value
+}
+
+@Composable
+@VisibleForTesting
+internal fun currentTime(
+ time: () -> Long,
+ timeFormat: String
+): State<String> {
+
+ var calendar by remember { mutableStateOf(Calendar.getInstance()) }
+ var currentTime by remember { mutableLongStateOf(time()) }
+
+ val timeText = remember {
+ derivedStateOf { formatTime(calendar, currentTime, timeFormat) }
+ }
+
+ val context = LocalContext.current
+ val updatedTimeLambda by rememberUpdatedState(time)
+
+ DisposableEffect(context, updatedTimeLambda) {
+ val receiver = TimeBroadcastReceiver(
+ onTimeChanged = { currentTime = updatedTimeLambda() },
+ onTimeZoneChanged = { calendar = Calendar.getInstance() }
+ )
+ receiver.register(context)
+ onDispose {
+ receiver.unregister(context)
+ }
+ }
+ return timeText
+}
+
+/**
+ * An extension function, which converts [PaddingValues] into [ArcPaddingValues].
+ */
+private fun PaddingValues.toArcPadding() = object : ArcPaddingValues {
+ override fun calculateOuterPadding(radialDirection: CurvedDirection.Radial) =
+ calculateTopPadding()
+
+ override fun calculateInnerPadding(radialDirection: CurvedDirection.Radial) =
+ calculateBottomPadding()
+
+ override fun calculateAfterPadding(
+ layoutDirection: LayoutDirection,
+ angularDirection: CurvedDirection.Angular
+ ) = calculateRightPadding(layoutDirection)
+
+ override fun calculateBeforePadding(
+ layoutDirection: LayoutDirection,
+ angularDirection: CurvedDirection.Angular
+ ) = calculateLeftPadding(layoutDirection)
+}
+
+/**
+ * A [BroadcastReceiver] to receive time tick, time change, and time zone change events.
+ */
+private class TimeBroadcastReceiver(
+ val onTimeChanged: () -> Unit,
+ val onTimeZoneChanged: () -> Unit
+) : BroadcastReceiver() {
+ private var registered = false
+
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == Intent.ACTION_TIMEZONE_CHANGED) {
+ onTimeZoneChanged()
+ } else {
+ onTimeChanged()
+ }
+ }
+
+ fun register(context: Context) {
+ if (!registered) {
+ val filter = IntentFilter()
+ filter.addAction(Intent.ACTION_TIME_TICK)
+ filter.addAction(Intent.ACTION_TIME_CHANGED)
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
+ context.registerReceiver(this, filter)
+ registered = true
+ }
+ }
+
+ fun unregister(context: Context) {
+ if (registered) {
+ context.unregisterReceiver(this)
+ registered = false
+ }
+ }
+}
+
+private fun formatTime(
+ calendar: Calendar,
+ currentTime: Long,
+ timeFormat: String
+): String {
+ calendar.timeInMillis = currentTime
+ return DateFormat.format(timeFormat, calendar).toString()
+}
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
index a56f6baa..3436d0a 100644
--- 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
@@ -117,6 +117,7 @@
val scope = remember(enabled, checked) { ToggleControlScope(enabled, checked) }
toggleControl(scope)
},
+ selectionControl = null,
modifier = modifier
.defaultMinSize(minHeight = MIN_HEIGHT)
.height(IntrinsicSize.Min),
@@ -232,6 +233,7 @@
val scope = remember(enabled, checked) { ToggleControlScope(enabled, checked) }
toggleControl(scope)
},
+ selectionControl = null,
modifier = modifier
.defaultMinSize(minHeight = MIN_HEIGHT)
.height(IntrinsicSize.Min),
@@ -426,6 +428,9 @@
private val ChipHorizontalPadding = 14.dp
private val ChipVerticalPadding = 6.dp
+ /**
+ * The default content padding used by [ToggleButton]
+ */
val ContentPadding: PaddingValues = PaddingValues(
start = ChipHorizontalPadding,
top = ChipVerticalPadding,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt
new file mode 100644
index 0000000..85d5654
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/RadioButtonTokens.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+// VERSION: v0_41
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.wear.compose.material3.tokens
+internal object RadioButtonTokens {
+ val DisabledOpacity = 0.38f
+ val DisabledSelectedContainerColor = ColorSchemeKeyTokens.PrimaryContainer
+ val DisabledSelectedContentColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val DisabledSelectedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val DisabledSelectedSecondaryLabelColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val DisabledSelectedSecondaryLabelOpacity = 0.8f
+ val DisabledUnselectedContainerColor = ColorSchemeKeyTokens.Surface
+ val DisabledUnselectedContentColor = ColorSchemeKeyTokens.OnSurface
+ val DisabledUnselectedIconColor = ColorSchemeKeyTokens.Primary
+ val DisabledUnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
+ val LabelFont = TypographyKeyTokens.LabelMedium
+ val SecondaryLabelFont = TypographyKeyTokens.LabelSmall
+ val SelectedContainerColor = ColorSchemeKeyTokens.PrimaryContainer
+ val SelectedContentColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val SelectedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val SelectedSecondaryLabelColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val SelectedSecondaryLabelOpacity = 0.8f
+ val Shape = ShapeKeyTokens.CornerLarge
+ val UnselectedContainerColor = ColorSchemeKeyTokens.Surface
+ val UnselectedContentColor = ColorSchemeKeyTokens.OnSurface
+ val UnselectedIconColor = ColorSchemeKeyTokens.Primary
+ val UnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt
new file mode 100644
index 0000000..91b1135
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/SplitRadioButtonTokens.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+// VERSION: v0_41
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.wear.compose.material3.tokens
+internal object SplitRadioButtonTokens {
+ val DisabledOpacity = 0.38f
+ val DisabledSelectedContainerColor = ColorSchemeKeyTokens.PrimaryContainer
+ val DisabledSelectedContentColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val DisabledSelectedSecondaryLabelColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val DisabledSelectedSecondaryLabelOpacity = 0.8f
+ val DisabledSelectedSplitContainerColor = ColorSchemeKeyTokens.Primary
+ val DisabledSelectedSplitContainerOpacity = 0.15f
+ val DisabledUnselectedContainerColor = ColorSchemeKeyTokens.Surface
+ val DisabledUnselectedContentColor = ColorSchemeKeyTokens.OnSurface
+ val DisabledUnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
+ val DisabledUnselectedSplitContainerColor = ColorSchemeKeyTokens.SurfaceBright
+ val LabelFont = TypographyKeyTokens.LabelMedium
+ val SecondaryLabelFont = TypographyKeyTokens.LabelSmall
+ val SelectedContainerColor = ColorSchemeKeyTokens.PrimaryContainer
+ val SelectedContentColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val SelectedSecondaryLabelColor = ColorSchemeKeyTokens.OnPrimaryContainer
+ val SelectedSecondaryLabelOpacity = 0.8f
+ val SelectedSplitContainerColor = ColorSchemeKeyTokens.Primary
+ val SelectedSplitContainerOpacity = 0.15f
+ val Shape = ShapeKeyTokens.CornerLarge
+ val UnselectedContainerColor = ColorSchemeKeyTokens.Surface
+ val UnselectedContentColor = ColorSchemeKeyTokens.OnSurface
+ val UnselectedSecondaryLabelColor = ColorSchemeKeyTokens.OnSurfaceVariant
+ val UnselectedSplitContainerColor = ColorSchemeKeyTokens.SurfaceBright
+}
diff --git a/wear/compose/compose-navigation/build.gradle b/wear/compose/compose-navigation/build.gradle
index 3f37907..991acdb 100644
--- a/wear/compose/compose-navigation/build.gradle
+++ b/wear/compose/compose-navigation/build.gradle
@@ -31,9 +31,8 @@
}
dependencies {
-
- api(project(":compose:ui:ui"))
- api(project(":compose:runtime:runtime"))
+ api("androidx.compose.ui:ui:1.6.0")
+ api("androidx.compose.runtime:runtime:1.6.0")
api("androidx.navigation:navigation-runtime:2.6.0")
api(project(":wear:compose:compose-material"))
api("androidx.activity:activity-compose:1.7.0")
@@ -69,13 +68,12 @@
androidx {
name = "Android Wear Compose Navigation"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
inceptionYear = "2021"
description = "WearOS Compose Navigation Library. This library makes it easier for developers" +
"to write Jetpack Compose applications for Wearable devices by providing " +
"functionality to support navigation. " +
"It builds upon the Jetpack Compose libraries."
- targetsJavaConsumers = false
metalavaK2UastEnabled = true
samples(project(":wear:compose:compose-navigation-samples"))
}
diff --git a/wear/compose/compose-navigation/samples/build.gradle b/wear/compose/compose-navigation/samples/build.gradle
index 9ee6695..d015a62 100644
--- a/wear/compose/compose-navigation/samples/build.gradle
+++ b/wear/compose/compose-navigation/samples/build.gradle
@@ -31,19 +31,18 @@
}
dependencies {
-
implementation(libs.kotlinStdlib)
compileOnly(project(":annotation:annotation-sampled"))
- implementation(project(":activity:activity-compose"))
- implementation(project(":compose:animation:animation-graphics"))
- implementation(project(":compose:foundation:foundation"))
- implementation(project(":compose:foundation:foundation-layout"))
- implementation(project(":compose:runtime:runtime"))
- implementation(project(":compose:ui:ui"))
- implementation(project(":compose:ui:ui-text"))
- implementation(project(":compose:ui:ui-tooling"))
- implementation(project(":compose:ui:ui-tooling-preview"))
+ implementation("androidx.activity:activity-compose:1.8.2")
+ implementation("androidx.compose.animation:animation-graphics:1.6.0")
+ implementation("androidx.compose.foundation:foundation:1.6.0")
+ implementation("androidx.compose.foundation:foundation-layout:1.6.0")
+ implementation("androidx.compose.runtime:runtime:1.6.0")
+ implementation("androidx.compose.ui:ui:1.6.0")
+ implementation("androidx.compose.ui:ui-text:1.6.0")
+ implementation("androidx.compose.ui:ui-tooling:1.6.0")
+ implementation("androidx.compose.ui:ui-tooling-preview:1.6.0")
implementation(project(":wear:compose:compose-material"))
implementation(project(":wear:compose:compose-foundation"))
implementation(project(":wear:compose:compose-navigation"))
diff --git a/wear/compose/compose-ui-tooling/build.gradle b/wear/compose/compose-ui-tooling/build.gradle
index c7998e9e..ee2ece8 100644
--- a/wear/compose/compose-ui-tooling/build.gradle
+++ b/wear/compose/compose-ui-tooling/build.gradle
@@ -34,7 +34,7 @@
api("androidx.annotation:annotation:1.5.0")
api(project(":compose:ui:ui-tooling-preview"))
- implementation(libs.kotlinStdlibCommon)
+ implementation(libs.kotlinStdlib)
implementation("androidx.wear:wear-tooling-preview:1.0.0")
}
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 912149c..5784324 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -25,7 +25,6 @@
defaultConfig {
applicationId "androidx.wear.compose.integration.demos"
minSdk 25
- targetSdk 30
versionCode 19
versionName "1.19"
}
@@ -65,9 +64,9 @@
implementation(project(':wear:compose:integration-tests:demos:common'))
implementation(project(":wear:compose:compose-material3-integration-tests"))
- androidTestImplementation(project(":activity:activity"))
- androidTestImplementation(project(":activity:activity-compose"))
- androidTestImplementation(project(":activity:activity-ktx"))
+ androidTestImplementation("androidx.activity:activity:1.8.2")
+ androidTestImplementation("androidx.activity:activity-compose:1.8.2")
+ androidTestImplementation("androidx.activity:activity-ktx:1.8.2")
androidTestImplementation(project(":compose:runtime:runtime"))
androidTestImplementation(project(":compose:ui:ui-test-junit4"))
androidTestImplementation("androidx.lifecycle:lifecycle-runtime:2.7.0")
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/CardDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/CardDemo.kt
index 06c9aa3..b20cf49 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/CardDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/CardDemo.kt
@@ -18,8 +18,12 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
@@ -90,8 +94,12 @@
AppCard(
onClick = {},
appName = { Text("AppName") },
- appImage = { DemoImage(resourceId = R.drawable.ic_maps_icon,
- size = CardDefaults.AppImageSize) },
+ appImage = {
+ DemoImage(
+ resourceId = R.drawable.ic_maps_icon,
+ size = CardDefaults.AppImageSize
+ )
+ },
title = { Text("AppCard") },
time = { Text("now") },
) {
@@ -137,6 +145,29 @@
item {
TitleCard(
onClick = {},
+ title = {
+ DemoIcon(
+ resourceId = R.drawable.ic_accessibility_24px,
+ size = CardDefaults.AppImageSize
+ )
+ Spacer(Modifier.width(6.dp))
+ Text(
+ text = "Title text",
+ style = MaterialTheme.typography.caption1,
+ )
+ },
+ titleColor = AlternatePrimaryColor2
+ ) {
+ Spacer(Modifier.height(6.dp))
+ Text(
+ text = "TitleCard with title having icon and custom color",
+ style = MaterialTheme.typography.button,
+ )
+ }
+ }
+ item {
+ TitleCard(
+ onClick = {},
title = { Text("TitleCard With an ImageBackground") },
backgroundPainter = CardDefaults.imageWithScrimBackgroundPainter(
backgroundImagePainter = painterResource(id = R.drawable.backgroundimage1)
@@ -144,7 +175,13 @@
contentColor = MaterialTheme.colors.onSurface,
titleColor = MaterialTheme.colors.onSurface,
) {
- Column(modifier = Modifier.fillMaxWidth()) {
+ // Apply 24.dp padding in bottom for TitleCard with an ImageBackground.
+ // Already 12.dp padding exists. Ref - [CardDefaults.ContentPadding]
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(bottom = 12.dp),
+ ) {
Text("Text coloured to stand out on the image")
}
}
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
index d102123..45ff0fb 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
@@ -38,6 +38,7 @@
import androidx.wear.compose.material.samples.AlertWithChips
import androidx.wear.compose.material.samples.AnimateOptionChangePicker
import androidx.wear.compose.material.samples.AppCardWithIcon
+import androidx.wear.compose.material.samples.AppCardWithImage
import androidx.wear.compose.material.samples.AutoCenteringPickerGroup
import androidx.wear.compose.material.samples.ButtonWithIcon
import androidx.wear.compose.material.samples.ButtonWithText
@@ -76,6 +77,7 @@
import androidx.wear.compose.material.samples.SimpleScalingLazyColumnWithSnap
import androidx.wear.compose.material.samples.SimpleSwipeToDismissBox
import androidx.wear.compose.material.samples.SplitToggleChipWithCheckbox
+import androidx.wear.compose.material.samples.SplitToggleChipWithRadioButton
import androidx.wear.compose.material.samples.StatefulSwipeToDismissBox
import androidx.wear.compose.material.samples.StepperSample
import androidx.wear.compose.material.samples.StepperWithCustomSemanticsSample
@@ -86,7 +88,7 @@
import androidx.wear.compose.material.samples.TimeTextWithFullDateAndTimeFormat
import androidx.wear.compose.material.samples.TimeTextWithStatus
import androidx.wear.compose.material.samples.TitleCardStandard
-import androidx.wear.compose.material.samples.TitleCardWithImage
+import androidx.wear.compose.material.samples.TitleCardWithImageBackground
import androidx.wear.compose.material.samples.ToggleButtonWithIcon
import androidx.wear.compose.material.samples.ToggleChipWithRadioButton
import androidx.wear.compose.material.samples.ToggleChipWithSwitch
@@ -488,7 +490,12 @@
Centralize(Modifier.padding(horizontal = 10.dp)) {
SplitToggleChipWithCheckbox()
}
- }
+ },
+ ComposableDemo("SplitToggleChip With RadioButton") {
+ Centralize(Modifier.padding(horizontal = 10.dp)) {
+ SplitToggleChipWithRadioButton()
+ }
+ },
)
),
DemoCategory(
@@ -516,14 +523,19 @@
AppCardWithIcon()
}
},
+ ComposableDemo("AppCard With Image") {
+ Centralize(Modifier.padding(horizontal = 10.dp)) {
+ AppCardWithImage()
+ }
+ },
ComposableDemo("TitleCard") {
Centralize(Modifier.padding(horizontal = 10.dp)) {
TitleCardStandard()
}
},
- ComposableDemo("TitleCard With Image") {
+ ComposableDemo("TitleCard With Image Background") {
Centralize(Modifier.padding(horizontal = 10.dp)) {
- TitleCardWithImage()
+ TitleCardWithImageBackground()
}
},
)
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt
index bc4adf7..6bf36b7 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt
@@ -61,11 +61,11 @@
var checkBoxIconCustomColorChecked by remember { mutableStateOf(true) }
var switchIconChecked by remember { mutableStateOf(true) }
var switchIconCustomColorChecked by remember { mutableStateOf(true) }
- var radioIconChecked by remember { mutableStateOf(true) }
- var radioIconWithSecondaryChecked by remember { mutableStateOf(true) }
+ var radioIconSelected by remember { mutableStateOf(true) }
+ var radioIconWithSecondarySelected by remember { mutableStateOf(true) }
var splitWithCheckboxIconChecked by remember { mutableStateOf(true) }
var splitWithSwitchIconChecked by remember { mutableStateOf(true) }
- var splitWithRadioIconChecked by remember { mutableStateOf(true) }
+ var splitWithRadioIconSelected by remember { mutableStateOf(true) }
var switchIconWithSecondaryChecked by remember { mutableStateOf(true) }
var switchIconWithIconChecked by remember { mutableStateOf(true) }
@@ -166,18 +166,13 @@
}
item {
CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+ // Call the selectionControl variation, with default selectionControl = RadioButton
ToggleChip(
label = {
Text("Radio", maxLines = 2, overflow = TextOverflow.Ellipsis)
},
- checked = radioIconChecked,
- toggleControl = {
- RadioButton(
- selected = radioIconChecked,
- enabled = enabled,
- )
- },
- onCheckedChange = { radioIconChecked = it },
+ selected = radioIconSelected,
+ onSelect = { radioIconSelected = it },
enabled = enabled,
)
}
@@ -195,10 +190,10 @@
secondaryLabel = {
Text("CustomColor", maxLines = 1, overflow = TextOverflow.Ellipsis)
},
- checked = radioIconWithSecondaryChecked,
- toggleControl = {
+ selected = radioIconWithSecondarySelected,
+ selectionControl = {
RadioButton(
- selected = radioIconWithSecondaryChecked,
+ selected = radioIconWithSecondarySelected,
enabled = enabled,
colors = RadioButtonDefaults.colors(
selectedRingColor = MaterialTheme.colors.primary,
@@ -208,7 +203,7 @@
),
)
},
- onCheckedChange = { radioIconWithSecondaryChecked = it },
+ onSelect = { radioIconWithSecondarySelected = it },
enabled = enabled,
colors = ToggleChipDefaults.toggleChipColors(
checkedToggleControlColor = AlternatePrimaryColor3,
@@ -375,16 +370,11 @@
}
item {
CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+ // Call the selectionControl variation, with default selectionControl = RadioButton
SplitToggleChip(
label = { Text("Split with Radio") },
- checked = splitWithRadioIconChecked,
- toggleControl = {
- RadioButton(
- selected = splitWithRadioIconChecked,
- enabled = enabled,
- )
- },
- onCheckedChange = { splitWithRadioIconChecked = it },
+ selected = splitWithRadioIconSelected,
+ onSelect = { splitWithRadioIconSelected = it },
onClick = {
Toast.makeText(
applicationContext, "Text was clicked",
diff --git a/wear/compose/integration-tests/macrobenchmark-target/build.gradle b/wear/compose/integration-tests/macrobenchmark-target/build.gradle
index 5ed6e31..b6d82c7 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/build.gradle
+++ b/wear/compose/integration-tests/macrobenchmark-target/build.gradle
@@ -35,7 +35,6 @@
}
dependencies {
-
implementation(libs.kotlinStdlib)
implementation(project(":compose:foundation:foundation-layout"))
implementation(project(":compose:ui:ui"))
@@ -43,7 +42,6 @@
implementation(project(":compose:foundation:foundation-layout"))
implementation(project(":compose:runtime:runtime"))
implementation(project(":compose:ui:ui-tooling"))
- implementation(project(":compose:material:material-icons-core"))
implementation(project(":activity:activity-compose"))
implementation(project(":profileinstaller:profileinstaller"))
implementation project(path: ':wear:compose:compose-foundation')
diff --git a/wear/protolayout/protolayout-material/api/current.txt b/wear/protolayout/protolayout-material/api/current.txt
index bd5e676..38c33fb 100644
--- a/wear/protolayout/protolayout-material/api/current.txt
+++ b/wear/protolayout/protolayout-material/api/current.txt
@@ -233,51 +233,35 @@
package androidx.wear.protolayout.material.layouts {
- @Deprecated public class EdgeContentLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
- method @Deprecated public static androidx.wear.protolayout.material.layouts.EdgeContentLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method @Deprecated public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getContent();
- method @Deprecated public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getEdgeContent();
- method @Deprecated public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelTextContent();
- method @Deprecated public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelTextContent();
- method @Deprecated public boolean isEdgeContentBehindAllOtherContent();
- }
-
- @Deprecated public static final class EdgeContentLayout.Builder {
- ctor @Deprecated public EdgeContentLayout.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout build();
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContentBehindAllOtherContent(boolean);
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setPrimaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setSecondaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- }
-
- public final class EdgeContentLayout2 implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
- method public static androidx.wear.protolayout.material.layouts.EdgeContentLayout2? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ public class EdgeContentLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.EdgeContentLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getContent();
method @Dimension(unit=androidx.annotation.Dimension.DP) public float getContentAndSecondaryLabelSpacing();
method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getEdgeContent();
method public float getEdgeContentThickness();
- method public androidx.wear.protolayout.expression.Fingerprint? getFingerprint();
- method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelContent();
- method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelTextContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelTextContent();
+ method public boolean isEdgeContentBehindAllOtherContent();
+ method public boolean isResponsiveContentInsetEnabled();
}
- public static final class EdgeContentLayout2.Builder {
- ctor public EdgeContentLayout2.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2 build();
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setContentAndSecondaryLabelSpacing(androidx.wear.protolayout.DimensionBuilders.DpProp);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setEdgeContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setEdgeContentThickness(@Dimension(unit=androidx.annotation.Dimension.DP) float);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setPrimaryLabelContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setSecondaryLabelContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ public static final class EdgeContentLayout.Builder {
+ ctor public EdgeContentLayout.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout build();
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setContentAndSecondaryLabelSpacing(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContentBehindAllOtherContent(boolean);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContentThickness(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setPrimaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setResponsiveContentInsetEnabled(boolean);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setSecondaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
}
public class LayoutDefaults {
field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_VERTICAL_SPACER_HEIGHT;
- field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EDGE_CONTENT_LAYOUT2_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
- field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EDGE_CONTENT_LAYOUT2_LARGE_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EDGE_CONTENT_LAYOUT_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EDGE_CONTENT_LAYOUT_LARGE_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
field public static final float EDGE_CONTENT_LAYOUT_PADDING_ABOVE_MAIN_CONTENT_DP = 6.0f;
field public static final float EDGE_CONTENT_LAYOUT_PADDING_BELOW_MAIN_CONTENT_DP = 8.0f;
field @Deprecated public static final int MULTI_BUTTON_MAX_NUMBER = 7; // 0x7
diff --git a/wear/protolayout/protolayout-material/api/restricted_current.txt b/wear/protolayout/protolayout-material/api/restricted_current.txt
index bd5e676..38c33fb 100644
--- a/wear/protolayout/protolayout-material/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material/api/restricted_current.txt
@@ -233,51 +233,35 @@
package androidx.wear.protolayout.material.layouts {
- @Deprecated public class EdgeContentLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
- method @Deprecated public static androidx.wear.protolayout.material.layouts.EdgeContentLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method @Deprecated public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getContent();
- method @Deprecated public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getEdgeContent();
- method @Deprecated public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelTextContent();
- method @Deprecated public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelTextContent();
- method @Deprecated public boolean isEdgeContentBehindAllOtherContent();
- }
-
- @Deprecated public static final class EdgeContentLayout.Builder {
- ctor @Deprecated public EdgeContentLayout.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout build();
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContentBehindAllOtherContent(boolean);
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setPrimaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method @Deprecated public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setSecondaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- }
-
- public final class EdgeContentLayout2 implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
- method public static androidx.wear.protolayout.material.layouts.EdgeContentLayout2? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ public class EdgeContentLayout implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
+ method public static androidx.wear.protolayout.material.layouts.EdgeContentLayout? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getContent();
method @Dimension(unit=androidx.annotation.Dimension.DP) public float getContentAndSecondaryLabelSpacing();
method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getEdgeContent();
method public float getEdgeContentThickness();
- method public androidx.wear.protolayout.expression.Fingerprint? getFingerprint();
- method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelContent();
- method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getPrimaryLabelTextContent();
+ method public androidx.wear.protolayout.LayoutElementBuilders.LayoutElement? getSecondaryLabelTextContent();
+ method public boolean isEdgeContentBehindAllOtherContent();
+ method public boolean isResponsiveContentInsetEnabled();
}
- public static final class EdgeContentLayout2.Builder {
- ctor public EdgeContentLayout2.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2 build();
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setContentAndSecondaryLabelSpacing(androidx.wear.protolayout.DimensionBuilders.DpProp);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setEdgeContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setEdgeContentThickness(@Dimension(unit=androidx.annotation.Dimension.DP) float);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setPrimaryLabelContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
- method public androidx.wear.protolayout.material.layouts.EdgeContentLayout2.Builder setSecondaryLabelContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ public static final class EdgeContentLayout.Builder {
+ ctor public EdgeContentLayout.Builder(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout build();
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setContentAndSecondaryLabelSpacing(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContentBehindAllOtherContent(boolean);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setEdgeContentThickness(@Dimension(unit=androidx.annotation.Dimension.DP) float);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setPrimaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setResponsiveContentInsetEnabled(boolean);
+ method public androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder setSecondaryLabelTextContent(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
}
public class LayoutDefaults {
field public static final androidx.wear.protolayout.DimensionBuilders.DpProp DEFAULT_VERTICAL_SPACER_HEIGHT;
- field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EDGE_CONTENT_LAYOUT2_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
- field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EDGE_CONTENT_LAYOUT2_LARGE_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EDGE_CONTENT_LAYOUT_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
+ field public static final androidx.wear.protolayout.DimensionBuilders.DpProp EDGE_CONTENT_LAYOUT_LARGE_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
field public static final float EDGE_CONTENT_LAYOUT_PADDING_ABOVE_MAIN_CONTENT_DP = 6.0f;
field public static final float EDGE_CONTENT_LAYOUT_PADDING_BELOW_MAIN_CONTENT_DP = 8.0f;
field @Deprecated public static final int MULTI_BUTTON_MAX_NUMBER = 7; // 0x7
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java
index f726ebd..78cb89a 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java
@@ -69,7 +69,6 @@
* different for different user font sizes. Note that some of the golden will have the same name
* as it should point on the same size independent image.
*/
- @SuppressWarnings("deprecation")
@NonNull
static Map<String, Layout> generateTestCases(
@NonNull Context context,
@@ -524,15 +523,16 @@
.build();
testCases.put(
"edgecontentlayout2_all_present_golden" + goldenSuffix,
- new EdgeContentLayout2.Builder(deviceParameters)
+ new EdgeContentLayout.Builder(deviceParameters)
+ .setResponsiveContentInsetEnabled(true)
.setEdgeContent(progressIndicatorBuilder.build())
- .setPrimaryLabelContent(
+ .setPrimaryLabelTextContent(
new Text.Builder(context, "Primary label")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.PRIMARY))
.build())
.setContent(mainContentText)
- .setSecondaryLabelContent(
+ .setSecondaryLabelTextContent(
new Text.Builder(context, "Secondary label")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.ON_SURFACE))
@@ -540,7 +540,8 @@
.build());
testCases.put(
"edgecontentlayout2_all_present_other_edgecontent_golden" + goldenSuffix,
- new EdgeContentLayout2.Builder(deviceParameters)
+ new EdgeContentLayout.Builder(deviceParameters)
+ .setResponsiveContentInsetEnabled(true)
.setEdgeContent(
new Arc.Builder()
.addContent(
@@ -554,13 +555,13 @@
.build())
.build())
.setEdgeContentThickness(12)
- .setPrimaryLabelContent(
+ .setPrimaryLabelTextContent(
new Text.Builder(context, "Primary label")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.PRIMARY))
.build())
.setContent(mainContentText)
- .setSecondaryLabelContent(
+ .setSecondaryLabelTextContent(
new Text.Builder(context, "Secondary label")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.ON_SURFACE))
@@ -568,12 +569,14 @@
.build());
testCases.put(
"edgecontentlayout2_only_edgecontent_golden" + NORMAL_SCALE_SUFFIX,
- new EdgeContentLayout2.Builder(deviceParameters)
+ new EdgeContentLayout.Builder(deviceParameters)
+ .setResponsiveContentInsetEnabled(true)
.setEdgeContent(progressIndicatorBuilder.build())
.build());
testCases.put(
"edgecontentlayout2_all_present_bigger_thickness_golden" + goldenSuffix,
- new EdgeContentLayout2.Builder(deviceParameters)
+ new EdgeContentLayout.Builder(deviceParameters)
+ .setResponsiveContentInsetEnabled(true)
.setEdgeContent(
new CircularProgressIndicator.Builder()
.setProgress(0.3f)
@@ -581,14 +584,14 @@
.build())
.setEdgeContentThickness(16
+ ProgressIndicatorDefaults.DEFAULT_PADDING.getValue())
- .setPrimaryLabelContent(
+ .setPrimaryLabelTextContent(
new Text.Builder(context, "Primary label that overflows")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.PRIMARY))
.setOverflow(LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE)
.build())
.setContent(mainContentText)
- .setSecondaryLabelContent(
+ .setSecondaryLabelTextContent(
new Text.Builder(context, "Secondary label")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.ON_SURFACE))
@@ -597,14 +600,15 @@
testCases.put(
"edgecontentlayout2_all_present_smaller_thickness_custom_spacer_golden"
+ goldenSuffix,
- new EdgeContentLayout2.Builder(deviceParameters)
+ new EdgeContentLayout.Builder(deviceParameters)
+ .setResponsiveContentInsetEnabled(true)
.setEdgeContent(
new CircularProgressIndicator.Builder()
.setProgress(0.3f)
.setStrokeWidth(2).build())
.setEdgeContentThickness(2
+ ProgressIndicatorDefaults.DEFAULT_PADDING.getValue())
- .setPrimaryLabelContent(
+ .setPrimaryLabelTextContent(
new Text.Builder(context, "Primary label that overflows")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.PRIMARY))
@@ -612,7 +616,7 @@
.build())
.setContent(mainContentText)
.setContentAndSecondaryLabelSpacing(dp(11))
- .setSecondaryLabelContent(
+ .setSecondaryLabelTextContent(
new Text.Builder(context, "Secondary label")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.ON_SURFACE))
@@ -620,9 +624,10 @@
.build());
testCases.put(
"edgecontentlayout2_all_present_overflows_golden" + goldenSuffix,
- new EdgeContentLayout2.Builder(deviceParameters)
+ new EdgeContentLayout.Builder(deviceParameters)
+ .setResponsiveContentInsetEnabled(true)
.setEdgeContent(progressIndicatorBuilder.build())
- .setPrimaryLabelContent(
+ .setPrimaryLabelTextContent(
new Text.Builder(context, "Primary label that overflows")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.PRIMARY))
@@ -634,7 +639,7 @@
.setTypography(Typography.TYPOGRAPHY_DISPLAY2)
.setOverflow(LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE)
.build())
- .setSecondaryLabelContent(
+ .setSecondaryLabelTextContent(
new Text.Builder(context, "Secondary label that overflows")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.ON_SURFACE))
@@ -643,10 +648,11 @@
.build());
testCases.put(
"edgecontentlayout2_no_primarylabel_golden" + goldenSuffix,
- new EdgeContentLayout2.Builder(deviceParameters)
+ new EdgeContentLayout.Builder(deviceParameters)
+ .setResponsiveContentInsetEnabled(true)
.setEdgeContent(progressIndicatorBuilder.build())
.setContent(mainContentText)
- .setSecondaryLabelContent(
+ .setSecondaryLabelTextContent(
new Text.Builder(context, "Secondary label")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.ON_SURFACE))
@@ -654,9 +660,10 @@
.build());
testCases.put(
"edgecontentlayout2_no_secondarylabel_present_golden" + goldenSuffix,
- new EdgeContentLayout2.Builder(deviceParameters)
+ new EdgeContentLayout.Builder(deviceParameters)
+ .setResponsiveContentInsetEnabled(true)
.setEdgeContent(progressIndicatorBuilder.build())
- .setPrimaryLabelContent(
+ .setPrimaryLabelTextContent(
new Text.Builder(context, "Primary label")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.PRIMARY))
@@ -665,15 +672,16 @@
.build());
testCases.put(
"edgecontentlayout2_all_double_secondarylabel_present_golden" + goldenSuffix,
- new EdgeContentLayout2.Builder(deviceParameters)
+ new EdgeContentLayout.Builder(deviceParameters)
+ .setResponsiveContentInsetEnabled(true)
.setEdgeContent(progressIndicatorBuilder.build())
- .setPrimaryLabelContent(
+ .setPrimaryLabelTextContent(
new Text.Builder(context, "Primary label")
.setTypography(Typography.TYPOGRAPHY_CAPTION1)
.setColor(argb(Colors.PRIMARY))
.build())
.setContent(mainContentText)
- .setSecondaryLabelContent(
+ .setSecondaryLabelTextContent(
new Column.Builder()
.addContent(
new Text.Builder(context, "Data point 1")
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CircularProgressIndicator.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CircularProgressIndicator.java
index f0865e4..b9d4517 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CircularProgressIndicator.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CircularProgressIndicator.java
@@ -238,7 +238,7 @@
* additional margin around it by setting it to {@code false}.
*
* <p>Otherwise, if this indicator is used as a full screen one or in {@link
- * androidx.wear.protolayout.material.layouts.EdgeContentLayout2}, it's strongly recommended
+ * androidx.wear.protolayout.material.layouts.EdgeContentLayout}, it's strongly recommended
* to set this to {@code true}.
*
* <p>If not set, defaults to true.
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout.java
index 4fcbdf0..c200c36 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout.java
@@ -16,19 +16,29 @@
package androidx.wear.protolayout.material.layouts;
+import static androidx.annotation.Dimension.DP;
import static androidx.wear.protolayout.DimensionBuilders.dp;
import static androidx.wear.protolayout.DimensionBuilders.expand;
+import static androidx.wear.protolayout.DimensionBuilders.wrap;
import static androidx.wear.protolayout.material.ProgressIndicatorDefaults.DEFAULT_PADDING;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_ROUND_DP;
import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_SQUARE_DP;
import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_PADDING_ABOVE_MAIN_CONTENT_DP;
import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_PADDING_BELOW_MAIN_CONTENT_DP;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_RESPONSIVE_MARGIN_HORIZONTAL_PERCENT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_RESPONSIVE_MARGIN_VERTICAL_PERCENT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_RESPONSIVE_OUTER_MARGIN_DP;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT_RESPONSIVE_PRIMARY_LABEL_SPACING_DP;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.LAYOUTS_LABEL_PADDING_PERCENT;
+import static androidx.wear.protolayout.material.layouts.LayoutDefaults.insetElementWithPadding;
import static androidx.wear.protolayout.materialcore.Helper.checkNotNull;
import static androidx.wear.protolayout.materialcore.Helper.checkTag;
import static androidx.wear.protolayout.materialcore.Helper.getMetadataTagBytes;
import static androidx.wear.protolayout.materialcore.Helper.getTagBytes;
import static androidx.wear.protolayout.materialcore.Helper.isRoundDevice;
+import androidx.annotation.Dimension;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -36,6 +46,7 @@
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.DimensionBuilders.SpacerDimension;
import androidx.wear.protolayout.LayoutElementBuilders;
import androidx.wear.protolayout.LayoutElementBuilders.Box;
import androidx.wear.protolayout.LayoutElementBuilders.Column;
@@ -57,7 +68,7 @@
* ProtoLayout layout that represents the suggested layout style for Material ProtoLayout, which has
* content around the edge of the screen (e.g. a ProgressIndicator) and the given content inside of
* it with the recommended margin and padding applied. Optional primary or secondary label can be
- * added above and below the main content, respectively.
+ * added above and below the additional content, respectively.
*
* <p>When accessing the contents of a container for testing, note that this element can't be simply
* casted back to the original type, i.e.:
@@ -78,12 +89,8 @@
* EdgeContentLayout myEcl =
* EdgeContentLayout.fromLayoutElement(box.getContents().get(0));
* }</pre>
- *
- * @deprecated Please use new version of this layout, {@link EdgeContentLayout2} that ensures that
- * tiles in the carousel are consistent and behave correctly on different screen sizes.
*/
// TODO(b/274916652): Link visuals once they are available.
-@Deprecated
public class EdgeContentLayout implements LayoutElement {
/**
* Prefix tool tag for Metadata in Modifiers, so we know that Box is actually a
@@ -113,23 +120,29 @@
* Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
* the primary label is present or not.
*/
- static final int PRIMARY_LABEL_PRESENT = 0x2;
+ static final int PRIMARY_LABEL_PRESENT = 1 << 1;
/**
* Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
* the secondary label is present or not.
*/
- static final int SECONDARY_LABEL_PRESENT = 0x4;
+ static final int SECONDARY_LABEL_PRESENT = 1 << 2;
/**
* Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
- * the main content is present or not.
+ * the additional content is present or not.
*/
- static final int CONTENT_PRESENT = 0x8;
+ static final int CONTENT_PRESENT = 1 << 3;
/**
* Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
- * the edge content is added before the main content (0) or after it (1).
+ * the edge content is added before the additional content (0) or after it (1).
*/
- static final int EDGE_CONTENT_POSITION = 0x10;
+ static final int EDGE_CONTENT_POSITION = 1 << 4;
+
+ /**
+ * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
+ * the responsive content inset is used or not.
+ */
+ static final int CONTENT_INSET_USED = 1 << 5;
@RestrictTo(Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@@ -140,30 +153,15 @@
PRIMARY_LABEL_PRESENT,
SECONDARY_LABEL_PRESENT,
CONTENT_PRESENT,
- EDGE_CONTENT_POSITION
+ EDGE_CONTENT_POSITION,
+ CONTENT_INSET_USED
})
@interface ContentBits {}
@NonNull private final Box mImpl;
- // This contains optional labels, spacers and main content.
- @NonNull private final List<LayoutElement> mInnerColumn;
-
- // This contains edge content;
- @Nullable private final LayoutElement mEdgeContent;
- private final boolean mIsEdgeContentBehind;
-
EdgeContentLayout(@NonNull Box layoutElement) {
this.mImpl = layoutElement;
- // This contains inner columns and edge content.
- List<LayoutElement> contents = mImpl.getContents();
- int edgeContentIndex = (getMetadataTag()[FLAG_INDEX] & EDGE_CONTENT_POSITION) == 0 ? 0 : 1;
- int contentIndex = 1 - edgeContentIndex;
- this.mInnerColumn =
- ((Column) ((Box) contents.get(contentIndex)).getContents().get(0)).getContents();
- this.mEdgeContent =
- areElementsPresent(EDGE_CONTENT_PRESENT) ? contents.get(edgeContentIndex) : null;
- mIsEdgeContentBehind = edgeContentIndex == 0;
}
/** Builder class for {@link EdgeContentLayout}. */
@@ -175,17 +173,84 @@
@Nullable private LayoutElement mContent = null;
private byte mMetadataContentByte = 0;
private boolean mIsEdgeContentBehind = false;
+ private boolean mIsResponsiveInsetEnabled = false;
+ @Nullable private Float mEdgeContentThickness = null;
+ @NonNull
+ private DpProp mVerticalSpacerHeight =
+ EDGE_CONTENT_LAYOUT_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
/**
- * Creates a builder for the {@link EdgeContentLayout}t. Custom content inside of it can
+ * Creates a builder for the {@link EdgeContentLayout}. Custom content inside of it can
* later be set with ({@link #setContent}.
+ *
+ * <p>For optimal layouts across different screen sizes and better alignment with UX
+ * guidelines, it is highly recommended to call {@link #setResponsiveContentInsetEnabled}.
*/
public Builder(@NonNull DeviceParameters deviceParameters) {
this.mDeviceParameters = deviceParameters;
}
/**
+ * Changes this {@link EdgeContentLayout} to better follow guidelines for type of layout
+ * that has content around the edge.
+ *
+ * <p>These updates include:
+ * 1. Using responsive insets for its content primary and secondary label by adding some
+ * additional space on the sides of these elements to avoid content going off the screen
+ * edge.
+ * 2. Changing layout padding to responsive to better follow different screen sizes.
+ * 3. Positioning primary label at a fixed place on top of the screen rather than
+ * following additional content.
+ *
+ * <p>It is highly recommended to call this method with {@code true} when using this layout
+ * to optimize it for different screen sizes.
+ *
+ * @throws IllegalStateException if this and
+ * {@link #setEdgeContentBehindAllOtherContent(boolean)} are used together.
+ */
+ @NonNull
+ public Builder setResponsiveContentInsetEnabled(boolean enabled) {
+ if (mIsEdgeContentBehind) {
+ // We don't allow mixing this method with responsiveness.
+ throw new IllegalStateException(
+ "Setters setResponsiveContentInsetEnabled and "
+ + "setEdgeContentBehindAllOtherContent can't be used together. "
+ + "Please use only setResponsiveContentInsetEnabled, which will "
+ + "always place the edge content behind other content.");
+ }
+
+ this.mIsResponsiveInsetEnabled = enabled;
+ if (enabled) {
+ mMetadataContentByte = (byte) (mMetadataContentByte | CONTENT_INSET_USED);
+ } else {
+ mMetadataContentByte = (byte) (mMetadataContentByte & ~CONTENT_INSET_USED);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the thickness of the hollow edge content so that other content is correctly placed.
+ * In other words, sets the space that should be reserved exclusively for the edge
+ * content and not be overdrawn by other inner content.
+ *
+ * <p>For example, for {@link CircularProgressIndicator} or {@link
+ * androidx.wear.protolayout.LayoutElementBuilders.ArcLine} elements, this should be equal
+ * to their stroke width/thickness.
+ *
+ * <p>Note that, calling this method when responsiveness is not set with
+ * {@link #setResponsiveContentInsetEnabled}, will be ignored.
+ */
+ @NonNull
+ public Builder setEdgeContentThickness(@Dimension(unit = DP) float thickness) {
+ this.mEdgeContentThickness = thickness;
+ return this;
+ }
+
+ /**
* Sets the content to be around the edges. This can be {@link CircularProgressIndicator}.
+ *
+ * <p>If this content is something other that {@link CircularProgressIndicator}, please add
+ * its thickness with {@link #setEdgeContentThickness} for best results.
*/
@NonNull
public Builder setEdgeContent(@NonNull LayoutElement edgeContent) {
@@ -194,7 +259,17 @@
return this;
}
- /** Sets the content in the primary label slot which will be above the main content. */
+ /**
+ * Sets the content in the primary label slot.
+ *
+ * <p>Depending on whether {@link #setResponsiveContentInsetEnabled} is set to true or
+ * not, this label will be placed as following:
+ * - If responsive behaviour is set, label will be above the additional content, on a fixed
+ * place to ensure Tiles consistency with other layouts. Additionally, the label will
+ * also have an inset to prevent it from going off the screen.
+ * - If responsive behaviour is not set or called, label will be above the additional
+ * content, centered in the remaining space.
+ */
@NonNull
public Builder setPrimaryLabelTextContent(@NonNull LayoutElement primaryLabelText) {
this.mPrimaryLabelText = primaryLabelText;
@@ -203,8 +278,11 @@
}
/**
- * Sets the content in the secondary label slot which will be below the main content. It is
- * highly recommended to have primary label set when having secondary label.
+ * Sets the content in the secondary label slot which will be below the additional content.
+ * It is highly recommended to have primary label set when having secondary label.
+ *
+ * <p>Note that when {@link #setResponsiveContentInsetEnabled} is set to {@code true}, the
+ * label will also have an inset to prevent it from going off the screen.
*/
@NonNull
public Builder setSecondaryLabelTextContent(@NonNull LayoutElement secondaryLabelText) {
@@ -222,12 +300,42 @@
}
/**
+ * Sets the space size between the additional content and secondary label if there is any.
+ * If one of those is not present, spacer is not used. If not set,
+ * {@link LayoutDefaults#EDGE_CONTENT_LAYOUT_CONTENT_AND_SECONDARY_LABEL_SPACING_DP} will
+ * be used.
+ *
+ * <p>Note that, this method should be used together with
+ * {@link #setResponsiveContentInsetEnabled}, otherwise it will be ignored.
+ */
+ @NonNull
+ public Builder setContentAndSecondaryLabelSpacing(@NonNull DpProp height) {
+ this.mVerticalSpacerHeight = height;
+ return this;
+ }
+
+ /**
* Sets whether the edge content passed in with {@link #setEdgeContent} should be positioned
* behind all other content in this layout or above it. If not set, defaults to {@code
* false}, meaning that the edge content will be placed above all other content.
+ *
+ * <p>Note that, if {@link #setResponsiveContentInsetEnabled} is set to {@code true}, edge
+ * content will always go behind all other content and this method call will be ignored.
+ *
+ * @throws IllegalStateException if this and {@link #setResponsiveContentInsetEnabled}
+ * are used together.
*/
@NonNull
public Builder setEdgeContentBehindAllOtherContent(boolean isBehind) {
+ if (mIsResponsiveInsetEnabled) {
+ // We don't allow mixing this method with responsiveness.
+ throw new IllegalStateException(
+ "Setters setResponsiveContentInsetEnabled and "
+ + "setEdgeContentBehindAllOtherContent can't be used together. "
+ + "Please use only setResponsiveContentInsetEnabled, which will "
+ + "always place the edge content behind other content.");
+ }
+
this.mIsEdgeContentBehind = isBehind;
return this;
}
@@ -236,6 +344,150 @@
@NonNull
@Override
public EdgeContentLayout build() {
+ if (mIsResponsiveInsetEnabled && mIsEdgeContentBehind) {
+ // We don't allow mixing this method with responsiveness.
+ throw new IllegalStateException(
+ "Setters setResponsiveContentInsetEnabled and "
+ + "setEdgeContentBehindAllOtherContent can't be used together. "
+ + "Please use only setResponsiveContentInsetEnabled, which will "
+ + "always place the edge content behind other content.");
+ }
+
+ return mIsResponsiveInsetEnabled ? responsiveLayoutBuild() : legacyLayoutBuild();
+ }
+
+ @NonNull
+ private EdgeContentLayout responsiveLayoutBuild() {
+ // Calculate what is the inset box max size, i.e., the size that all content can occupy
+ // without the edge content.
+ // Use provided thickness if set. Otherwise, see if we can get it from
+ // CircularProgressIndicator.
+ float edgeContentSize = getEdgeContentSize();
+
+ DpProp contentHeight = dp(
+ mDeviceParameters.getScreenWidthDp() - edgeContentSize);
+ DpProp contentWidth = dp(
+ mDeviceParameters.getScreenHeightDp() - edgeContentSize);
+
+ // TODO(b/321681652): Confirm with the UX if we can put 6dp as outer margin so it
+ // matches CPI.
+ float outerMargin =
+ mEdgeContent instanceof CircularProgressIndicator
+ && ((CircularProgressIndicator) mEdgeContent).isOuterMarginApplied()
+ ? 0 // CPI has this margin already.
+ : EDGE_CONTENT_LAYOUT_RESPONSIVE_OUTER_MARGIN_DP;
+
+ // Horizontal and vertical padding added to the inner content.
+ float horizontalPaddingDp =
+ EDGE_CONTENT_LAYOUT_RESPONSIVE_MARGIN_HORIZONTAL_PERCENT
+ * mDeviceParameters.getScreenWidthDp();
+ float verticalPaddingDp =
+ EDGE_CONTENT_LAYOUT_RESPONSIVE_MARGIN_VERTICAL_PERCENT
+ * mDeviceParameters.getScreenWidthDp();
+
+ // Padding to restrict labels from going off the screen.
+ float labelHorizontalPaddingDp =
+ mDeviceParameters.getScreenWidthDp() * LAYOUTS_LABEL_PADDING_PERCENT;
+
+ Modifiers modifiers =
+ new Modifiers.Builder()
+ .setPadding(
+ new Padding.Builder()
+ .setRtlAware(true)
+ .setStart(dp(horizontalPaddingDp))
+ .setEnd(dp(horizontalPaddingDp))
+ .setTop(dp(verticalPaddingDp))
+ .setBottom(dp(verticalPaddingDp))
+ .build())
+ .build();
+
+ // In this variant, it's always behind so resetting the flag to 0.
+ mMetadataContentByte = (byte) (mMetadataContentByte & ~EDGE_CONTENT_POSITION);
+ byte[] metadata = METADATA_TAG_BASE.clone();
+ metadata[FLAG_INDEX] = mMetadataContentByte;
+
+ Box.Builder layout =
+ new Box.Builder()
+ .setWidth(dp(mDeviceParameters.getScreenWidthDp()))
+ .setHeight(dp(mDeviceParameters.getScreenHeightDp()))
+ .setModifiers(
+ new Modifiers.Builder()
+ .setMetadata(
+ new ElementMetadata.Builder()
+ .setTagData(metadata).build())
+ .setPadding(
+ new Padding.Builder()
+ .setAll(dp(outerMargin))
+ .setRtlAware(true).build())
+ .build());
+
+ if (mEdgeContent != null) {
+ layout.addContent(mEdgeContent);
+ }
+
+ // Contains primary label, additional content and secondary label.
+ Column.Builder allInnerContent =
+ new Column.Builder()
+ .setWidth(contentWidth)
+ .setHeight(contentHeight)
+ .setModifiers(modifiers);
+
+ if (mPrimaryLabelText != null) {
+ allInnerContent.addContent(
+ insetElementWithPadding(mPrimaryLabelText, labelHorizontalPaddingDp));
+ allInnerContent.addContent(
+ new Spacer.Builder()
+ .setHeight(EDGE_CONTENT_LAYOUT_RESPONSIVE_PRIMARY_LABEL_SPACING_DP)
+ .build());
+ }
+
+ // Contains additional content and secondary label with wrapped height so it can be put
+ // inside of the Box to be centered. This is because primary label stays on top at
+ // the fixed place, while this content should be centered in the remaining space.
+ Column.Builder contentSecondaryLabel =
+ new Column.Builder().setWidth(expand()).setHeight(wrap());
+
+ if (mContent != null) {
+ contentSecondaryLabel.addContent(mContent);
+ }
+
+ if (mSecondaryLabelText != null) {
+ if (mContent != null) {
+ contentSecondaryLabel.addContent(
+ new Spacer.Builder().setHeight(mVerticalSpacerHeight).build());
+ }
+ contentSecondaryLabel.addContent(
+ insetElementWithPadding(mSecondaryLabelText, labelHorizontalPaddingDp));
+ }
+
+ allInnerContent.addContent(
+ new Box.Builder()
+ .setWidth(expand())
+ .setHeight(expand())
+ .addContent(contentSecondaryLabel.build())
+ .build());
+
+ layout.addContent(allInnerContent.build());
+
+ return new EdgeContentLayout(layout.build());
+ }
+
+ private float getEdgeContentSize() {
+ float edgeContentThickness =
+ mEdgeContentThickness == null
+ ?
+ // When not set, we try to get the thickness from CPI, otherwise we can
+ // only use 0.
+ (mEdgeContent instanceof CircularProgressIndicator
+ ? ((CircularProgressIndicator) mEdgeContent)
+ .getStrokeWidth().getValue()
+ : 0)
+ : mEdgeContentThickness;
+ return 2 * (EDGE_CONTENT_LAYOUT_RESPONSIVE_OUTER_MARGIN_DP + edgeContentThickness);
+ }
+
+ @NonNull
+ private EdgeContentLayout legacyLayoutBuild() {
float thicknessDp =
mEdgeContent instanceof CircularProgressIndicator
? ((CircularProgressIndicator) mEdgeContent).getStrokeWidth().getValue()
@@ -245,11 +497,11 @@
? EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_ROUND_DP
: EDGE_CONTENT_LAYOUT_MARGIN_HORIZONTAL_SQUARE_DP;
float indicatorWidth = 2 * (thicknessDp + DEFAULT_PADDING.getValue());
- float mainContentHeightDp = mDeviceParameters.getScreenHeightDp() - indicatorWidth;
- float mainContentWidthDp = mDeviceParameters.getScreenWidthDp() - indicatorWidth;
+ float contentHeightDp = mDeviceParameters.getScreenHeightDp() - indicatorWidth;
+ float contentWidthDp = mDeviceParameters.getScreenWidthDp() - indicatorWidth;
- DpProp mainContentHeight = dp(Math.min(mainContentHeightDp, mainContentWidthDp));
- DpProp mainContentWidth = dp(Math.min(mainContentHeightDp, mainContentWidthDp));
+ DpProp contentHeight = dp(Math.min(contentHeightDp, contentWidthDp));
+ DpProp contentWidth = dp(Math.min(contentHeightDp, contentWidthDp));
Modifiers modifiers =
new Modifiers.Builder()
@@ -262,7 +514,7 @@
.build();
if (!mIsEdgeContentBehind) {
- // If the edge content is above the main one, then its index should be 1.
+ // If the edge content is above the additional one, then its index should be 1.
// Otherwise it's 0.
mMetadataContentByte = (byte) (mMetadataContentByte | EDGE_CONTENT_POSITION);
}
@@ -316,8 +568,8 @@
.setModifiers(modifiers)
.setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
.setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER)
- .setHeight(mainContentHeight)
- .setWidth(mainContentWidth)
+ .setHeight(contentHeight)
+ .setWidth(contentWidth)
.addContent(innerContentBuilder.build())
.build();
@@ -353,10 +605,22 @@
if (!areElementsPresent(CONTENT_PRESENT)) {
return null;
}
- // By tag we know that content exists. It will be at position 0 if there is no primary
- // label, or at position 2 (primary label, spacer - content) otherwise.
- int contentPosition = areElementsPresent(PRIMARY_LABEL_PRESENT) ? 2 : 0;
- return ((Box) mInnerColumn.get(contentPosition)).getContents().get(0);
+ if (isResponsiveContentInsetEnabled()) {
+ return getInnerColumnContentsForResponsive().get(0);
+ } else {
+ // By tag we know that content exists. It will be at position 0 if there is no primary
+ // label, or at position 2 (primary label, spacer - content) otherwise.
+ int contentPosition = areElementsPresent(PRIMARY_LABEL_PRESENT) ? 2 : 0;
+ return ((Box) getInnerContent(contentPosition)).getContents().get(0);
+ }
+ }
+
+ /**
+ * Returns element from the inner content that is on the given index. It is a callers
+ * responsibility to pass in the correct index.
+ */
+ private LayoutElement getInnerContent(int contentPosition) {
+ return getAllContent().getContents().get(contentPosition);
}
/** Get the primary label content from this layout. */
@@ -365,8 +629,11 @@
if (!areElementsPresent(PRIMARY_LABEL_PRESENT)) {
return null;
}
- // By tag we know that primary label exists. It will always be at position 0.
- return mInnerColumn.get(0);
+ // By tag we know that primary label exists. It will always be at position 0 in the inner
+ // content area.
+ return isResponsiveContentInsetEnabled()
+ ? ((Box) getInnerContent(0)).getContents().get(0)
+ : getInnerContent(0);
}
/** Get the secondary label content from this layout. */
@@ -375,19 +642,98 @@
if (!areElementsPresent(SECONDARY_LABEL_PRESENT)) {
return null;
}
- // By tag we know that secondary label exists. It will always be at last position.
- return mInnerColumn.get(mInnerColumn.size() - 1);
+ if (isResponsiveContentInsetEnabled()) {
+ List<LayoutElement> innerColumnContents = getInnerColumnContentsForResponsive();
+ return ((Box) innerColumnContents.get(innerColumnContents.size() - 1))
+ .getContents().get(0);
+ } else {
+ // By tag we know that secondary label exists. It will always be at last position.
+ List<LayoutElement> mInnerColumn = getAllContent().getContents();
+ return mInnerColumn.get(mInnerColumn.size() - 1);
+ }
+ }
+
+ /** Get the size of spacing between content and secondary from this layout. */
+ @Dimension(unit = Dimension.DP)
+ public float getContentAndSecondaryLabelSpacing() {
+ if (!isResponsiveContentInsetEnabled()) {
+ return EDGE_CONTENT_LAYOUT_CONTENT_AND_SECONDARY_LABEL_SPACING_DP.getValue();
+ }
+
+ List<LayoutElement> innerColumnContents = getInnerColumnContentsForResponsive();
+ if (areElementsPresent(CONTENT_PRESENT) && areElementsPresent(SECONDARY_LABEL_PRESENT)) {
+ LayoutElement element =
+ ((Box) innerColumnContents.get(innerColumnContents.size() - 2))
+ .getContents().get(0);
+ if (element instanceof Spacer) {
+ SpacerDimension height = ((Spacer) element).getHeight();
+ if (height instanceof DpProp) {
+ return ((DpProp) height).getValue();
+ }
+ }
+ }
+ return EDGE_CONTENT_LAYOUT_CONTENT_AND_SECONDARY_LABEL_SPACING_DP.getValue();
}
/** Returns the edge content from this layout. */
@Nullable
public LayoutElement getEdgeContent() {
- return mEdgeContent;
+ return areElementsPresent(EDGE_CONTENT_PRESENT)
+ ? mImpl.getContents().get(getEdgeContentPosition()) : null;
+ }
+
+ private int getEdgeContentPosition() {
+ return isEdgeContentBehindAllOtherContent() ? 0 : 1;
}
/** Returns if the edge content has been placed behind the other contents. */
public boolean isEdgeContentBehindAllOtherContent() {
- return mIsEdgeContentBehind;
+ return (getMetadataTag()[FLAG_INDEX] & EDGE_CONTENT_POSITION) == 0;
+ }
+
+ /** Returns whether the contents from this layout are using responsive inset. */
+ public boolean isResponsiveContentInsetEnabled() {
+ return areElementsPresent(CONTENT_INSET_USED);
+ }
+
+ /** Returns the total size of the edge content including margins. */
+ public float getEdgeContentThickness() {
+ Column allContent = getAllContent();
+ if (mImpl.getWidth() instanceof DpProp && allContent.getWidth() instanceof DpProp) {
+ float edgeContentTotalThickness =
+ ((DpProp) mImpl.getWidth()).getValue()
+ - ((DpProp) allContent.getWidth()).getValue();
+ return edgeContentTotalThickness / 2 - EDGE_CONTENT_LAYOUT_RESPONSIVE_OUTER_MARGIN_DP;
+ }
+ return 0;
+ }
+
+ /** Returns Column that may contain primary label, additional content and secondary label. */
+ private Column getAllContent() {
+ int contentIndex = 1 - getEdgeContentPosition();
+ return (Column) (isResponsiveContentInsetEnabled()
+ ? mImpl.getContents().get(areElementsPresent(EDGE_CONTENT_PRESENT)
+ ? 1 : 0)
+ : ((Box) mImpl.getContents().get(contentIndex)).getContents().get(0));
+ }
+
+ /**
+ * Returns all content inside of the inner Column that may contain additional content, spacer
+ * and secondary label.
+ */
+ private List<LayoutElement> getInnerColumnContentsForResponsive() {
+ return ((Column)
+ ((Box)
+ getAllContent()
+ .getContents()
+ .get(areElementsPresent(PRIMARY_LABEL_PRESENT)
+ // There's a primary label and then spacer after it
+ // before other content in this Column.
+ ? 2
+ : 0))
+ .getContents()
+ .get(0))
+ .getContents();
}
/**
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout2.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout2.java
deleted file mode 100644
index cf88574..0000000
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout2.java
+++ /dev/null
@@ -1,504 +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.protolayout.material.layouts;
-
-import static androidx.annotation.Dimension.DP;
-import static androidx.wear.protolayout.DimensionBuilders.dp;
-import static androidx.wear.protolayout.DimensionBuilders.expand;
-import static androidx.wear.protolayout.DimensionBuilders.wrap;
-import static androidx.wear.protolayout.material.ProgressIndicatorDefaults.DEFAULT_PADDING;
-import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT2_MARGIN_HORIZONTAL_PERCENT;
-import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT2_MARGIN_VERTICAL_PERCENT;
-import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT2_OUTER_MARGIN_DP;
-import static androidx.wear.protolayout.material.layouts.LayoutDefaults.EDGE_CONTENT_LAYOUT2_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
-import static androidx.wear.protolayout.material.layouts.LayoutDefaults.LAYOUTS_LABEL_PADDING_PERCENT;
-import static androidx.wear.protolayout.material.layouts.LayoutDefaults.insetElementWithPadding;
-import static androidx.wear.protolayout.materialcore.Helper.checkNotNull;
-import static androidx.wear.protolayout.materialcore.Helper.checkTag;
-import static androidx.wear.protolayout.materialcore.Helper.getMetadataTagBytes;
-import static androidx.wear.protolayout.materialcore.Helper.getTagBytes;
-
-import android.annotation.SuppressLint;
-
-import androidx.annotation.Dimension;
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.RestrictTo.Scope;
-import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
-import androidx.wear.protolayout.DimensionBuilders.DpProp;
-import androidx.wear.protolayout.DimensionBuilders.SpacerDimension;
-import androidx.wear.protolayout.LayoutElementBuilders.Box;
-import androidx.wear.protolayout.LayoutElementBuilders.Column;
-import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
-import androidx.wear.protolayout.LayoutElementBuilders.Spacer;
-import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
-import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
-import androidx.wear.protolayout.ModifiersBuilders.Padding;
-import androidx.wear.protolayout.expression.Fingerprint;
-import androidx.wear.protolayout.material.CircularProgressIndicator;
-import androidx.wear.protolayout.proto.LayoutElementProto;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * ProtoLayout layout that represents the suggested layout style for Material ProtoLayout, which has
- * content around the edge of the screen (e.g. a CircularProgressIndicator) and the given content
- * inside of it with the recommended margin and padding applied. Optional primary or secondary label
- * can be added above and below the additional content, respectively.
- *
- * <p>When accessing the contents of a container for testing, note that this element can't be simply
- * casted back to the original type, i.e.:
- *
- * <pre>{@code
- * EdgeContentLayout2 ecl = new EdgeContentLayout2...
- * Box box = new Box.Builder().addContent(ecl).build();
- *
- * EdgeContentLayout2 myEcl = (EdgeContentLayout2) box.getContents().get(0);
- * }</pre>
- *
- * will fail.
- *
- * <p>To be able to get {@link EdgeContentLayout2} object from any layout element, {@link
- * #fromLayoutElement} method should be used, i.e.:
- *
- * <pre>{@code
- * EdgeContentLayout2 myEcl =
- * EdgeContentLayout.fromLayoutElement(box.getContents().get(0));
- * }</pre>
- */
-// TODO(b/274916652): Link visuals once they are available.
-public final class EdgeContentLayout2 implements LayoutElement {
- /**
- * Prefix tool tag for Metadata in Modifiers, so we know that Box is actually a
- * EdgeContentLayout.
- */
- static final String METADATA_TAG_PREFIX = "ECL2_";
-
- /**
- * Index for byte array that contains bits to check whether the content and indicator are
- * present or not.
- */
- static final int FLAG_INDEX = METADATA_TAG_PREFIX.length();
-
- /**
- * Base tool tag for Metadata in Modifiers, so we know that Box is actually a EdgeContentLayout
- * and what optional content is added.
- */
- static final byte[] METADATA_TAG_BASE =
- Arrays.copyOf(getTagBytes(METADATA_TAG_PREFIX), FLAG_INDEX + 1);
-
- /**
- * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
- * the edge content is present or not.
- */
- static final int EDGE_CONTENT_PRESENT = 0x1;
-
- /**
- * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
- * the primary label is present or not.
- */
- static final int PRIMARY_LABEL_PRESENT = 1 << 1;
-
- /**
- * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
- * the secondary label is present or not.
- */
- static final int SECONDARY_LABEL_PRESENT = 1 << 2;
-
- /**
- * Bit position in a byte on {@link #FLAG_INDEX} index in metadata byte array to check whether
- * the additional content is present or not.
- */
- static final int CONTENT_PRESENT = 1 << 3;
-
- @RestrictTo(Scope.LIBRARY)
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(
- flag = true,
- value = {
- EDGE_CONTENT_PRESENT,
- PRIMARY_LABEL_PRESENT,
- SECONDARY_LABEL_PRESENT,
- CONTENT_PRESENT
- })
- @interface ContentBits {}
-
- // Top level impl element.
- @NonNull private final Box mImpl;
-
- EdgeContentLayout2(@NonNull Box layoutElement) {
- this.mImpl = layoutElement;
- }
-
- /** Builder class for {@link EdgeContentLayout2}. */
- public static final class Builder implements LayoutElement.Builder {
- @NonNull private final DeviceParameters mDeviceParameters;
- @Nullable private LayoutElement mEdgeContent = null;
- @Nullable private LayoutElement mPrimaryLabel = null;
- @Nullable private LayoutElement mSecondaryLabel = null;
- @Nullable private LayoutElement mContent = null;
-
- @NonNull
- private DpProp mVerticalSpacerHeight =
- EDGE_CONTENT_LAYOUT2_CONTENT_AND_SECONDARY_LABEL_SPACING_DP;
-
- private byte mMetadataContentByte = 0;
- @Nullable private Float mEdgeContentThickness = null;
-
- /**
- * Creates a builder for the {@link EdgeContentLayout2}. Custom content inside of it can
- * later be set with {@link #setEdgeContent}, {@link #setContent},
- * {@link #setPrimaryLabelContent} and {@link #setSecondaryLabelContent}.
- */
- public Builder(@NonNull DeviceParameters deviceParameters) {
- this.mDeviceParameters = deviceParameters;
- }
-
- /**
- * Sets the content to be around the edges. This can be {@link CircularProgressIndicator},
- * custom {@link androidx.wear.protolayout.LayoutElementBuilders.Arc}, image or other
- * element.
- *
- * <p>If this content is something other that {@link CircularProgressIndicator}, please add
- * its thickness with {@link #setEdgeContentThickness} for best results.
- */
- @NonNull
- public Builder setEdgeContent(@NonNull LayoutElement edgeContent) {
- this.mEdgeContent = edgeContent;
- mMetadataContentByte = (byte) (mMetadataContentByte | EDGE_CONTENT_PRESENT);
- return this;
- }
-
- /**
- * Sets the thickness of the hollow edge content so that other content is correctly placed.
- * In other words, sets the space that should be reserved exclusively for the edge
- * content and not be overdrawn by other inner content.
- *
- * <p>For example, for {@link CircularProgressIndicator} or {@link
- * androidx.wear.protolayout.LayoutElementBuilders.ArcLine} elements, this should be equal
- * to their stroke width/thickness.
- */
- @NonNull
- public Builder setEdgeContentThickness(@Dimension(unit = DP) float thickness) {
- this.mEdgeContentThickness = thickness;
- return this;
- }
-
- /**
- * Sets the content in the primary label slot which will be above the additional content,
- * on a fixed place to ensure Tiles consistency with other layouts.
- *
- * <p>The label will also have an inset to prevent it from going off the screen.
- */
- @NonNull
- public Builder setPrimaryLabelContent(@NonNull LayoutElement primaryLabel) {
- this.mPrimaryLabel = primaryLabel;
- mMetadataContentByte = (byte) (mMetadataContentByte | PRIMARY_LABEL_PRESENT);
- return this;
- }
-
- /**
- * Sets the content in the secondary label slot which will be below the additional content.
- * It is highly recommended to have primary label set when having secondary label.
- *
- * <p>The label will also have an inset to prevent it from going off the screen.
- */
- @NonNull
- public Builder setSecondaryLabelContent(@NonNull LayoutElement secondaryLabel) {
- this.mSecondaryLabel = secondaryLabel;
- mMetadataContentByte = (byte) (mMetadataContentByte | SECONDARY_LABEL_PRESENT);
- return this;
- }
-
- /**
- * Sets the additional content to center of this layout, inside of the edge content and
- * between labels if present.
- */
- @NonNull
- public Builder setContent(@NonNull LayoutElement content) {
- this.mContent = content;
- mMetadataContentByte = (byte) (mMetadataContentByte | CONTENT_PRESENT);
- return this;
- }
-
- /**
- * Sets the size of space between an additional content and secondary label if there is
- * any. If one of those is not present, spacer is not used. If not set,
- * {@link LayoutDefaults#EDGE_CONTENT_LAYOUT2_CONTENT_AND_SECONDARY_LABEL_SPACING_DP} will be
- * used.
- */
- @NonNull
- public Builder setContentAndSecondaryLabelSpacing(@NonNull DpProp height) {
- this.mVerticalSpacerHeight = height;
- return this;
- }
-
- /** Constructs and returns {@link EdgeContentLayout2} with the provided content and look. */
- @SuppressLint("CheckResult") // (b/247804720)
- @NonNull
- @Override
- public EdgeContentLayout2 build() {
- // Calculate what is the inset box max size, i.e., the size that all content can occupy
- // without the edge content.
- // Use provided thickness if set. Otherwise, see if we can get it from
- // CircularProgressIndicator.
- float edgeContentThickness =
- mEdgeContentThickness == null
- ?
- // When not set, we try to get the thickness from CPI, otherwise we can
- // only use 0.
- (mEdgeContent instanceof CircularProgressIndicator
- ? ((CircularProgressIndicator) mEdgeContent)
- .getStrokeWidth().getValue()
- : 0)
- : mEdgeContentThickness;
- float edgeContentSize = 2
- * (EDGE_CONTENT_LAYOUT2_OUTER_MARGIN_DP + edgeContentThickness);
-
- DpProp contentHeight = dp(
- mDeviceParameters.getScreenWidthDp() - edgeContentSize);
- DpProp contentWidth = dp(
- mDeviceParameters.getScreenHeightDp() - edgeContentSize);
-
- // TODO(b/321681652): Confirm with the UX if we can put 6dp as outer margin so it
- // matches CPI.
- float outerMargin =
- mEdgeContent instanceof CircularProgressIndicator
- ? EDGE_CONTENT_LAYOUT2_OUTER_MARGIN_DP - DEFAULT_PADDING.getValue()
- : EDGE_CONTENT_LAYOUT2_OUTER_MARGIN_DP;
-
- // Horizontal and vertical padding added to the inner content.
- float horizontalPaddingDp =
- EDGE_CONTENT_LAYOUT2_MARGIN_HORIZONTAL_PERCENT
- * mDeviceParameters.getScreenWidthDp();
- float verticalPaddingDp =
- EDGE_CONTENT_LAYOUT2_MARGIN_VERTICAL_PERCENT
- * mDeviceParameters.getScreenWidthDp();
-
- // Padding to restrict labels from going off the screen.
- float labelHorizontalPaddingDp =
- mDeviceParameters.getScreenWidthDp() * LAYOUTS_LABEL_PADDING_PERCENT;
-
- Modifiers modifiers =
- new Modifiers.Builder()
- .setPadding(
- new Padding.Builder()
- .setRtlAware(true)
- .setStart(dp(horizontalPaddingDp))
- .setEnd(dp(horizontalPaddingDp))
- .setTop(dp(verticalPaddingDp))
- .setBottom(dp(verticalPaddingDp))
- .build())
- .build();
-
- byte[] metadata = METADATA_TAG_BASE.clone();
- metadata[FLAG_INDEX] = mMetadataContentByte;
-
- Box.Builder layout =
- new Box.Builder()
- .setWidth(dp(mDeviceParameters.getScreenWidthDp()))
- .setHeight(dp(mDeviceParameters.getScreenHeightDp()))
- .setModifiers(
- new Modifiers.Builder()
- .setMetadata(
- new ElementMetadata.Builder()
- .setTagData(metadata).build())
- .setPadding(
- new Padding.Builder()
- .setAll(dp(outerMargin))
- .setRtlAware(true).build())
- .build());
-
- if (mEdgeContent != null) {
- layout.addContent(mEdgeContent);
- }
-
- // Contains primary label, additional content and secondary label.
- Column.Builder allInnerContent =
- new Column.Builder()
- .setWidth(contentWidth)
- .setHeight(contentHeight)
- .setModifiers(modifiers);
-
- if (mPrimaryLabel != null) {
- allInnerContent.addContent(
- insetElementWithPadding(mPrimaryLabel, labelHorizontalPaddingDp));
- // TODO(b/321681652): Confirm with the UX that we don't need a space after this
- // abel.
- }
-
- // Contains additional content and secondary label with wrapped height so it can be put
- // inside of the Box to be centered. This is because primary label stays on top at
- // the fixed place, while this content should be centered in the remaining space.
- Column.Builder contentSecondaryLabel =
- new Column.Builder().setWidth(expand()).setHeight(wrap());
-
- if (mContent != null) {
- contentSecondaryLabel.addContent(mContent);
- }
-
- if (mSecondaryLabel != null) {
- if (mContent != null) {
- contentSecondaryLabel.addContent(
- new Spacer.Builder().setHeight(mVerticalSpacerHeight).build());
- }
- contentSecondaryLabel.addContent(
- insetElementWithPadding(mSecondaryLabel, labelHorizontalPaddingDp));
- }
-
- allInnerContent.addContent(
- new Box.Builder()
- .setWidth(expand())
- .setHeight(expand())
- .addContent(contentSecondaryLabel.build())
- .build());
-
- layout.addContent(allInnerContent.build());
-
- return new EdgeContentLayout2(layout.build());
- }
- }
-
- private boolean areElementsPresent(@ContentBits int elementFlag) {
- return (getMetadataTag()[FLAG_INDEX] & elementFlag) == elementFlag;
- }
-
- /** Returns Column that may contain primary label, additional content and secondary label. */
- private Column getAllContent() {
- return (Column) mImpl.getContents().get(areElementsPresent(EDGE_CONTENT_PRESENT) ? 1 : 0);
- }
-
- /**
- * Returns all content inside of the inner Column that may contain additional content, spacer
- * and secondary label.
- */
- private List<LayoutElement> getInnerColumnContents() {
- return ((Column)
- ((Box)
- getAllContent()
- .getContents()
- .get(areElementsPresent(PRIMARY_LABEL_PRESENT) ? 1 : 0))
- .getContents()
- .get(0))
- .getContents();
- }
-
- /** Returns metadata tag set to this EdgeContentLayout. */
- @NonNull
- byte[] getMetadataTag() {
- return getMetadataTagBytes(checkNotNull(checkNotNull(mImpl.getModifiers()).getMetadata()));
- }
-
- /** Returns the inner content from this layout. */
- @Nullable
- public LayoutElement getContent() {
- return areElementsPresent(CONTENT_PRESENT) ? getInnerColumnContents().get(0) : null;
- }
-
- /** Get the primary label content from this layout. */
- @Nullable
- public LayoutElement getPrimaryLabelContent() {
- return areElementsPresent(PRIMARY_LABEL_PRESENT)
- ? ((Box) getAllContent().getContents().get(0)).getContents().get(0)
- : null;
- }
-
- /** Get the secondary label content from this layout. */
- @Nullable
- public LayoutElement getSecondaryLabelContent() {
- List<LayoutElement> innerColumnContents = getInnerColumnContents();
- return areElementsPresent(SECONDARY_LABEL_PRESENT)
- ? ((Box) innerColumnContents.get(innerColumnContents.size() - 1))
- .getContents().get(0)
- : null;
- }
-
- /** Get the size of spacing between content and secondary from this layout. */
- @Dimension(unit = Dimension.DP)
- public float getContentAndSecondaryLabelSpacing() {
- List<LayoutElement> innerColumnContents = getInnerColumnContents();
- if (areElementsPresent(CONTENT_PRESENT) && areElementsPresent(SECONDARY_LABEL_PRESENT)) {
- LayoutElement element =
- ((Box) innerColumnContents.get(innerColumnContents.size() - 2))
- .getContents().get(0);
- if (element instanceof Spacer) {
- SpacerDimension height = ((Spacer) element).getHeight();
- if (height instanceof DpProp) {
- return ((DpProp) height).getValue();
- }
- }
- }
- return EDGE_CONTENT_LAYOUT2_CONTENT_AND_SECONDARY_LABEL_SPACING_DP.getValue();
- }
-
- /** Returns the edge content from this layout. */
- @Nullable
- public LayoutElement getEdgeContent() {
- return areElementsPresent(EDGE_CONTENT_PRESENT) ? mImpl.getContents().get(0) : null;
- }
-
- /** Returns the total size of the edge content including margins. */
- public float getEdgeContentThickness() {
- Column allContent = getAllContent();
- if (mImpl.getWidth() instanceof DpProp && allContent.getWidth() instanceof DpProp) {
- float edgeContentTotalThickness =
- ((DpProp) mImpl.getWidth()).getValue()
- - ((DpProp) allContent.getWidth()).getValue();
- return edgeContentTotalThickness / 2 - EDGE_CONTENT_LAYOUT2_OUTER_MARGIN_DP;
- }
- return 0;
- }
-
- /**
- * Returns EdgeContentLayout object from the given LayoutElement (e.g. one retrieved from a
- * container's content with {@code container.getContents().get(index)}) if that element can be
- * converted to EdgeContentLayout. Otherwise, it will return null.
- */
- @Nullable
- public static EdgeContentLayout2 fromLayoutElement(@NonNull LayoutElement element) {
- if (element instanceof EdgeContentLayout2) {
- return (EdgeContentLayout2) element;
- }
- if (!(element instanceof Box)) {
- return null;
- }
- Box boxElement = (Box) element;
- if (!checkTag(boxElement.getModifiers(), METADATA_TAG_PREFIX, METADATA_TAG_BASE)) {
- return null;
- }
- // Now we are sure that this element is a EdgeContentLayout.
- return new EdgeContentLayout2(boxElement);
- }
-
- @NonNull
- @Override
- @RestrictTo(Scope.LIBRARY_GROUP)
- public LayoutElementProto.LayoutElement toLayoutElementProto() {
- return mImpl.toLayoutElementProto();
- }
-
- @Nullable
- @Override
- public Fingerprint getFingerprint() {
- return mImpl.getFingerprint();
- }
-}
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java
index 4e55955..bb68851 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/layouts/LayoutDefaults.java
@@ -27,6 +27,7 @@
import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
import androidx.wear.protolayout.ModifiersBuilders.Padding;
import androidx.wear.protolayout.material.ButtonDefaults;
+import androidx.wear.protolayout.material.ProgressIndicatorDefaults;
/** Contains the default values used by layout templates for ProtoLayout. */
@@ -111,39 +112,46 @@
/**
* The default percentage of the screen width for the horizontal margin in the {@link
- * EdgeContentLayout2}.
+ * EdgeContentLayout} with responsiveness.
*/
- static final float EDGE_CONTENT_LAYOUT2_MARGIN_HORIZONTAL_PERCENT = 6.3f / 100;
+ static final float EDGE_CONTENT_LAYOUT_RESPONSIVE_MARGIN_HORIZONTAL_PERCENT = 6.3f / 100;
/**
* The default percentage of the screen height for the vertical margin in the {@link
- * EdgeContentLayout2}.
+ * EdgeContentLayout} with responsiveness.
*/
- static final float EDGE_CONTENT_LAYOUT2_MARGIN_VERTICAL_PERCENT = 12f / 100;
+ static final float EDGE_CONTENT_LAYOUT_RESPONSIVE_MARGIN_VERTICAL_PERCENT = 12f / 100;
- /** The margins used in the {@link EdgeContentLayout2}. */
+ /** The margins used in the {@link EdgeContentLayout} with responsiveness. */
@Dimension(unit = DP)
- static final int EDGE_CONTENT_LAYOUT2_OUTER_MARGIN_DP = 8;
+ static final float EDGE_CONTENT_LAYOUT_RESPONSIVE_OUTER_MARGIN_DP =
+ ProgressIndicatorDefaults.DEFAULT_PADDING.getValue();
/**
* The default spacer width that should be between main content and secondary label if set in
- * the {@link EdgeContentLayout2}.
+ * the {@link EdgeContentLayout}.
*
* <p>It is recommended to use this on smaller screen sizes or when there's a lot of content in
* the layout.
*/
- public static final DpProp EDGE_CONTENT_LAYOUT2_CONTENT_AND_SECONDARY_LABEL_SPACING_DP = dp(8);
+ public static final DpProp EDGE_CONTENT_LAYOUT_CONTENT_AND_SECONDARY_LABEL_SPACING_DP = dp(8);
/**
* The default spacer width that should be between main content and secondary label if set in
- * the {@link EdgeContentLayout2}.
+ * the {@link EdgeContentLayout}.
*
* <p>It is recommended to use this on larger screen sizes (screen sizes above 225dp).
*/
- public static final DpProp EDGE_CONTENT_LAYOUT2_LARGE_CONTENT_AND_SECONDARY_LABEL_SPACING_DP =
+ public static final DpProp EDGE_CONTENT_LAYOUT_LARGE_CONTENT_AND_SECONDARY_LABEL_SPACING_DP =
dp(12);
/**
+ * The default spacing below primary label in the {@link EdgeContentLayout} to ensure that inner
+ * content is not too high up and not near the primary label.
+ */
+ static final DpProp EDGE_CONTENT_LAYOUT_RESPONSIVE_PRIMARY_LABEL_SPACING_DP = dp(8);
+
+ /**
* The recommended padding that should be above the main content (text) in the {@link
* EdgeContentLayout}.
*/
diff --git a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout2Test.java b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout2Test.java
deleted file mode 100644
index 00c107b..0000000
--- a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/EdgeContentLayout2Test.java
+++ /dev/null
@@ -1,281 +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.wear.protolayout.material.layouts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
-import androidx.wear.protolayout.LayoutElementBuilders.Box;
-import androidx.wear.protolayout.LayoutElementBuilders.Column;
-import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
-import androidx.wear.protolayout.ModifiersBuilders.ElementMetadata;
-import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
-import androidx.wear.protolayout.material.CircularProgressIndicator;
-import androidx.wear.protolayout.material.Text;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-@RunWith(AndroidJUnit4.class)
-@DoNotInstrument
-public class EdgeContentLayout2Test {
- private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
- private static final DeviceParameters DEVICE_PARAMETERS =
- new DeviceParameters.Builder().setScreenWidthDp(192).setScreenHeightDp(192).build();
- private static final Text PRIMARY_LABEL =
- new Text.Builder(CONTEXT, "Primary label").build();
- private static final Text SECONDARY_LABEL =
- new Text.Builder(CONTEXT, "Secondary label").build();
-
- @Test
- public void testAll() {
- LayoutElement content = new Box.Builder().build();
- CircularProgressIndicator progressIndicator =
- new CircularProgressIndicator.Builder().build();
- int edgeContentThickness = 20;
- EdgeContentLayout2 layout =
- new EdgeContentLayout2.Builder(DEVICE_PARAMETERS)
- .setContent(content)
- .setEdgeContent(progressIndicator)
- .setPrimaryLabelContent(PRIMARY_LABEL)
- .setSecondaryLabelContent(SECONDARY_LABEL)
- .setEdgeContentThickness(edgeContentThickness)
- .build();
-
- assertLayout(
- layout,
- progressIndicator,
- content,
- PRIMARY_LABEL,
- SECONDARY_LABEL,
- edgeContentThickness);
- }
-
- @Test
- public void testAll_defaultThickness() {
- LayoutElement content = new Box.Builder().build();
- CircularProgressIndicator progressIndicator =
- new CircularProgressIndicator.Builder().build();
- EdgeContentLayout2 layout =
- new EdgeContentLayout2.Builder(DEVICE_PARAMETERS)
- .setContent(content)
- .setEdgeContent(progressIndicator)
- .setPrimaryLabelContent(PRIMARY_LABEL)
- .setSecondaryLabelContent(SECONDARY_LABEL)
- .build();
-
- assertLayout(
- layout,
- progressIndicator,
- content,
- PRIMARY_LABEL,
- SECONDARY_LABEL,
- progressIndicator.getStrokeWidth().getValue());
- }
-
- @Test
- public void testContentOnly() {
- LayoutElement content = new Box.Builder().build();
- EdgeContentLayout2 layout =
- new EdgeContentLayout2.Builder(DEVICE_PARAMETERS).setContent(content).build();
-
- assertLayout(
- layout,
- /* expectedProgressIndicator= */ null,
- content,
- /* expectedPrimaryLabel= */ null,
- /* expectedSecondaryLabel= */ null,
- /* expectedEdgeContentThickness= */ 0);
- }
-
- @Test
- public void testIndicatorOnly() {
- CircularProgressIndicator progressIndicator =
- new CircularProgressIndicator.Builder().build();
- EdgeContentLayout2 layout =
- new EdgeContentLayout2.Builder(DEVICE_PARAMETERS)
- .setEdgeContent(progressIndicator).build();
-
- assertLayout(
- layout,
- progressIndicator,
- /* expectedContent= */ null,
- /* expectedPrimaryLabel= */ null,
- /* expectedSecondaryLabel= */ null,
- progressIndicator.getStrokeWidth().getValue());
- }
-
- @Test
- public void testEmpty() {
- EdgeContentLayout2 layout = new EdgeContentLayout2.Builder(DEVICE_PARAMETERS).build();
-
- assertLayout(
- layout,
- /* expectedProgressIndicator= */ null,
- /* expectedContent= */ null,
- /* expectedPrimaryLabel= */ null,
- /* expectedSecondaryLabel= */ null,
- /* expectedEdgeContentThickness= */ 0);
- }
-
- @Test
- public void testWrongElement() {
- Column box = new Column.Builder().build();
-
- assertThat(EdgeContentLayout2.fromLayoutElement(box)).isNull();
- }
-
- @Test
- public void testWrongBox() {
- Box box = new Box.Builder().build();
-
- assertThat(EdgeContentLayout2.fromLayoutElement(box)).isNull();
- }
-
- @Test
- public void testWrongTag() {
- Box box =
- new Box.Builder()
- .setModifiers(
- new Modifiers.Builder()
- .setMetadata(
- new ElementMetadata.Builder()
- .setTagData("test".getBytes(UTF_8)).build())
- .build())
- .build();
-
- assertThat(EdgeContentLayout2.fromLayoutElement(box)).isNull();
- }
-
- @Test
- public void testWrongLengthTag() {
- Box box =
- new Box.Builder()
- .setModifiers(
- new Modifiers.Builder()
- .setMetadata(
- new ElementMetadata.Builder()
- .setTagData(
- EdgeContentLayout2
- .METADATA_TAG_PREFIX
- .getBytes(UTF_8))
- .build())
- .build())
- .build();
-
- assertThat(EdgeContentLayout2.fromLayoutElement(box)).isNull();
- }
-
- private void assertLayout(
- @NonNull EdgeContentLayout2 actualLayout,
- @Nullable LayoutElement expectedProgressIndicator,
- @Nullable LayoutElement expectedContent,
- @Nullable LayoutElement expectedPrimaryLabel,
- @Nullable LayoutElement expectedSecondaryLabel,
- float expectedEdgeContentThickness) {
- assertLayoutIsEqual(
- actualLayout,
- expectedProgressIndicator,
- expectedContent,
- expectedPrimaryLabel,
- expectedSecondaryLabel,
- expectedEdgeContentThickness);
-
- Box box = new Box.Builder().addContent(actualLayout).build();
-
- EdgeContentLayout2 newLayout = EdgeContentLayout2
- .fromLayoutElement(box.getContents().get(0));
-
- assertThat(newLayout).isNotNull();
- assertLayoutIsEqual(
- newLayout,
- expectedProgressIndicator,
- expectedContent,
- expectedPrimaryLabel,
- expectedSecondaryLabel,
- expectedEdgeContentThickness);
-
- assertThat(EdgeContentLayout2.fromLayoutElement(actualLayout)).isEqualTo(actualLayout);
- }
-
- private void assertLayoutIsEqual(
- @NonNull EdgeContentLayout2 actualLayout,
- @Nullable LayoutElement expectedProgressIndicator,
- @Nullable LayoutElement expectedContent,
- @Nullable LayoutElement expectedPrimaryLabel,
- @Nullable LayoutElement expectedSecondaryLabel,
- float expectedEdgeContentThickness) {
- byte[] expectedMetadata = EdgeContentLayout2.METADATA_TAG_BASE.clone();
-
- if (expectedProgressIndicator == null) {
- assertThat(actualLayout.getEdgeContent()).isNull();
- } else {
- assertThat(actualLayout.getEdgeContent().toLayoutElementProto())
- .isEqualTo(expectedProgressIndicator.toLayoutElementProto());
- expectedMetadata[EdgeContentLayout2.FLAG_INDEX] =
- (byte)
- (expectedMetadata[EdgeContentLayout2.FLAG_INDEX]
- | EdgeContentLayout2.EDGE_CONTENT_PRESENT);
- }
-
- if (expectedContent == null) {
- assertThat(actualLayout.getContent()).isNull();
- } else {
- assertThat(actualLayout.getContent().toLayoutElementProto())
- .isEqualTo(expectedContent.toLayoutElementProto());
- expectedMetadata[EdgeContentLayout2.FLAG_INDEX] =
- (byte)
- (expectedMetadata[EdgeContentLayout2.FLAG_INDEX]
- | EdgeContentLayout2.CONTENT_PRESENT);
- }
-
- if (expectedPrimaryLabel == null) {
- assertThat(actualLayout.getPrimaryLabelContent()).isNull();
- } else {
- assertThat(actualLayout.getPrimaryLabelContent().toLayoutElementProto())
- .isEqualTo(expectedPrimaryLabel.toLayoutElementProto());
- expectedMetadata[EdgeContentLayout2.FLAG_INDEX] =
- (byte)
- (expectedMetadata[EdgeContentLayout2.FLAG_INDEX]
- | EdgeContentLayout2.PRIMARY_LABEL_PRESENT);
- }
-
- if (expectedSecondaryLabel == null) {
- assertThat(actualLayout.getSecondaryLabelContent()).isNull();
- } else {
- assertThat(actualLayout.getSecondaryLabelContent().toLayoutElementProto())
- .isEqualTo(expectedSecondaryLabel.toLayoutElementProto());
- expectedMetadata[EdgeContentLayout2.FLAG_INDEX] =
- (byte)
- (expectedMetadata[EdgeContentLayout2.FLAG_INDEX]
- | EdgeContentLayout2.SECONDARY_LABEL_PRESENT);
- }
-
- assertThat(actualLayout.getEdgeContentThickness()).isEqualTo(expectedEdgeContentThickness);
-
- assertThat(actualLayout.getMetadataTag()).isEqualTo(expectedMetadata);
- }
-}
diff --git a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/EdgeContentLayoutTest.java b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/EdgeContentLayoutTest.java
index b74fc9d..3c9bf44 100644
--- a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/EdgeContentLayoutTest.java
+++ b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/EdgeContentLayoutTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
import static java.nio.charset.StandardCharsets.UTF_8;
import android.content.Context;
@@ -64,6 +66,8 @@
.build();
assertLayout(layout, progressIndicator, content, PRIMARY_LABEL, SECONDARY_LABEL);
+ assertThat(layout.isEdgeContentBehindAllOtherContent()).isFalse();
+ assertThat(layout.isResponsiveContentInsetEnabled()).isFalse();
}
@Test
@@ -82,6 +86,7 @@
assertLayout(layout, progressIndicator, content, PRIMARY_LABEL, SECONDARY_LABEL);
assertThat(layout.isEdgeContentBehindAllOtherContent()).isTrue();
+ assertThat(layout.isResponsiveContentInsetEnabled()).isFalse();
}
@Test
@@ -91,6 +96,8 @@
new EdgeContentLayout.Builder(DEVICE_PARAMETERS).setContent(content).build();
assertLayout(layout, null, content, null, null);
+ assertThat(layout.isEdgeContentBehindAllOtherContent()).isFalse();
+ assertThat(layout.isResponsiveContentInsetEnabled()).isFalse();
}
@Test
@@ -103,6 +110,8 @@
.build();
assertLayout(layout, progressIndicator, null, null, null);
+ assertThat(layout.isEdgeContentBehindAllOtherContent()).isFalse();
+ assertThat(layout.isResponsiveContentInsetEnabled()).isFalse();
}
@Test
@@ -110,6 +119,129 @@
EdgeContentLayout layout = new EdgeContentLayout.Builder(DEVICE_PARAMETERS).build();
assertLayout(layout, null, null, null, null);
+ assertThat(layout.isEdgeContentBehindAllOtherContent()).isFalse();
+ assertThat(layout.isResponsiveContentInsetEnabled()).isFalse();
+ }
+
+ // Responsive test cases with behaviour with using setResponsiveContentInsetEnabled. They are
+ // repeated because the organization of elements inside of the layout is different then without
+ // this setter.
+
+ @Test
+ public void testAll_responsive() {
+ LayoutElement content = new Box.Builder().build();
+ CircularProgressIndicator progressIndicator =
+ new CircularProgressIndicator.Builder().build();
+ int edgeContentThickness = 20;
+ EdgeContentLayout layout =
+ new EdgeContentLayout.Builder(DEVICE_PARAMETERS)
+ .setResponsiveContentInsetEnabled(true)
+ .setContent(content)
+ .setEdgeContent(progressIndicator)
+ .setPrimaryLabelTextContent(PRIMARY_LABEL)
+ .setSecondaryLabelTextContent(SECONDARY_LABEL)
+ .setEdgeContentThickness(edgeContentThickness)
+ .build();
+
+ assertLayout(
+ layout,
+ progressIndicator,
+ content,
+ PRIMARY_LABEL,
+ SECONDARY_LABEL);
+
+ assertThat(layout.getEdgeContentThickness()).isEqualTo(edgeContentThickness);
+ assertThat(layout.isEdgeContentBehindAllOtherContent()).isTrue();
+ assertThat(layout.isResponsiveContentInsetEnabled()).isTrue();
+ }
+
+ @Test
+ public void testAll_defaultThickness_responsive() {
+ LayoutElement content = new Box.Builder().build();
+ CircularProgressIndicator progressIndicator =
+ new CircularProgressIndicator.Builder().build();
+ EdgeContentLayout layout =
+ new EdgeContentLayout.Builder(DEVICE_PARAMETERS)
+ .setResponsiveContentInsetEnabled(true)
+ .setContent(content)
+ .setEdgeContent(progressIndicator)
+ .setPrimaryLabelTextContent(PRIMARY_LABEL)
+ .setSecondaryLabelTextContent(SECONDARY_LABEL)
+ .build();
+
+ assertLayout(
+ layout,
+ progressIndicator,
+ content,
+ PRIMARY_LABEL,
+ SECONDARY_LABEL);
+
+ assertThat(layout.getEdgeContentThickness())
+ .isEqualTo(progressIndicator.getStrokeWidth().getValue());
+ assertThat(layout.isEdgeContentBehindAllOtherContent()).isTrue();
+ assertThat(layout.isResponsiveContentInsetEnabled()).isTrue();
+ }
+
+ @Test
+ public void testContentOnly_responsive() {
+ LayoutElement content = new Box.Builder().build();
+ EdgeContentLayout layout =
+ new EdgeContentLayout.Builder(DEVICE_PARAMETERS)
+ .setResponsiveContentInsetEnabled(true)
+ .setContent(content)
+ .build();
+
+ assertLayout(
+ layout,
+ /* expectedProgressIndicator= */ null,
+ content,
+ /* expectedPrimaryLabel= */ null,
+ /* expectedSecondaryLabel= */ null);
+
+ assertThat(layout.getEdgeContentThickness()).isEqualTo(0);
+ assertThat(layout.isEdgeContentBehindAllOtherContent()).isTrue();
+ assertThat(layout.isResponsiveContentInsetEnabled()).isTrue();
+ }
+
+ @Test
+ public void testIndicatorOnly_responsive() {
+ CircularProgressIndicator progressIndicator =
+ new CircularProgressIndicator.Builder().build();
+ EdgeContentLayout layout =
+ new EdgeContentLayout.Builder(DEVICE_PARAMETERS)
+ .setResponsiveContentInsetEnabled(true)
+ .setEdgeContent(progressIndicator)
+ .build();
+
+ assertLayout(
+ layout,
+ progressIndicator,
+ /* expectedContent= */ null,
+ /* expectedPrimaryLabel= */ null,
+ /* expectedSecondaryLabel= */ null);
+
+ assertThat(layout.getEdgeContentThickness())
+ .isEqualTo(progressIndicator.getStrokeWidth().getValue());
+ assertThat(layout.isEdgeContentBehindAllOtherContent()).isTrue();
+ assertThat(layout.isResponsiveContentInsetEnabled()).isTrue();
+ }
+
+ @Test
+ public void testEmpty_responsive() {
+ EdgeContentLayout layout = new EdgeContentLayout.Builder(DEVICE_PARAMETERS)
+ .setResponsiveContentInsetEnabled(true)
+ .build();
+
+ assertLayout(
+ layout,
+ /* expectedProgressIndicator= */ null,
+ /* expectedContent= */ null,
+ /* expectedPrimaryLabel= */ null,
+ /* expectedSecondaryLabel= */ null);
+
+ assertThat(layout.getEdgeContentThickness()).isEqualTo(0);
+ assertThat(layout.isEdgeContentBehindAllOtherContent()).isTrue();
+ assertThat(layout.isResponsiveContentInsetEnabled()).isTrue();
}
@Test
@@ -161,6 +293,28 @@
assertThat(EdgeContentLayout.fromLayoutElement(box)).isNull();
}
+ @Test
+ public void testResponsiveAndBehindContentSettersMixed() {
+ EdgeContentLayout.Builder builder =
+ new EdgeContentLayout.Builder(DEVICE_PARAMETERS)
+ .setResponsiveContentInsetEnabled(true);
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> builder.setEdgeContentBehindAllOtherContent(true));
+ }
+
+ @Test
+ public void testBehindContentAndResponsiveSettersMixed() {
+ EdgeContentLayout.Builder builder =
+ new EdgeContentLayout.Builder(DEVICE_PARAMETERS)
+ .setEdgeContentBehindAllOtherContent(true);
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> builder.setResponsiveContentInsetEnabled(true));
+ }
+
private void assertLayout(
@NonNull EdgeContentLayout actualLayout,
@Nullable LayoutElement expectedProgressIndicator,
@@ -243,17 +397,21 @@
// Reset bit for edge content position. If that bit is wrong, the above checks around
// content will fail, so we don't need to specifically check it here.
- resetEdgeContentPositionFlag(expectedMetadata);
+ resetEdgeContentPositionAndResponsiveFlag(expectedMetadata);
byte[] actualMetadata = actualLayout.getMetadataTag();
- resetEdgeContentPositionFlag(actualMetadata);
+ resetEdgeContentPositionAndResponsiveFlag(actualMetadata);
assertThat(actualMetadata).isEqualTo(expectedMetadata);
}
- private static void resetEdgeContentPositionFlag(byte[] expectedMetadata) {
+ private static void resetEdgeContentPositionAndResponsiveFlag(byte[] expectedMetadata) {
expectedMetadata[EdgeContentLayout.FLAG_INDEX] =
(byte)
(expectedMetadata[EdgeContentLayout.FLAG_INDEX]
& ~EdgeContentLayout.EDGE_CONTENT_POSITION);
+ expectedMetadata[EdgeContentLayout.FLAG_INDEX] =
+ (byte)
+ (expectedMetadata[EdgeContentLayout.FLAG_INDEX]
+ & ~EdgeContentLayout.CONTENT_INSET_USED);
}
}
diff --git a/wear/protolayout/protolayout-proto/src/main/proto/dimension.proto b/wear/protolayout/protolayout-proto/src/main/proto/dimension.proto
index 2a84f1e..3276613 100644
--- a/wear/protolayout/protolayout-proto/src/main/proto/dimension.proto
+++ b/wear/protolayout/protolayout-proto/src/main/proto/dimension.proto
@@ -173,3 +173,22 @@
DpProp linear_dimension = 1;
}
}
+
+// Provide a length measurement proportional to the element's bounding box.
+message BoundingBoxRatio {
+ // The ratio relative to the bounding box width/height, with the bounding box
+ // top / start as 0 and bottom / end as 1. Values outside [0, 1] are also valid.
+ // Dynamic value is supported. If not set, default to the middle of the element.
+ FloatProp ratio = 1;
+}
+
+// A dimension that can be applied to a pivot location for scale and rotate transformations.
+message PivotDimension {
+ oneof inner {
+ // Location in dp, with the default pivot (view center) as 0
+ DpProp offset_dp = 1;
+ // Location proportion to bounding box, with the bounding box top / start as
+ // 0 and bottom / end as 1. Values outside [0, 1] are also valid.
+ BoundingBoxRatio location_ratio = 2;
+ }
+}
diff --git a/wear/protolayout/protolayout-proto/src/main/proto/modifiers.proto b/wear/protolayout/protolayout-proto/src/main/proto/modifiers.proto
index f771ea4..c28400ba 100644
--- a/wear/protolayout/protolayout-proto/src/main/proto/modifiers.proto
+++ b/wear/protolayout/protolayout-proto/src/main/proto/modifiers.proto
@@ -159,6 +159,35 @@
bytes tag_data = 1;
}
+// A modifier to apply transformations to the element. All of these transformations can be animated
+// by setting dynamic values. This modifier is not layout affecting.
+message Transformation {
+ // The horizontal offset of this element relative to the location where the element's layout
+ // placed it.
+ DpProp translation_x = 1;
+ // The vertical offset of this element in addition to the location where the element's layout
+ // placed it.
+ DpProp translation_y = 2;
+ // The scale of this element in the x direction around the pivot point, as a proportion of the
+ // element's unscaled width .
+ FloatProp scale_x = 3;
+ // The scale of this element in the y direction around the pivot point, as a proportion of the
+ // element's unscaled height.
+ FloatProp scale_y = 4;
+ // The clockwise Degrees that the element is rotated around the pivot point.
+ DegreesProp rotation = 5;
+ // The x offset of the point around which the element is rotated
+ // and scaled. Dynamic value is supported. By default, the pivot is centered
+ // on the element. Note that, for ArcText or ArcLine, the element inscribes the
+ // entire circle and the default pivot is located at the center of the circle.
+ PivotDimension pivot_x = 6;
+ // The y offset of the point around which the element is rotated
+ // and scaled. Dynamic value is supported. By default, the pivot is centered
+ // on the element. Note that, for ArcText or ArcLine, the element inscribes the
+ // entire circle and the default pivot is located at the center of the circle.
+ PivotDimension pivot_y = 7;
+}
+
// Modifiers for an element. These may change the way they are drawn (e.g.
// Padding or Background), or change their behaviour (e.g. Clickable, or
// Semantics).
@@ -204,6 +233,14 @@
// Note that a hidden element also cannot be clickable (i.e. a Clickable
// modifier would be ignored).
BoolProp visible = 10;
+
+ // The transformation applied to the element post-layout
+ Transformation transformation = 11;
+
+ // The opacity of the element with a value from 0 to 1, where 0 means
+ // the element is completely transparent and 1 means the element is
+ // completely opaque. Dynamic value is supported.
+ FloatProp opacity = 12;
}
// The content transition of an element. Any update to the element or its
@@ -366,6 +403,12 @@
// Adds metadata for the modified element, for example, screen reader content
// descriptions.
Semantics semantics = 2;
+
+ // The transformation applied to the element post-layout
+ Transformation transformation = 3;
+
+ // The opacity of the element
+ FloatProp opacity = 4;
}
// Modifiers that can be used with Span elements. These may change the way
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index b19b5d5..9a40fa9 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -209,6 +209,15 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint.Builder setAngularAlignment(int);
}
+ @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final class DimensionBuilders.BoundingBoxRatio implements androidx.wear.protolayout.DimensionBuilders.PivotDimension {
+ method public androidx.wear.protolayout.TypeBuilders.FloatProp getRatio();
+ }
+
+ public static final class DimensionBuilders.BoundingBoxRatio.Builder {
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public DimensionBuilders.BoundingBoxRatio.Builder(androidx.wear.protolayout.TypeBuilders.FloatProp);
+ method public androidx.wear.protolayout.DimensionBuilders.BoundingBoxRatio build();
+ }
+
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static interface DimensionBuilders.ContainerDimension {
}
@@ -225,7 +234,7 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.DimensionBuilders.DegreesProp.Builder setValue(float);
}
- @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class DimensionBuilders.DpProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension androidx.wear.protolayout.DimensionBuilders.ExtensionDimension androidx.wear.protolayout.DimensionBuilders.ImageDimension androidx.wear.protolayout.DimensionBuilders.SpacerDimension {
+ @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class DimensionBuilders.DpProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension androidx.wear.protolayout.DimensionBuilders.ExtensionDimension androidx.wear.protolayout.DimensionBuilders.ImageDimension androidx.wear.protolayout.DimensionBuilders.PivotDimension androidx.wear.protolayout.DimensionBuilders.SpacerDimension {
method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue();
method @Dimension(unit=androidx.annotation.Dimension.DP) public float getValue();
}
@@ -275,6 +284,9 @@
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static interface DimensionBuilders.ImageDimension {
}
+ @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static interface DimensionBuilders.PivotDimension {
+ }
+
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class DimensionBuilders.ProportionalDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ImageDimension {
method @IntRange(from=0) public int getAspectRatioHeight();
method @IntRange(from=0) public int getAspectRatioWidth();
@@ -886,14 +898,18 @@
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class ModifiersBuilders.ArcModifiers {
method public androidx.wear.protolayout.ModifiersBuilders.Clickable? getClickable();
+ method public androidx.wear.protolayout.TypeBuilders.FloatProp? getOpacity();
method public androidx.wear.protolayout.ModifiersBuilders.Semantics? getSemantics();
+ method public androidx.wear.protolayout.ModifiersBuilders.Transformation? getTransformation();
}
public static final class ModifiersBuilders.ArcModifiers.Builder {
ctor public ModifiersBuilders.ArcModifiers.Builder();
method public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers build();
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers.Builder setClickable(androidx.wear.protolayout.ModifiersBuilders.Clickable);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers.Builder setOpacity(androidx.wear.protolayout.TypeBuilders.FloatProp);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers.Builder setSemantics(androidx.wear.protolayout.ModifiersBuilders.Semantics);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers.Builder setTransformation(androidx.wear.protolayout.ModifiersBuilders.Transformation);
}
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class ModifiersBuilders.Background {
@@ -1019,8 +1035,10 @@
method public androidx.wear.protolayout.ModifiersBuilders.Clickable? getClickable();
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility? getContentUpdateAnimation();
method public androidx.wear.protolayout.ModifiersBuilders.ElementMetadata? getMetadata();
+ method public androidx.wear.protolayout.TypeBuilders.FloatProp? getOpacity();
method public androidx.wear.protolayout.ModifiersBuilders.Padding? getPadding();
method public androidx.wear.protolayout.ModifiersBuilders.Semantics? getSemantics();
+ method public androidx.wear.protolayout.ModifiersBuilders.Transformation? getTransformation();
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.TypeBuilders.BoolProp isVisible();
}
@@ -1032,8 +1050,10 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setClickable(androidx.wear.protolayout.ModifiersBuilders.Clickable);
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setContentUpdateAnimation(androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setMetadata(androidx.wear.protolayout.ModifiersBuilders.ElementMetadata);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setOpacity(androidx.wear.protolayout.TypeBuilders.FloatProp);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setPadding(androidx.wear.protolayout.ModifiersBuilders.Padding);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setSemantics(androidx.wear.protolayout.ModifiersBuilders.Semantics);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setTransformation(androidx.wear.protolayout.ModifiersBuilders.Transformation);
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setVisible(androidx.wear.protolayout.TypeBuilders.BoolProp);
}
@@ -1135,6 +1155,28 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.SpanModifiers.Builder setClickable(androidx.wear.protolayout.ModifiersBuilders.Clickable);
}
+ @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final class ModifiersBuilders.Transformation {
+ method public androidx.wear.protolayout.DimensionBuilders.PivotDimension? getPivotX();
+ method public androidx.wear.protolayout.DimensionBuilders.PivotDimension? getPivotY();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getRotation();
+ method public androidx.wear.protolayout.TypeBuilders.FloatProp? getScaleX();
+ method public androidx.wear.protolayout.TypeBuilders.FloatProp? getScaleY();
+ method public androidx.wear.protolayout.DimensionBuilders.DpProp? getTranslationX();
+ method public androidx.wear.protolayout.DimensionBuilders.DpProp? getTranslationY();
+ }
+
+ public static final class ModifiersBuilders.Transformation.Builder {
+ ctor public ModifiersBuilders.Transformation.Builder();
+ method public androidx.wear.protolayout.ModifiersBuilders.Transformation build();
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setPivotX(androidx.wear.protolayout.DimensionBuilders.PivotDimension);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setPivotY(androidx.wear.protolayout.DimensionBuilders.PivotDimension);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setRotation(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setScaleX(androidx.wear.protolayout.TypeBuilders.FloatProp);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setScaleY(androidx.wear.protolayout.TypeBuilders.FloatProp);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setTranslationX(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setTranslationY(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ }
+
public final class ResourceBuilders {
field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static final int ANIMATED_IMAGE_FORMAT_AVD = 1; // 0x1
field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static final int ANIMATED_IMAGE_FORMAT_UNDEFINED = 0; // 0x0
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index b19b5d5..9a40fa9 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -209,6 +209,15 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint.Builder setAngularAlignment(int);
}
+ @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final class DimensionBuilders.BoundingBoxRatio implements androidx.wear.protolayout.DimensionBuilders.PivotDimension {
+ method public androidx.wear.protolayout.TypeBuilders.FloatProp getRatio();
+ }
+
+ public static final class DimensionBuilders.BoundingBoxRatio.Builder {
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public DimensionBuilders.BoundingBoxRatio.Builder(androidx.wear.protolayout.TypeBuilders.FloatProp);
+ method public androidx.wear.protolayout.DimensionBuilders.BoundingBoxRatio build();
+ }
+
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static interface DimensionBuilders.ContainerDimension {
}
@@ -225,7 +234,7 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.DimensionBuilders.DegreesProp.Builder setValue(float);
}
- @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class DimensionBuilders.DpProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension androidx.wear.protolayout.DimensionBuilders.ExtensionDimension androidx.wear.protolayout.DimensionBuilders.ImageDimension androidx.wear.protolayout.DimensionBuilders.SpacerDimension {
+ @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class DimensionBuilders.DpProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension androidx.wear.protolayout.DimensionBuilders.ExtensionDimension androidx.wear.protolayout.DimensionBuilders.ImageDimension androidx.wear.protolayout.DimensionBuilders.PivotDimension androidx.wear.protolayout.DimensionBuilders.SpacerDimension {
method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue();
method @Dimension(unit=androidx.annotation.Dimension.DP) public float getValue();
}
@@ -275,6 +284,9 @@
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static interface DimensionBuilders.ImageDimension {
}
+ @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static interface DimensionBuilders.PivotDimension {
+ }
+
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class DimensionBuilders.ProportionalDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ImageDimension {
method @IntRange(from=0) public int getAspectRatioHeight();
method @IntRange(from=0) public int getAspectRatioWidth();
@@ -886,14 +898,18 @@
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class ModifiersBuilders.ArcModifiers {
method public androidx.wear.protolayout.ModifiersBuilders.Clickable? getClickable();
+ method public androidx.wear.protolayout.TypeBuilders.FloatProp? getOpacity();
method public androidx.wear.protolayout.ModifiersBuilders.Semantics? getSemantics();
+ method public androidx.wear.protolayout.ModifiersBuilders.Transformation? getTransformation();
}
public static final class ModifiersBuilders.ArcModifiers.Builder {
ctor public ModifiersBuilders.ArcModifiers.Builder();
method public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers build();
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers.Builder setClickable(androidx.wear.protolayout.ModifiersBuilders.Clickable);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers.Builder setOpacity(androidx.wear.protolayout.TypeBuilders.FloatProp);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers.Builder setSemantics(androidx.wear.protolayout.ModifiersBuilders.Semantics);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.ArcModifiers.Builder setTransformation(androidx.wear.protolayout.ModifiersBuilders.Transformation);
}
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class ModifiersBuilders.Background {
@@ -1019,8 +1035,10 @@
method public androidx.wear.protolayout.ModifiersBuilders.Clickable? getClickable();
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility? getContentUpdateAnimation();
method public androidx.wear.protolayout.ModifiersBuilders.ElementMetadata? getMetadata();
+ method public androidx.wear.protolayout.TypeBuilders.FloatProp? getOpacity();
method public androidx.wear.protolayout.ModifiersBuilders.Padding? getPadding();
method public androidx.wear.protolayout.ModifiersBuilders.Semantics? getSemantics();
+ method public androidx.wear.protolayout.ModifiersBuilders.Transformation? getTransformation();
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.TypeBuilders.BoolProp isVisible();
}
@@ -1032,8 +1050,10 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setClickable(androidx.wear.protolayout.ModifiersBuilders.Clickable);
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setContentUpdateAnimation(androidx.wear.protolayout.ModifiersBuilders.AnimatedVisibility);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setMetadata(androidx.wear.protolayout.ModifiersBuilders.ElementMetadata);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setOpacity(androidx.wear.protolayout.TypeBuilders.FloatProp);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setPadding(androidx.wear.protolayout.ModifiersBuilders.Padding);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setSemantics(androidx.wear.protolayout.ModifiersBuilders.Semantics);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setTransformation(androidx.wear.protolayout.ModifiersBuilders.Transformation);
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.ModifiersBuilders.Modifiers.Builder setVisible(androidx.wear.protolayout.TypeBuilders.BoolProp);
}
@@ -1135,6 +1155,28 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ModifiersBuilders.SpanModifiers.Builder setClickable(androidx.wear.protolayout.ModifiersBuilders.Clickable);
}
+ @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final class ModifiersBuilders.Transformation {
+ method public androidx.wear.protolayout.DimensionBuilders.PivotDimension? getPivotX();
+ method public androidx.wear.protolayout.DimensionBuilders.PivotDimension? getPivotY();
+ method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getRotation();
+ method public androidx.wear.protolayout.TypeBuilders.FloatProp? getScaleX();
+ method public androidx.wear.protolayout.TypeBuilders.FloatProp? getScaleY();
+ method public androidx.wear.protolayout.DimensionBuilders.DpProp? getTranslationX();
+ method public androidx.wear.protolayout.DimensionBuilders.DpProp? getTranslationY();
+ }
+
+ public static final class ModifiersBuilders.Transformation.Builder {
+ ctor public ModifiersBuilders.Transformation.Builder();
+ method public androidx.wear.protolayout.ModifiersBuilders.Transformation build();
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setPivotX(androidx.wear.protolayout.DimensionBuilders.PivotDimension);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setPivotY(androidx.wear.protolayout.DimensionBuilders.PivotDimension);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setRotation(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setScaleX(androidx.wear.protolayout.TypeBuilders.FloatProp);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setScaleY(androidx.wear.protolayout.TypeBuilders.FloatProp);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setTranslationX(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.protolayout.ModifiersBuilders.Transformation.Builder setTranslationY(androidx.wear.protolayout.DimensionBuilders.DpProp);
+ }
+
public final class ResourceBuilders {
field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static final int ANIMATED_IMAGE_FORMAT_AVD = 1; // 0x1
field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static final int ANIMATED_IMAGE_FORMAT_UNDEFINED = 0; // 0x0
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ActionBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ActionBuilders.java
index ad80dba..a33e2eb 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ActionBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ActionBuilders.java
@@ -156,6 +156,7 @@
}
/** Builder for {@link AndroidStringExtra}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements AndroidExtra.Builder {
private final ActionProto.AndroidStringExtra.Builder mImpl =
ActionProto.AndroidStringExtra.newBuilder();
@@ -239,6 +240,7 @@
}
/** Builder for {@link AndroidIntExtra}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements AndroidExtra.Builder {
private final ActionProto.AndroidIntExtra.Builder mImpl =
ActionProto.AndroidIntExtra.newBuilder();
@@ -322,6 +324,7 @@
}
/** Builder for {@link AndroidLongExtra}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements AndroidExtra.Builder {
private final ActionProto.AndroidLongExtra.Builder mImpl =
ActionProto.AndroidLongExtra.newBuilder();
@@ -405,6 +408,7 @@
}
/** Builder for {@link AndroidDoubleExtra}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements AndroidExtra.Builder {
private final ActionProto.AndroidDoubleExtra.Builder mImpl =
ActionProto.AndroidDoubleExtra.newBuilder();
@@ -489,6 +493,7 @@
}
/** Builder for {@link AndroidBooleanExtra}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements AndroidExtra.Builder {
private final ActionProto.AndroidBooleanExtra.Builder mImpl =
ActionProto.AndroidBooleanExtra.newBuilder();
@@ -762,6 +767,7 @@
}
/** Builder for {@link LaunchAction}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements Action.Builder {
private final ActionProto.LaunchAction.Builder mImpl =
ActionProto.LaunchAction.newBuilder();
@@ -855,6 +861,7 @@
}
/** Builder for {@link LoadAction}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements Action.Builder {
private final ActionProto.LoadAction.Builder mImpl =
ActionProto.LoadAction.newBuilder();
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
index 8373f43..8c116f0 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
@@ -449,6 +449,7 @@
}
/** Builder for {@link SweepGradient}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements Brush.Builder {
private final ColorProto.SweepGradient.Builder mImpl =
ColorProto.SweepGradient.newBuilder();
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/DimensionBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/DimensionBuilders.java
index 4c53afb..d8efe42 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/DimensionBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/DimensionBuilders.java
@@ -123,7 +123,11 @@
@RequiresSchemaVersion(major = 1, minor = 0)
@OptIn(markerClass = ExperimentalProtoLayoutExtensionApi.class)
public static final class DpProp
- implements ContainerDimension, ImageDimension, SpacerDimension, ExtensionDimension {
+ implements ContainerDimension,
+ ImageDimension,
+ SpacerDimension,
+ ExtensionDimension,
+ PivotDimension {
private final DimensionProto.DpProp mImpl;
@Nullable private final Fingerprint mFingerprint;
@@ -214,17 +218,26 @@
}
@Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public DimensionProto.PivotDimension toPivotDimensionProto() {
+ return DimensionProto.PivotDimension.newBuilder().setOffsetDp(mImpl).build();
+ }
+
+ @Override
@NonNull
public String toString() {
return "DpProp{" + "value=" + getValue() + ", dynamicValue=" + getDynamicValue() + "}";
}
/** Builder for {@link DpProp}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder
implements ContainerDimension.Builder,
ImageDimension.Builder,
SpacerDimension.Builder,
- ExtensionDimension.Builder {
+ ExtensionDimension.Builder,
+ PivotDimension.Builder {
private final DimensionProto.DpProp.Builder mImpl = DimensionProto.DpProp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(756413087);
@@ -363,6 +376,7 @@
* bindable layout element.
*/
@RequiresSchemaVersion(major = 1, minor = 200)
+ @SuppressWarnings("PrivateSuperclass")
public static final class HorizontalLayoutConstraint extends DpPropLayoutConstraint {
HorizontalLayoutConstraint(DimensionProto.DpProp impl, @Nullable Fingerprint fingerprint) {
super(impl, fingerprint);
@@ -421,6 +435,7 @@
* bindable layout element.
*/
@RequiresSchemaVersion(major = 1, minor = 200)
+ @SuppressWarnings("PrivateSuperclass")
public static final class VerticalLayoutConstraint extends DpPropLayoutConstraint {
VerticalLayoutConstraint(DimensionProto.DpProp impl, @Nullable Fingerprint fingerprint) {
super(impl, fingerprint);
@@ -949,6 +964,7 @@
}
/** Builder for {@link ExpandedDimensionProp}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder
implements ContainerDimension.Builder,
ImageDimension.Builder,
@@ -1062,6 +1078,7 @@
}
/** Builder for {@link WrappedDimensionProp}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements ContainerDimension.Builder {
private final DimensionProto.WrappedDimensionProp.Builder mImpl =
DimensionProto.WrappedDimensionProp.newBuilder();
@@ -1180,6 +1197,7 @@
}
/** Builder for {@link ProportionalDimensionProp}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements ImageDimension.Builder {
private final DimensionProto.ProportionalDimensionProp.Builder mImpl =
DimensionProto.ProportionalDimensionProp.newBuilder();
@@ -1394,4 +1412,153 @@
@NonNull DimensionProto.ExtensionDimension proto) {
return extensionDimensionFromProto(proto, null);
}
+
+ /** Provide a length measurement proportional to the element's bounding box. */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ public static final class BoundingBoxRatio implements PivotDimension {
+ private final DimensionProto.BoundingBoxRatio mImpl;
+ @Nullable private final Fingerprint mFingerprint;
+
+ BoundingBoxRatio(DimensionProto.BoundingBoxRatio impl, @Nullable Fingerprint fingerprint) {
+ this.mImpl = impl;
+ this.mFingerprint = fingerprint;
+ }
+
+ /**
+ * Gets the ratio relative to the bounding box width/height, with the bounding box
+ * top / start as 0 and bottom / end as 1. Values outside [0, 1] are also valid.
+ * Dynamic value is supported. If not set, defaults to the middle of the element.
+ */
+ @NonNull
+ public FloatProp getRatio() {
+ if (mImpl.hasRatio()) {
+ return FloatProp.fromProto(mImpl.getRatio());
+ } else {
+ return new FloatProp.Builder(0.5f).build();
+ }
+ }
+
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ public Fingerprint getFingerprint() {
+ return mFingerprint;
+ }
+
+ /** Creates a new wrapper instance from the proto. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static BoundingBoxRatio fromProto(
+ @NonNull DimensionProto.BoundingBoxRatio proto, @Nullable Fingerprint fingerprint) {
+ return new BoundingBoxRatio(proto, fingerprint);
+ }
+
+ @NonNull
+ static BoundingBoxRatio fromProto(@NonNull DimensionProto.BoundingBoxRatio proto) {
+ return fromProto(proto, null);
+ }
+
+ /** Returns the internal proto instance. */
+ @NonNull
+ DimensionProto.BoundingBoxRatio toProto() {
+ return mImpl;
+ }
+
+ @Override
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public DimensionProto.PivotDimension toPivotDimensionProto() {
+ return DimensionProto.PivotDimension.newBuilder().setLocationRatio(mImpl).build();
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "BoundingBoxRatio{" + "ratio=" + getRatio() + "}";
+ }
+
+ /** Builder for {@link BoundingBoxRatio}. */
+ @SuppressWarnings("HiddenSuperclass")
+ public static final class Builder implements PivotDimension.Builder {
+ private final DimensionProto.BoundingBoxRatio.Builder mImpl =
+ DimensionProto.BoundingBoxRatio.newBuilder();
+ private final Fingerprint mFingerprint = new Fingerprint(-1387873430);
+
+ /**
+ * Creates an instance of {@link Builder}.
+ * @param ratio the ratio relative to the bounding box width/height, with the bounding
+ * box top / start as 0 and bottom / end as 1. Values outside [0, 1] are also
+ * valid. Dynamic value is supported.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ public Builder(@NonNull FloatProp ratio) {
+ setRatio(ratio);
+ }
+
+ /**
+ * Sets the ratio relative to the bounding box width/height, with the bounding box
+ * top / start as 0 and bottom / end as 1. Values outside [0, 1] are also valid. Dynamic
+ * value is supported. If not set, defaults to the middle of the element.
+ */
+ @NonNull
+ private Builder setRatio(@NonNull FloatProp ratio) {
+ mImpl.setRatio(ratio.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 1, checkNotNull(ratio.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+ /** Builds an instance from accumulated values. */
+ @Override
+ @NonNull
+ public BoundingBoxRatio build() {
+ return new BoundingBoxRatio(mImpl.build(), mFingerprint);
+ }
+ }
+ }
+
+ /**
+ * Interface defining a dimension that can be applied to a pivot location for scale and rotate
+ * transformations.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ public interface PivotDimension {
+ /** Get the protocol buffer representation of this object. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ DimensionProto.PivotDimension toPivotDimensionProto();
+
+ /** Get the fingerprint for this object or null if unknown. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ Fingerprint getFingerprint();
+
+ /** Builder to create {@link PivotDimension} objects. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ interface Builder {
+
+ /** Builds an instance with values accumulated in this Builder. */
+ @NonNull
+ PivotDimension build();
+ }
+ }
+
+ /** Creates a new wrapper instance from the proto. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static PivotDimension pivotDimensionFromProto(
+ @NonNull DimensionProto.PivotDimension proto, @Nullable Fingerprint fingerprint) {
+ if (proto.hasOffsetDp()) {
+ return DpProp.fromProto(proto.getOffsetDp(), fingerprint);
+ }
+ if (proto.hasLocationRatio()) {
+ return BoundingBoxRatio.fromProto(proto.getLocationRatio(), fingerprint);
+ }
+ throw new IllegalStateException("Proto was not a recognised instance of PivotDimension");
+ }
+
+ @NonNull
+ static PivotDimension pivotDimensionFromProto(@NonNull DimensionProto.PivotDimension proto) {
+ return pivotDimensionFromProto(proto, null);
+ }
}
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
index 951f510..c95f398 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
@@ -1311,6 +1311,7 @@
}
/** Builder for {@link Text}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Text.Builder mImpl =
LayoutElementProto.Text.newBuilder();
@@ -1833,6 +1834,7 @@
}
/** Builder for {@link Image}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Image.Builder mImpl =
LayoutElementProto.Image.newBuilder();
@@ -2073,6 +2075,7 @@
}
/** Builder for {@link Spacer}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Spacer.Builder mImpl =
LayoutElementProto.Spacer.newBuilder();
@@ -2341,6 +2344,7 @@
}
/** Builder for {@link Box}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Box.Builder mImpl =
LayoutElementProto.Box.newBuilder();
@@ -2554,6 +2558,7 @@
}
/** Builder for {@link SpanText}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements Span.Builder {
private final LayoutElementProto.SpanText.Builder mImpl =
LayoutElementProto.SpanText.newBuilder();
@@ -2749,6 +2754,7 @@
}
/** Builder for {@link SpanImage}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements Span.Builder {
private final LayoutElementProto.SpanImage.Builder mImpl =
LayoutElementProto.SpanImage.newBuilder();
@@ -3072,6 +3078,7 @@
}
/** Builder for {@link Spannable}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Spannable.Builder mImpl =
LayoutElementProto.Spannable.newBuilder();
@@ -3355,6 +3362,7 @@
}
/** Builder for {@link Column}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Column.Builder mImpl =
LayoutElementProto.Column.newBuilder();
@@ -3580,6 +3588,7 @@
}
/** Builder for {@link Row}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Row.Builder mImpl =
LayoutElementProto.Row.newBuilder();
@@ -3819,6 +3828,7 @@
}
/** Builder for {@link Arc}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Arc.Builder mImpl =
LayoutElementProto.Arc.newBuilder();
@@ -4060,6 +4070,7 @@
}
/** Builder for {@link ArcText}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements ArcLayoutElement.Builder {
private final LayoutElementProto.ArcText.Builder mImpl =
LayoutElementProto.ArcText.newBuilder();
@@ -4318,6 +4329,7 @@
}
/** Builder for {@link ArcLine}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements ArcLayoutElement.Builder {
private final LayoutElementProto.ArcLine.Builder mImpl =
LayoutElementProto.ArcLine.newBuilder();
@@ -4692,6 +4704,7 @@
}
/** Builder for {@link ArcSpacer}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements ArcLayoutElement.Builder {
private final LayoutElementProto.ArcSpacer.Builder mImpl =
LayoutElementProto.ArcSpacer.newBuilder();
@@ -4841,6 +4854,7 @@
}
/** Builder for {@link ArcAdapter}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements ArcLayoutElement.Builder {
private final LayoutElementProto.ArcAdapter.Builder mImpl =
LayoutElementProto.ArcAdapter.newBuilder();
@@ -5098,6 +5112,7 @@
}
/** Builder for {@link ExtensionLayoutElement}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.ExtensionLayoutElement.Builder mImpl =
LayoutElementProto.ExtensionLayoutElement.newBuilder();
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java
index 51f3c14..53248b6 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java
@@ -29,8 +29,11 @@
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.protolayout.ActionBuilders.Action;
import androidx.wear.protolayout.ColorBuilders.ColorProp;
+import androidx.wear.protolayout.DimensionBuilders.DegreesProp;
import androidx.wear.protolayout.DimensionBuilders.DpProp;
+import androidx.wear.protolayout.DimensionBuilders.PivotDimension;
import androidx.wear.protolayout.TypeBuilders.BoolProp;
+import androidx.wear.protolayout.TypeBuilders.FloatProp;
import androidx.wear.protolayout.TypeBuilders.StringProp;
import androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec;
import androidx.wear.protolayout.expression.Fingerprint;
@@ -1317,6 +1320,279 @@
}
/**
+ * A modifier to apply transformations to the element. All of these transformations can be
+ * animated by setting dynamic values. This modifier is not layout affecting.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ public static final class Transformation {
+ private final ModifiersProto.Transformation mImpl;
+ @Nullable private final Fingerprint mFingerprint;
+
+ Transformation(ModifiersProto.Transformation impl, @Nullable Fingerprint fingerprint) {
+ this.mImpl = impl;
+ this.mFingerprint = fingerprint;
+ }
+
+ /**
+ * Gets the horizontal offset of this element relative to the location where the element's
+ * layout placed it.
+ */
+ @Nullable
+ public DpProp getTranslationX() {
+ if (mImpl.hasTranslationX()) {
+ return DpProp.fromProto(mImpl.getTranslationX());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the vertical offset of this element in addition to the location where the element's
+ * layout placed it.
+ */
+ @Nullable
+ public DpProp getTranslationY() {
+ if (mImpl.hasTranslationY()) {
+ return DpProp.fromProto(mImpl.getTranslationY());
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Gets the scale of this element in the x direction around the pivot point, as a proportion
+ * of the element's unscaled width .
+ */
+ @Nullable
+ public FloatProp getScaleX() {
+ if (mImpl.hasScaleX()) {
+ return FloatProp.fromProto(mImpl.getScaleX());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the scale of this element in the y direction around the pivot point, as a proportion
+ * of the element's unscaled height.
+ */
+ @Nullable
+ public FloatProp getScaleY() {
+ if (mImpl.hasScaleY()) {
+ return FloatProp.fromProto(mImpl.getScaleY());
+ } else {
+ return null;
+ }
+ }
+
+ /** Gets the clockwise Degrees that the element is rotated around the pivot point. */
+ @Nullable
+ public DegreesProp getRotation() {
+ if (mImpl.hasRotation()) {
+ return DegreesProp.fromProto(mImpl.getRotation());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the x offset of the point around which the element is rotated and scaled.
+ * Dynamic value is supported. By default, the pivot is centered on the element.
+ * Note that, for {@link androidx.wear.protolayout.LayoutElementBuilders.ArcText} or
+ * {@link androidx.wear.protolayout.LayoutElementBuilders.ArcLine}, the element
+ * inscribes the entire circle and the default pivot is located at the center of the
+ * circle.
+ */
+ @Nullable
+ public PivotDimension getPivotX() {
+ if (mImpl.hasPivotX()) {
+ return DimensionBuilders.pivotDimensionFromProto(mImpl.getPivotX());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the y offset of the point around which the element is rotated and scaled.
+ * Dynamic value is supported. By default, the pivot is centered on the element.
+ * Note that, for {@link androidx.wear.protolayout.LayoutElementBuilders.ArcText} or
+ * {@link androidx.wear.protolayout.LayoutElementBuilders.ArcLine}, the element
+ * inscribes the entire circle and the default pivot is located at the center of the
+ * circle.
+ */
+ @Nullable
+ public PivotDimension getPivotY() {
+ if (mImpl.hasPivotY()) {
+ return DimensionBuilders.pivotDimensionFromProto(mImpl.getPivotY());
+ } else {
+ return null;
+ }
+ }
+
+ /** Get the fingerprint for this object, or null if unknown. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ public Fingerprint getFingerprint() {
+ return mFingerprint;
+ }
+
+ /** Creates a new wrapper instance from the proto. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static Transformation fromProto(
+ @NonNull ModifiersProto.Transformation proto, @Nullable Fingerprint fingerprint) {
+ return new Transformation(proto, fingerprint);
+ }
+
+ @NonNull
+ static Transformation fromProto(@NonNull ModifiersProto.Transformation proto) {
+ return fromProto(proto, null);
+ }
+
+ /** Returns the internal proto instance. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public ModifiersProto.Transformation toProto() {
+ return mImpl;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "Transformation{"
+ + "translationX="
+ + getTranslationX()
+ + ", translationY="
+ + getTranslationY()
+ + ", scaleX="
+ + getScaleX()
+ + ", scaleY="
+ + getScaleY()
+ + ", rotation="
+ + getRotation()
+ + ", pivotX="
+ + getPivotX()
+ + ", pivotY="
+ + getPivotY()
+ + "}";
+ }
+
+ /** Builder for {@link Transformation} */
+ public static final class Builder {
+ private final ModifiersProto.Transformation.Builder mImpl =
+ ModifiersProto.Transformation.newBuilder();
+ private final Fingerprint mFingerprint = new Fingerprint(369448770);
+
+ /** Creates an instance of {@link Builder}. */
+ public Builder() {}
+
+ /**
+ * Sets the horizontal offset of this element relative to the location where the
+ * element's layout placed it.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setTranslationX(@NonNull DpProp translationX) {
+ mImpl.setTranslationX(translationX.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 1, checkNotNull(translationX.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+
+ /**
+ * Sets the vertical offset of this element in addition to the location where the
+ * element's layout placed it.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setTranslationY(@NonNull DpProp translationY) {
+ mImpl.setTranslationY(translationY.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 2, checkNotNull(translationY.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+ /**
+ * Sets the scale of this element in the x direction around the pivot point, as a
+ * proportion of the element's unscaled width .
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setScaleX(@NonNull FloatProp scaleX) {
+ mImpl.setScaleX(scaleX.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 3, checkNotNull(scaleX.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+ /**
+ * Sets the scale of this element in the y direction around the pivot point, as a
+ * proportion of the element's unscaled height.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setScaleY(@NonNull FloatProp scaleY) {
+ mImpl.setScaleY(scaleY.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 4, checkNotNull(scaleY.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+ /** Sets the clockwise degrees that the element is rotated around the pivot point. */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setRotation(@NonNull DegreesProp rotation) {
+ mImpl.setRotation(rotation.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 5, checkNotNull(rotation.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+ /**
+ * Sets the x offset of the point around which the element is rotated and scaled.
+ * Dynamic value is supported. By default, the pivot is centered on the element.
+ * Note that, for {@link androidx.wear.protolayout.LayoutElementBuilders.ArcText} or
+ * {@link androidx.wear.protolayout.LayoutElementBuilders.ArcLine}, the element
+ * inscribes the entire circle and the default pivot is located at the center of the
+ * circle.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setPivotX(@NonNull PivotDimension pivotX) {
+ mImpl.setPivotX(pivotX.toPivotDimensionProto());
+ mFingerprint.recordPropertyUpdate(
+ 6, checkNotNull(pivotX.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+ /**
+ * Sets the y offset of the point around which the element is rotated and scaled.
+ * Dynamic value is supported. By default, the pivot is centered on the element.
+ * Note that, for {@link androidx.wear.protolayout.LayoutElementBuilders.ArcText} or
+ * {@link androidx.wear.protolayout.LayoutElementBuilders.ArcLine}, the element
+ * inscribes the entire circle and the default pivot is located at the center of the
+ * circle.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setPivotY(@NonNull PivotDimension pivotY) {
+ mImpl.setPivotY(pivotY.toPivotDimensionProto());
+ mFingerprint.recordPropertyUpdate(
+ 7, checkNotNull(pivotY.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+ /** Builds an instance from accumulated values. */
+ @NonNull
+ public Transformation build() {
+ return new Transformation(mImpl.build(), mFingerprint);
+ }
+ }
+ }
+
+ /**
* {@link Modifiers} for an element. These may change the way they are drawn (e.g. {@link
* Padding} or {@link Background}), or change their behaviour (e.g. {@link Clickable}, or {@link
* Semantics}).
@@ -1429,6 +1705,29 @@
}
}
+ /** Gets the transformation applied to the element post-layout. */
+ @Nullable
+ public Transformation getTransformation() {
+ if (mImpl.hasTransformation()) {
+ return Transformation.fromProto(mImpl.getTransformation());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the opacity of the element with a value from 0 to 1, where 0 means the view is the
+ * element is completely transparent and 1 means the element is completely opaque.
+ */
+ @Nullable
+ public FloatProp getOpacity() {
+ if (mImpl.hasOpacity()) {
+ return FloatProp.fromProto(mImpl.getOpacity());
+ } else {
+ return null;
+ }
+ }
+
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
@@ -1482,6 +1781,10 @@
+ getContentUpdateAnimation()
+ ", visible="
+ isVisible()
+ + ", transformation="
+ + getTransformation()
+ + ", opacity="
+ + getOpacity()
+ "}";
}
@@ -1601,6 +1904,31 @@
return this;
}
+ /** Sets the transformation applied to the element post-layout. */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setTransformation(@NonNull Transformation transformation) {
+ mImpl.setTransformation(transformation.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 11, checkNotNull(transformation.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+ /**
+ * Sets the opacity of the element with a value from 0 to 1, where 0 means the element
+ * is completely transparent and 1 means the element is completely opaque. Dynamic value
+ * is supported.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setOpacity(@NonNull FloatProp opacity) {
+ mImpl.setOpacity(opacity.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 12, checkNotNull(opacity.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+
/** Builds an instance from accumulated values. */
@NonNull
public Modifiers build() {
@@ -2575,6 +2903,7 @@
}
/** Builder for {@link SlideParentBound}. */
+ @SuppressWarnings("HiddenSuperclass")
public static final class Builder implements SlideBound.Builder {
private final ModifiersProto.SlideParentBound.Builder mImpl =
ModifiersProto.SlideParentBound.newBuilder();
@@ -2644,6 +2973,31 @@
}
}
+
+ /** Gets the transformation applied to the element post-layout. */
+ @Nullable
+ public Transformation getTransformation() {
+ if (mImpl.hasTransformation()) {
+ return Transformation.fromProto(mImpl.getTransformation());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the opacity of the element with a value from 0 to 1, where 0 means the element
+ * is completely transparent and 1 means the element is completely opaque. Dynamic value
+ * is supported.
+ */
+ @Nullable
+ public FloatProp getOpacity() {
+ if (mImpl.hasOpacity()) {
+ return FloatProp.fromProto(mImpl.getOpacity());
+ } else {
+ return null;
+ }
+ }
+
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
@@ -2679,6 +3033,10 @@
+ getClickable()
+ ", semantics="
+ getSemantics()
+ + ", transformation="
+ + getTransformation()
+ + ", opacity="
+ + getOpacity()
+ "}";
}
@@ -2717,6 +3075,26 @@
return this;
}
+ /** Sets the transformation applied to the element post-layout. */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setTransformation(@NonNull Transformation transformation) {
+ mImpl.setTransformation(transformation.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 3, checkNotNull(transformation.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
+ /** Sets the opacity of the element. */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @NonNull
+ public Builder setOpacity(@NonNull FloatProp opacity) {
+ mImpl.setOpacity(opacity.toProto());
+ mFingerprint.recordPropertyUpdate(
+ 4, checkNotNull(opacity.getFingerprint()).aggregateValueAsInt());
+ return this;
+ }
+
/** Builds an instance from accumulated values. */
@NonNull
public ArcModifiers build() {
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/DimensionBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/DimensionBuildersTest.java
index ee93ff1..8a19995 100644
--- a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/DimensionBuildersTest.java
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/DimensionBuildersTest.java
@@ -23,9 +23,12 @@
import static org.junit.Assert.assertThrows;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.wear.protolayout.expression.AppDataKey;
import androidx.wear.protolayout.expression.DynamicBuilders;
+import androidx.wear.protolayout.expression.Fingerprint;
import androidx.wear.protolayout.proto.DimensionProto;
import org.junit.Test;
@@ -117,4 +120,44 @@
new DimensionBuilders.WrappedDimensionProp.Builder()
.setMinimumSize(minSizeDynamic));
}
+
+ @Test
+ public void pivotDimensionWithDpValue() {
+ DimensionBuilders.PivotDimension pivotDimension =
+ new DimensionBuilders.DpProp.Builder(42)
+ .setDynamicValue(
+ DynamicBuilders.DynamicFloat.from(new AppDataKey<>("some-state")))
+ .build();
+
+
+ DimensionProto.PivotDimension dimensionProto = pivotDimension.toPivotDimensionProto();
+ assertThat(dimensionProto.getInnerCase())
+ .isEqualTo(DimensionProto.PivotDimension.InnerCase.OFFSET_DP);
+ assertThat(dimensionProto.getOffsetDp().getValue())
+ .isEqualTo(42);
+ assertThat(dimensionProto.getOffsetDp().getDynamicValue().getStateSource().getSourceKey())
+ .isEqualTo("some-state");
+ }
+
+ @Test
+ public void pivotDimensionWithBoundingBoxRatio() {
+ DimensionBuilders.PivotDimension pivotDimension =
+ new DimensionBuilders.BoundingBoxRatio.Builder(
+ new TypeBuilders.FloatProp.Builder(0.8f)
+ .setDynamicValue(
+ DynamicBuilders.DynamicFloat.constant(0.2f))
+ .build())
+ .build();
+
+
+ DimensionProto.PivotDimension dimensionProto = pivotDimension.toPivotDimensionProto();
+ assertThat(dimensionProto.getInnerCase())
+ .isEqualTo(DimensionProto.PivotDimension.InnerCase.LOCATION_RATIO);
+ assertThat(dimensionProto.getLocationRatio().getRatio().getValue())
+ .isEqualTo(0.8f);
+ assertThat(dimensionProto.getLocationRatio()
+ .getRatio().getDynamicValue().getFixed().getValue())
+ .isEqualTo(0.2f);
+ }
+
}
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ModifiersBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ModifiersBuildersTest.java
index 5088dd8..970d87c 100644
--- a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ModifiersBuildersTest.java
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ModifiersBuildersTest.java
@@ -101,4 +101,55 @@
Color.GRAY))
.build()));
}
+
+ @Test
+ public void buildTransformationModifier() {
+ DimensionBuilders.DpProp translation =
+ new DimensionBuilders.DpProp.Builder(42)
+ .setDynamicValue(
+ DynamicBuilders.DynamicFloat.from(new AppDataKey<>("some-state")))
+ .build();
+ TypeBuilders.FloatProp scaleX = new TypeBuilders.FloatProp.Builder(0.8f).build();
+ TypeBuilders.FloatProp scaleY = new TypeBuilders.FloatProp.Builder(1.2f).build();
+ DimensionBuilders.DegreesProp rotation =
+ new DimensionBuilders.DegreesProp.Builder(210.f).build();
+ DimensionBuilders.PivotDimension pivotDimensionX =
+ new DimensionBuilders.DpProp.Builder(42)
+ .setDynamicValue(
+ DynamicBuilders.DynamicFloat.from(new AppDataKey<>("other-state")))
+ .build();
+ DimensionBuilders.PivotDimension pivotDimensionY =
+ new DimensionBuilders.BoundingBoxRatio.Builder(
+ new TypeBuilders.FloatProp.Builder(0.8f)
+ .setDynamicValue(
+ DynamicBuilders.DynamicFloat.constant(0.2f))
+ .build())
+ .build();
+
+ ModifiersBuilders.Transformation transformationModifier=
+ new ModifiersBuilders.Transformation.Builder()
+ .setTranslationX(translation)
+ .setTranslationY(translation)
+ .setRotation(rotation)
+ .setScaleX(scaleX)
+ .setScaleY(scaleY)
+ .setPivotX(pivotDimensionX)
+ .setPivotY(pivotDimensionY)
+ .build();
+
+ ModifiersProto.Transformation transformationProto = transformationModifier.toProto();
+ assertThat(transformationProto.getTranslationX()).isEqualTo(translation.toProto());
+ assertThat(transformationProto.getTranslationY())
+ .isEqualTo(transformationProto.getTranslationX());
+ assertThat(transformationProto.getRotation()).isEqualTo(rotation.toProto());
+ assertThat(transformationProto.getScaleX()).isNotEqualTo(transformationProto.getScaleY());
+ assertThat(transformationProto.getScaleX().getValue()).isEqualTo(0.8f);
+ assertThat(transformationProto.getScaleY().getValue()).isEqualTo(1.2f);
+ assertThat(transformationProto.getPivotX())
+ .isEqualTo(pivotDimensionX.toPivotDimensionProto());
+ assertThat(transformationProto.getPivotY())
+ .isEqualTo(pivotDimensionY.toPivotDimensionProto());
+
+
+ }
}
diff --git a/wear/tiles/tiles/api/current.ignore b/wear/tiles/tiles/api/current.ignore
index 81357e9..4a67fcb 100644
--- a/wear/tiles/tiles/api/current.ignore
+++ b/wear/tiles/tiles/api/current.ignore
@@ -1,7 +1,3 @@
// Baseline format: 1.0
-AddedMethod: androidx.wear.tiles.TileService#getActiveTilesAsync(android.content.Context, java.util.concurrent.Executor):
- Added method androidx.wear.tiles.TileService.getActiveTilesAsync(android.content.Context,java.util.concurrent.Executor)
-
-
RemovedMethod: androidx.wear.tiles.TileService#getActiveTilesSnapshotAsync(android.content.Context, java.util.concurrent.Executor):
Removed method androidx.wear.tiles.TileService.getActiveTilesSnapshotAsync(android.content.Context,java.util.concurrent.Executor)
diff --git a/wear/tiles/tiles/api/restricted_current.ignore b/wear/tiles/tiles/api/restricted_current.ignore
index 81357e9..4a67fcb 100644
--- a/wear/tiles/tiles/api/restricted_current.ignore
+++ b/wear/tiles/tiles/api/restricted_current.ignore
@@ -1,7 +1,3 @@
// Baseline format: 1.0
-AddedMethod: androidx.wear.tiles.TileService#getActiveTilesAsync(android.content.Context, java.util.concurrent.Executor):
- Added method androidx.wear.tiles.TileService.getActiveTilesAsync(android.content.Context,java.util.concurrent.Executor)
-
-
RemovedMethod: androidx.wear.tiles.TileService#getActiveTilesSnapshotAsync(android.content.Context, java.util.concurrent.Executor):
Removed method androidx.wear.tiles.TileService.getActiveTilesSnapshotAsync(android.content.Context,java.util.concurrent.Executor)
diff --git a/wear/watchface/watchface-client/api/current.ignore b/wear/watchface/watchface-client/api/current.ignore
deleted file mode 100644
index dcb0c59..0000000
--- a/wear/watchface/watchface-client/api/current.ignore
+++ /dev/null
@@ -1,19 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.wear.watchface.client.DeviceConfig#hasBurnInProtection():
- Added method androidx.wear.watchface.client.DeviceConfig.hasBurnInProtection()
-AddedMethod: androidx.wear.watchface.client.DeviceConfig#hasLowBitAmbient():
- Added method androidx.wear.watchface.client.DeviceConfig.hasLowBitAmbient()
-AddedMethod: androidx.wear.watchface.client.EditorState#shouldCommitChanges():
- Added method androidx.wear.watchface.client.EditorState.shouldCommitChanges()
-AddedMethod: androidx.wear.watchface.client.WatchUiState#inAmbientMode():
- Added method androidx.wear.watchface.client.WatchUiState.inAmbientMode()
-
-
-RemovedMethod: androidx.wear.watchface.client.DeviceConfig#getHasBurnInProtection():
- Removed method androidx.wear.watchface.client.DeviceConfig.getHasBurnInProtection()
-RemovedMethod: androidx.wear.watchface.client.DeviceConfig#getHasLowBitAmbient():
- Removed method androidx.wear.watchface.client.DeviceConfig.getHasLowBitAmbient()
-RemovedMethod: androidx.wear.watchface.client.EditorState#getShouldCommitChanges():
- Removed method androidx.wear.watchface.client.EditorState.getShouldCommitChanges()
-RemovedMethod: androidx.wear.watchface.client.WatchUiState#getInAmbientMode():
- Removed method androidx.wear.watchface.client.WatchUiState.getInAmbientMode()
diff --git a/wear/watchface/watchface-client/api/restricted_current.ignore b/wear/watchface/watchface-client/api/restricted_current.ignore
deleted file mode 100644
index dcb0c59..0000000
--- a/wear/watchface/watchface-client/api/restricted_current.ignore
+++ /dev/null
@@ -1,19 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.wear.watchface.client.DeviceConfig#hasBurnInProtection():
- Added method androidx.wear.watchface.client.DeviceConfig.hasBurnInProtection()
-AddedMethod: androidx.wear.watchface.client.DeviceConfig#hasLowBitAmbient():
- Added method androidx.wear.watchface.client.DeviceConfig.hasLowBitAmbient()
-AddedMethod: androidx.wear.watchface.client.EditorState#shouldCommitChanges():
- Added method androidx.wear.watchface.client.EditorState.shouldCommitChanges()
-AddedMethod: androidx.wear.watchface.client.WatchUiState#inAmbientMode():
- Added method androidx.wear.watchface.client.WatchUiState.inAmbientMode()
-
-
-RemovedMethod: androidx.wear.watchface.client.DeviceConfig#getHasBurnInProtection():
- Removed method androidx.wear.watchface.client.DeviceConfig.getHasBurnInProtection()
-RemovedMethod: androidx.wear.watchface.client.DeviceConfig#getHasLowBitAmbient():
- Removed method androidx.wear.watchface.client.DeviceConfig.getHasLowBitAmbient()
-RemovedMethod: androidx.wear.watchface.client.EditorState#getShouldCommitChanges():
- Removed method androidx.wear.watchface.client.EditorState.getShouldCommitChanges()
-RemovedMethod: androidx.wear.watchface.client.WatchUiState#getInAmbientMode():
- Removed method androidx.wear.watchface.client.WatchUiState.getInAmbientMode()
diff --git a/wear/watchface/watchface-complications-data/api/current.ignore b/wear/watchface/watchface-complications-data/api/current.ignore
index fb686a5..29e8c5f 100644
--- a/wear/watchface/watchface-complications-data/api/current.ignore
+++ b/wear/watchface/watchface-complications-data/api/current.ignore
@@ -1,8 +1,4 @@
// Baseline format: 1.0
-AddedMethod: androidx.wear.watchface.complications.data.ColorRamp#isInterpolated():
- Added method androidx.wear.watchface.complications.data.ColorRamp.isInterpolated()
-
-
ChangedType: androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder#setDataSource(android.content.ComponentName):
Method androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder.setDataSource has changed return type from java.lang.BuilderT to androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder
ChangedType: androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder#setDisplayPolicy(int):
@@ -59,8 +55,6 @@
Method androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder.setPersistencePolicy has changed return type from java.lang.BuilderT to androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder
-RemovedMethod: androidx.wear.watchface.complications.data.ColorRamp#getInterpolated():
- Removed method androidx.wear.watchface.complications.data.ColorRamp.getInterpolated()
RemovedMethod: androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder#setDynamicValueInvalidationFallback(BuiltT):
Removed method androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder.setDynamicValueInvalidationFallback(BuiltT)
RemovedMethod: androidx.wear.watchface.complications.data.LongTextComplicationData.Builder#setDynamicValueInvalidationFallback(BuiltT):
diff --git a/wear/watchface/watchface-complications-data/api/current.txt b/wear/watchface/watchface-complications-data/api/current.txt
index 1740d3f..9ea522a 100644
--- a/wear/watchface/watchface-complications-data/api/current.txt
+++ b/wear/watchface/watchface-complications-data/api/current.txt
@@ -60,8 +60,6 @@
}
public enum ComplicationType {
- method public static androidx.wear.watchface.complications.data.ComplicationType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.complications.data.ComplicationType[] values();
enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType EMPTY;
enum_constant @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final androidx.wear.watchface.complications.data.ComplicationType GOAL_PROGRESS;
enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType LONG_TEXT;
@@ -451,8 +449,6 @@
}
public enum SmallImageType {
- method public static androidx.wear.watchface.complications.data.SmallImageType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.complications.data.SmallImageType[] values();
enum_constant public static final androidx.wear.watchface.complications.data.SmallImageType ICON;
enum_constant public static final androidx.wear.watchface.complications.data.SmallImageType PHOTO;
}
@@ -475,8 +471,6 @@
}
public enum TimeDifferenceStyle {
- method public static androidx.wear.watchface.complications.data.TimeDifferenceStyle valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.complications.data.TimeDifferenceStyle[] values();
enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_DUAL_UNIT;
enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_SINGLE_UNIT;
enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_WORDS_SINGLE_UNIT;
@@ -500,8 +494,6 @@
}
public enum TimeFormatStyle {
- method public static androidx.wear.watchface.complications.data.TimeFormatStyle valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.complications.data.TimeFormatStyle[] values();
enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle DEFAULT;
enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle LOWER_CASE;
enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle UPPER_CASE;
diff --git a/wear/watchface/watchface-complications-data/api/restricted_current.ignore b/wear/watchface/watchface-complications-data/api/restricted_current.ignore
index fb686a5..29e8c5f 100644
--- a/wear/watchface/watchface-complications-data/api/restricted_current.ignore
+++ b/wear/watchface/watchface-complications-data/api/restricted_current.ignore
@@ -1,8 +1,4 @@
// Baseline format: 1.0
-AddedMethod: androidx.wear.watchface.complications.data.ColorRamp#isInterpolated():
- Added method androidx.wear.watchface.complications.data.ColorRamp.isInterpolated()
-
-
ChangedType: androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder#setDataSource(android.content.ComponentName):
Method androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder.setDataSource has changed return type from java.lang.BuilderT to androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder
ChangedType: androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder#setDisplayPolicy(int):
@@ -59,8 +55,6 @@
Method androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder.setPersistencePolicy has changed return type from java.lang.BuilderT to androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder
-RemovedMethod: androidx.wear.watchface.complications.data.ColorRamp#getInterpolated():
- Removed method androidx.wear.watchface.complications.data.ColorRamp.getInterpolated()
RemovedMethod: androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder#setDynamicValueInvalidationFallback(BuiltT):
Removed method androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder.setDynamicValueInvalidationFallback(BuiltT)
RemovedMethod: androidx.wear.watchface.complications.data.LongTextComplicationData.Builder#setDynamicValueInvalidationFallback(BuiltT):
diff --git a/wear/watchface/watchface-complications-data/api/restricted_current.txt b/wear/watchface/watchface-complications-data/api/restricted_current.txt
index 1740d3f..9ea522a 100644
--- a/wear/watchface/watchface-complications-data/api/restricted_current.txt
+++ b/wear/watchface/watchface-complications-data/api/restricted_current.txt
@@ -60,8 +60,6 @@
}
public enum ComplicationType {
- method public static androidx.wear.watchface.complications.data.ComplicationType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.complications.data.ComplicationType[] values();
enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType EMPTY;
enum_constant @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final androidx.wear.watchface.complications.data.ComplicationType GOAL_PROGRESS;
enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType LONG_TEXT;
@@ -451,8 +449,6 @@
}
public enum SmallImageType {
- method public static androidx.wear.watchface.complications.data.SmallImageType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.complications.data.SmallImageType[] values();
enum_constant public static final androidx.wear.watchface.complications.data.SmallImageType ICON;
enum_constant public static final androidx.wear.watchface.complications.data.SmallImageType PHOTO;
}
@@ -475,8 +471,6 @@
}
public enum TimeDifferenceStyle {
- method public static androidx.wear.watchface.complications.data.TimeDifferenceStyle valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.complications.data.TimeDifferenceStyle[] values();
enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_DUAL_UNIT;
enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_SINGLE_UNIT;
enum_constant public static final androidx.wear.watchface.complications.data.TimeDifferenceStyle SHORT_WORDS_SINGLE_UNIT;
@@ -500,8 +494,6 @@
}
public enum TimeFormatStyle {
- method public static androidx.wear.watchface.complications.data.TimeFormatStyle valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.complications.data.TimeFormatStyle[] values();
enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle DEFAULT;
enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle LOWER_CASE;
enum_constant public static final androidx.wear.watchface.complications.data.TimeFormatStyle UPPER_CASE;
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
index 29cf91f..05190ea 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
@@ -533,6 +533,7 @@
* do not have textual representation this attribute can be used for providing such. Please do
* not include the word 'complication' in the description.
*/
+ @SuppressWarnings("HiddenSuperclass")
public class Builder(
private val text: ComplicationText,
private val contentDescription: ComplicationText
@@ -717,6 +718,7 @@
* do not have textual representation this attribute can be used for providing such. Please do
* not include the word 'complication' in the description.
*/
+ @SuppressWarnings("HiddenSuperclass")
public class Builder(
private val text: ComplicationText,
private val contentDescription: ComplicationText
@@ -1002,6 +1004,7 @@
* [value] or [dynamicValue], and at least one of [monochromaticImage], [smallImage], [text] or
* [title].
*/
+ @SuppressWarnings("HiddenSuperclass")
public class Builder
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public constructor(
@@ -1385,6 +1388,7 @@
* or [dynamicValue], and at least one of [monochromaticImage], [smallImage], [text] or [title].
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @SuppressWarnings("HiddenSuperclass")
public class Builder
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public constructor(
@@ -1747,6 +1751,7 @@
* not include the word 'complication' in the description.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @SuppressWarnings("HiddenSuperclass")
public class Builder(
elements: List<Element>,
private val contentDescription: ComplicationText
@@ -1963,6 +1968,7 @@
* content description is provided, a generic content description will be used instead. Please
* do not include the word 'complication' in the description.
*/
+ @SuppressWarnings("HiddenSuperclass")
public class Builder(
private val monochromaticImage: MonochromaticImage,
private val contentDescription: ComplicationText
@@ -2080,6 +2086,7 @@
* content description is provided, a generic content description will be used instead. Please
* do not include the word 'complication' in the description.
*/
+ @SuppressWarnings("HiddenSuperclass")
public class Builder(
private val smallImage: SmallImage,
private val contentDescription: ComplicationText
@@ -2202,6 +2209,7 @@
* content description is provided, a generic content description will be used instead. Please
* do not include the word 'complication' in the description.
*/
+ @SuppressWarnings("HiddenSuperclass")
public class Builder(
private val photoImage: Icon,
private val contentDescription: ComplicationText
@@ -2327,6 +2335,7 @@
dynamicValueInvalidationFallback = null,
) {
/** Builder for [NoPermissionComplicationData]. */
+ @SuppressWarnings("HiddenSuperclass")
public class Builder : BaseBuilder<Builder, NoPermissionComplicationData>() {
private var text: ComplicationText? = null
private var title: ComplicationText? = null
diff --git a/wear/watchface/watchface-style/api/current.ignore b/wear/watchface/watchface-style/api/current.ignore
deleted file mode 100644
index 116179c..0000000
--- a/wear/watchface/watchface-style/api/current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay#isEnabled():
- Added method androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay.isEnabled()
-
-
-RemovedMethod: androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay#getEnabled():
- Removed method androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay.getEnabled()
diff --git a/wear/watchface/watchface-style/api/current.txt b/wear/watchface/watchface-style/api/current.txt
index 030379a..f980193 100644
--- a/wear/watchface/watchface-style/api/current.txt
+++ b/wear/watchface/watchface-style/api/current.txt
@@ -305,8 +305,6 @@
}
public enum WatchFaceLayer {
- method public static androidx.wear.watchface.style.WatchFaceLayer valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.style.WatchFaceLayer[] values();
enum_constant public static final androidx.wear.watchface.style.WatchFaceLayer BASE;
enum_constant public static final androidx.wear.watchface.style.WatchFaceLayer COMPLICATIONS;
enum_constant public static final androidx.wear.watchface.style.WatchFaceLayer COMPLICATIONS_OVERLAY;
diff --git a/wear/watchface/watchface-style/api/restricted_current.ignore b/wear/watchface/watchface-style/api/restricted_current.ignore
deleted file mode 100644
index 116179c..0000000
--- a/wear/watchface/watchface-style/api/restricted_current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay#isEnabled():
- Added method androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay.isEnabled()
-
-
-RemovedMethod: androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay#getEnabled():
- Removed method androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay.getEnabled()
diff --git a/wear/watchface/watchface-style/api/restricted_current.txt b/wear/watchface/watchface-style/api/restricted_current.txt
index 030379a..f980193 100644
--- a/wear/watchface/watchface-style/api/restricted_current.txt
+++ b/wear/watchface/watchface-style/api/restricted_current.txt
@@ -305,8 +305,6 @@
}
public enum WatchFaceLayer {
- method public static androidx.wear.watchface.style.WatchFaceLayer valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.style.WatchFaceLayer[] values();
enum_constant public static final androidx.wear.watchface.style.WatchFaceLayer BASE;
enum_constant public static final androidx.wear.watchface.style.WatchFaceLayer COMPLICATIONS;
enum_constant public static final androidx.wear.watchface.style.WatchFaceLayer COMPLICATIONS_OVERLAY;
diff --git a/wear/watchface/watchface/api/current.ignore b/wear/watchface/watchface/api/current.ignore
deleted file mode 100644
index 79a4c016..0000000
--- a/wear/watchface/watchface/api/current.ignore
+++ /dev/null
@@ -1,23 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.wear.watchface.ComplicationSlot#isFixedComplicationDataSource():
- Added method androidx.wear.watchface.ComplicationSlot.isFixedComplicationDataSource()
-AddedMethod: androidx.wear.watchface.ComplicationSlot#isInitiallyEnabled():
- Added method androidx.wear.watchface.ComplicationSlot.isInitiallyEnabled()
-AddedMethod: androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle#isTapEventsAccepted():
- Added method androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle.isTapEventsAccepted()
-AddedMethod: androidx.wear.watchface.WatchState#hasBurnInProtection():
- Added method androidx.wear.watchface.WatchState.hasBurnInProtection()
-AddedMethod: androidx.wear.watchface.WatchState#hasLowBitAmbient():
- Added method androidx.wear.watchface.WatchState.hasLowBitAmbient()
-
-
-RemovedMethod: androidx.wear.watchface.ComplicationSlot#getFixedComplicationDataSource():
- Removed method androidx.wear.watchface.ComplicationSlot.getFixedComplicationDataSource()
-RemovedMethod: androidx.wear.watchface.ComplicationSlot#getInitiallyEnabled():
- Removed method androidx.wear.watchface.ComplicationSlot.getInitiallyEnabled()
-RemovedMethod: androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle#getTapEventsAccepted():
- Removed method androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle.getTapEventsAccepted()
-RemovedMethod: androidx.wear.watchface.WatchState#getHasBurnInProtection():
- Removed method androidx.wear.watchface.WatchState.getHasBurnInProtection()
-RemovedMethod: androidx.wear.watchface.WatchState#getHasLowBitAmbient():
- Removed method androidx.wear.watchface.WatchState.getHasLowBitAmbient()
diff --git a/wear/watchface/watchface/api/current.txt b/wear/watchface/watchface/api/current.txt
index 83765cc..9767bf0 100644
--- a/wear/watchface/watchface/api/current.txt
+++ b/wear/watchface/watchface/api/current.txt
@@ -154,8 +154,6 @@
}
public enum DrawMode {
- method public static androidx.wear.watchface.DrawMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.DrawMode[] values();
enum_constant public static final androidx.wear.watchface.DrawMode AMBIENT;
enum_constant public static final androidx.wear.watchface.DrawMode INTERACTIVE;
enum_constant public static final androidx.wear.watchface.DrawMode LOW_BATTERY_INTERACTIVE;
diff --git a/wear/watchface/watchface/api/restricted_current.ignore b/wear/watchface/watchface/api/restricted_current.ignore
deleted file mode 100644
index 79a4c016..0000000
--- a/wear/watchface/watchface/api/restricted_current.ignore
+++ /dev/null
@@ -1,23 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.wear.watchface.ComplicationSlot#isFixedComplicationDataSource():
- Added method androidx.wear.watchface.ComplicationSlot.isFixedComplicationDataSource()
-AddedMethod: androidx.wear.watchface.ComplicationSlot#isInitiallyEnabled():
- Added method androidx.wear.watchface.ComplicationSlot.isInitiallyEnabled()
-AddedMethod: androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle#isTapEventsAccepted():
- Added method androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle.isTapEventsAccepted()
-AddedMethod: androidx.wear.watchface.WatchState#hasBurnInProtection():
- Added method androidx.wear.watchface.WatchState.hasBurnInProtection()
-AddedMethod: androidx.wear.watchface.WatchState#hasLowBitAmbient():
- Added method androidx.wear.watchface.WatchState.hasLowBitAmbient()
-
-
-RemovedMethod: androidx.wear.watchface.ComplicationSlot#getFixedComplicationDataSource():
- Removed method androidx.wear.watchface.ComplicationSlot.getFixedComplicationDataSource()
-RemovedMethod: androidx.wear.watchface.ComplicationSlot#getInitiallyEnabled():
- Removed method androidx.wear.watchface.ComplicationSlot.getInitiallyEnabled()
-RemovedMethod: androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle#getTapEventsAccepted():
- Removed method androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle.getTapEventsAccepted()
-RemovedMethod: androidx.wear.watchface.WatchState#getHasBurnInProtection():
- Removed method androidx.wear.watchface.WatchState.getHasBurnInProtection()
-RemovedMethod: androidx.wear.watchface.WatchState#getHasLowBitAmbient():
- Removed method androidx.wear.watchface.WatchState.getHasLowBitAmbient()
diff --git a/wear/watchface/watchface/api/restricted_current.txt b/wear/watchface/watchface/api/restricted_current.txt
index 83765cc..9767bf0 100644
--- a/wear/watchface/watchface/api/restricted_current.txt
+++ b/wear/watchface/watchface/api/restricted_current.txt
@@ -154,8 +154,6 @@
}
public enum DrawMode {
- method public static androidx.wear.watchface.DrawMode valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.wear.watchface.DrawMode[] values();
enum_constant public static final androidx.wear.watchface.DrawMode AMBIENT;
enum_constant public static final androidx.wear.watchface.DrawMode INTERACTIVE;
enum_constant public static final androidx.wear.watchface.DrawMode LOW_BATTERY_INTERACTIVE;
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
index c26add6..57bcabe 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
@@ -1143,7 +1143,9 @@
best = screenLockedFallback // This is NoDataComplicationData.
}
- if (!forceUpdate && selectedData == best) return
+ // When b/323483515 is fixed, go back to using regular equality rather than reference
+ // equality.
+ if (!forceUpdate && selectedData === best) return
val frozen = frozenDataSourceForEdit != null
if (!frozen || forceLoad) {
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index a76fbda..6a34ec9 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -783,14 +783,18 @@
// To save power we request a lower hardware display frame rate when the battery is low
// and not charging.
if (renderer.surfaceHolder.surface.isValid) {
- SetFrameRateHelper.setFrameRate(
- renderer.surfaceHolder.surface,
- if (it) {
+ if (it) {
+ SetFrameRateHelper.setFrameRate(
+ renderer.surfaceHolder.surface,
1000f / MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS.toFloat()
+ )
+ } else {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ ClearFrameRateHelperU.clearFrameRate(renderer.surfaceHolder.surface)
} else {
- SYSTEM_DECIDES_FRAME_RATE
+ ClearFrameRateHelperR.clearFrameRate(renderer.surfaceHolder.surface)
}
- )
+ }
}
}
@@ -1386,12 +1390,24 @@
}
}
-internal class SetFrameRateHelper {
- @RequiresApi(Build.VERSION_CODES.R)
- companion object {
- fun setFrameRate(surface: Surface, frameRate: Float) {
- surface.setFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_DEFAULT)
- }
+@RequiresApi(Build.VERSION_CODES.R)
+internal object SetFrameRateHelper {
+ fun setFrameRate(surface: Surface, frameRate: Float) {
+ surface.setFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_DEFAULT)
+ }
+}
+
+@RequiresApi(Build.VERSION_CODES.R)
+internal object ClearFrameRateHelperR {
+ fun clearFrameRate(surface: Surface) {
+ surface.setFrameRate(SYSTEM_DECIDES_FRAME_RATE, FRAME_RATE_COMPATIBILITY_DEFAULT)
+ }
+}
+
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+internal object ClearFrameRateHelperU {
+ fun clearFrameRate(surface: Surface) {
+ surface.clearFrameRate()
}
}
diff --git a/webkit/integration-tests/instrumentation/build.gradle b/webkit/integration-tests/instrumentation/build.gradle
index 683d7ee..a3fa81d 100644
--- a/webkit/integration-tests/instrumentation/build.gradle
+++ b/webkit/integration-tests/instrumentation/build.gradle
@@ -17,7 +17,6 @@
defaultConfig {
multiDexEnabled = true
- targetSdkVersion 33 // This should be the latest SDK version at all times.
}
flavorDimensions = ["targetSdk"]
diff --git a/webkit/integration-tests/testapp/build.gradle b/webkit/integration-tests/testapp/build.gradle
index 97efa1c..9b9342f 100644
--- a/webkit/integration-tests/testapp/build.gradle
+++ b/webkit/integration-tests/testapp/build.gradle
@@ -57,7 +57,6 @@
android {
defaultConfig {
minSdkVersion 19
- targetSdkVersion 29
}
lintOptions {
disable "UnusedResources"
diff --git a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
index 595b50d9..db89fed4 100644
--- a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -30,7 +30,9 @@
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute">
<!-- Top-level Activity -->
- <activity android:name=".MainActivity">
+ <activity
+ android:name=".MainActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/window/window/api/current.ignore b/window/window/api/current.ignore
deleted file mode 100644
index dbfa356..0000000
--- a/window/window/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.window.embedding.ActivityEmbeddingOptions:
- Removed class androidx.window.embedding.ActivityEmbeddingOptions
diff --git a/window/window/api/restricted_current.ignore b/window/window/api/restricted_current.ignore
deleted file mode 100644
index dbfa356..0000000
--- a/window/window/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.window.embedding.ActivityEmbeddingOptions:
- Removed class androidx.window.embedding.ActivityEmbeddingOptions
diff --git a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt
index 3b1c18b..d24f28c 100644
--- a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt
+++ b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt
@@ -71,9 +71,10 @@
.setExecutor(mExecutor)
.setTaskExecutor(mExecutor)
.build()
- mTaskExecutor = mock(TaskExecutor::class.java)
- `when`(mTaskExecutor.serialTaskExecutor).thenReturn(SerialExecutorImpl(mExecutor))
- `when`(mTaskExecutor.mainThreadExecutor).thenReturn(mExecutor)
+ mTaskExecutor = object : TaskExecutor {
+ override fun getMainThreadExecutor() = mExecutor
+ override fun getSerialTaskExecutor() = SerialExecutorImpl(mExecutor)
+ }
mScheduler = mock(Scheduler::class.java)
mForegroundProcessor = mock(ForegroundProcessor::class.java)
mWorkManager = mock(WorkManagerImpl::class.java)
@@ -101,8 +102,7 @@
val request = buildRequest<RemoteSuccessWorker>()
val wrapper = buildWrapper(request)
- wrapper.run()
- wrapper.future.get()
+ wrapper.launch().get()
val workSpec = mDatabase.workSpecDao().getWorkSpec(request.stringId)!!
assertEquals(workSpec.state, WorkInfo.State.SUCCEEDED)
}
@@ -117,8 +117,7 @@
val request = buildRequest<RemoteFailureWorker>()
val wrapper = buildWrapper(request)
- wrapper.run()
- wrapper.future.get()
+ wrapper.launch().get()
val workSpec = mDatabase.workSpecDao().getWorkSpec(request.stringId)!!
assertEquals(workSpec.state, WorkInfo.State.FAILED)
}
@@ -133,8 +132,7 @@
val request = buildRequest<RemoteRetryWorker>()
val wrapper = buildWrapper(request)
- wrapper.run()
- wrapper.future.get()
+ wrapper.launch().get()
val workSpec = mDatabase.workSpecDao().getWorkSpec(request.stringId)!!
assertEquals(workSpec.state, WorkInfo.State.ENQUEUED)
}
diff --git a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
index 147d2a9..2a374ea 100644
--- a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
+++ b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
@@ -87,9 +87,10 @@
.setTaskExecutor(mExecutor)
.setWorkerFactory(workerFactory)
.build()
- mTaskExecutor = mock(TaskExecutor::class.java)
- `when`(mTaskExecutor.serialTaskExecutor).thenReturn(SerialExecutorImpl(mExecutor))
- `when`(mTaskExecutor.mainThreadExecutor).thenReturn(mExecutor)
+ mTaskExecutor = object : TaskExecutor {
+ override fun getMainThreadExecutor() = mExecutor
+ override fun getSerialTaskExecutor() = SerialExecutorImpl(mExecutor)
+ }
mScheduler = mock(Scheduler::class.java)
mForegroundProcessor = mock(ForegroundProcessor::class.java)
mWorkManager = mock(WorkManagerImpl::class.java)
@@ -117,8 +118,7 @@
val request = buildRequest<RemoteSuccessWorker>()
val wrapper = buildWrapper(request)
- wrapper.run()
- wrapper.future.get()
+ wrapper.launch().get()
val workSpec = mDatabase.workSpecDao().getWorkSpec(request.stringId)!!
assertEquals(workSpec.state, WorkInfo.State.SUCCEEDED)
assertEquals(workSpec.output, RemoteSuccessWorker.outputData())
@@ -134,8 +134,7 @@
val request = buildRequest<RemoteFailureWorker>()
val wrapper = buildWrapper(request)
- wrapper.run()
- wrapper.future.get()
+ wrapper.launch().get()
val workSpec = mDatabase.workSpecDao().getWorkSpec(request.stringId)!!
assertEquals(workSpec.state, WorkInfo.State.FAILED)
assertEquals(workSpec.output, RemoteFailureWorker.outputData())
@@ -151,8 +150,7 @@
val request = buildRequest<RemoteRetryWorker>()
val wrapper = buildWrapper(request)
- wrapper.run()
- wrapper.future.get()
+ wrapper.launch().get()
val workSpec = mDatabase.workSpecDao().getWorkSpec(request.stringId)!!
assertEquals(workSpec.state, WorkInfo.State.ENQUEUED)
}
@@ -168,7 +166,7 @@
val request = buildRequest<RemoteStopWorker>()
val wrapper = buildWrapper(request)
- wrapper.run()
+ wrapper.launch()
val remote = workerFactory.awaitRemote(request.id) as RemoteStopWorker
remote.startRemoteDeferred.await()
wrapper.interrupt(STOP_REASON_CONSTRAINT_CONNECTIVITY)
diff --git a/work/work-runtime/api/current.ignore b/work/work-runtime/api/current.ignore
deleted file mode 100644
index 14b1e6a..0000000
--- a/work/work-runtime/api/current.ignore
+++ /dev/null
@@ -1,13 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.work.Data#getKeyValueMap():
- Method androidx.work.Data.getKeyValueMap has changed return type from java.util.Map<java.lang.String,java.lang.Object> to java.util.Map<java.lang.String,java.lang.Object>
-ChangedType: androidx.work.WorkQuery#getIds():
- Method androidx.work.WorkQuery.getIds has changed return type from java.util.List<java.util.UUID!> to java.util.List<java.util.UUID>
-ChangedType: androidx.work.WorkQuery#getStates():
- Method androidx.work.WorkQuery.getStates has changed return type from java.util.List<androidx.work.WorkInfo.State!> to java.util.List<androidx.work.WorkInfo.State>
-ChangedType: androidx.work.WorkQuery#getTags():
- Method androidx.work.WorkQuery.getTags has changed return type from java.util.List<java.lang.String!> to java.util.List<java.lang.String>
-ChangedType: androidx.work.WorkQuery#getUniqueWorkNames():
- Method androidx.work.WorkQuery.getUniqueWorkNames has changed return type from java.util.List<java.lang.String!> to java.util.List<java.lang.String>
-ChangedType: androidx.work.Worker#startWork():
- Method androidx.work.Worker.startWork has changed return type from com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> to com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result>
diff --git a/work/work-runtime/api/current.txt b/work/work-runtime/api/current.txt
index 452cc68..0fd0cead 100644
--- a/work/work-runtime/api/current.txt
+++ b/work/work-runtime/api/current.txt
@@ -7,8 +7,6 @@
}
public enum BackoffPolicy {
- method public static androidx.work.BackoffPolicy valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.BackoffPolicy[] values();
enum_constant public static final androidx.work.BackoffPolicy EXPONENTIAL;
enum_constant public static final androidx.work.BackoffPolicy LINEAR;
}
@@ -207,8 +205,6 @@
}
public enum ExistingPeriodicWorkPolicy {
- method public static androidx.work.ExistingPeriodicWorkPolicy valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.ExistingPeriodicWorkPolicy[] values();
enum_constant public static final androidx.work.ExistingPeriodicWorkPolicy CANCEL_AND_REENQUEUE;
enum_constant public static final androidx.work.ExistingPeriodicWorkPolicy KEEP;
enum_constant @Deprecated public static final androidx.work.ExistingPeriodicWorkPolicy REPLACE;
@@ -216,8 +212,6 @@
}
public enum ExistingWorkPolicy {
- method public static androidx.work.ExistingWorkPolicy valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.ExistingWorkPolicy[] values();
enum_constant public static final androidx.work.ExistingWorkPolicy APPEND;
enum_constant public static final androidx.work.ExistingWorkPolicy APPEND_OR_REPLACE;
enum_constant public static final androidx.work.ExistingWorkPolicy KEEP;
@@ -275,8 +269,6 @@
}
public enum NetworkType {
- method public static androidx.work.NetworkType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.NetworkType[] values();
enum_constant public static final androidx.work.NetworkType CONNECTED;
enum_constant public static final androidx.work.NetworkType METERED;
enum_constant public static final androidx.work.NetworkType NOT_REQUIRED;
@@ -330,8 +322,6 @@
}
public enum OutOfQuotaPolicy {
- method public static androidx.work.OutOfQuotaPolicy valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.OutOfQuotaPolicy[] values();
enum_constant public static final androidx.work.OutOfQuotaPolicy DROP_WORK_REQUEST;
enum_constant public static final androidx.work.OutOfQuotaPolicy RUN_AS_NON_EXPEDITED_WORK_REQUEST;
}
@@ -452,9 +442,7 @@
}
public enum WorkInfo.State {
- method public final boolean isFinished();
- method public static androidx.work.WorkInfo.State valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.WorkInfo.State[] values();
+ method public boolean isFinished();
property public final boolean isFinished;
enum_constant public static final androidx.work.WorkInfo.State BLOCKED;
enum_constant public static final androidx.work.WorkInfo.State CANCELLED;
diff --git a/work/work-runtime/api/restricted_current.ignore b/work/work-runtime/api/restricted_current.ignore
deleted file mode 100644
index 14b1e6a..0000000
--- a/work/work-runtime/api/restricted_current.ignore
+++ /dev/null
@@ -1,13 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.work.Data#getKeyValueMap():
- Method androidx.work.Data.getKeyValueMap has changed return type from java.util.Map<java.lang.String,java.lang.Object> to java.util.Map<java.lang.String,java.lang.Object>
-ChangedType: androidx.work.WorkQuery#getIds():
- Method androidx.work.WorkQuery.getIds has changed return type from java.util.List<java.util.UUID!> to java.util.List<java.util.UUID>
-ChangedType: androidx.work.WorkQuery#getStates():
- Method androidx.work.WorkQuery.getStates has changed return type from java.util.List<androidx.work.WorkInfo.State!> to java.util.List<androidx.work.WorkInfo.State>
-ChangedType: androidx.work.WorkQuery#getTags():
- Method androidx.work.WorkQuery.getTags has changed return type from java.util.List<java.lang.String!> to java.util.List<java.lang.String>
-ChangedType: androidx.work.WorkQuery#getUniqueWorkNames():
- Method androidx.work.WorkQuery.getUniqueWorkNames has changed return type from java.util.List<java.lang.String!> to java.util.List<java.lang.String>
-ChangedType: androidx.work.Worker#startWork():
- Method androidx.work.Worker.startWork has changed return type from com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> to com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result>
diff --git a/work/work-runtime/api/restricted_current.txt b/work/work-runtime/api/restricted_current.txt
index 452cc68..0fd0cead 100644
--- a/work/work-runtime/api/restricted_current.txt
+++ b/work/work-runtime/api/restricted_current.txt
@@ -7,8 +7,6 @@
}
public enum BackoffPolicy {
- method public static androidx.work.BackoffPolicy valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.BackoffPolicy[] values();
enum_constant public static final androidx.work.BackoffPolicy EXPONENTIAL;
enum_constant public static final androidx.work.BackoffPolicy LINEAR;
}
@@ -207,8 +205,6 @@
}
public enum ExistingPeriodicWorkPolicy {
- method public static androidx.work.ExistingPeriodicWorkPolicy valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.ExistingPeriodicWorkPolicy[] values();
enum_constant public static final androidx.work.ExistingPeriodicWorkPolicy CANCEL_AND_REENQUEUE;
enum_constant public static final androidx.work.ExistingPeriodicWorkPolicy KEEP;
enum_constant @Deprecated public static final androidx.work.ExistingPeriodicWorkPolicy REPLACE;
@@ -216,8 +212,6 @@
}
public enum ExistingWorkPolicy {
- method public static androidx.work.ExistingWorkPolicy valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.ExistingWorkPolicy[] values();
enum_constant public static final androidx.work.ExistingWorkPolicy APPEND;
enum_constant public static final androidx.work.ExistingWorkPolicy APPEND_OR_REPLACE;
enum_constant public static final androidx.work.ExistingWorkPolicy KEEP;
@@ -275,8 +269,6 @@
}
public enum NetworkType {
- method public static androidx.work.NetworkType valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.NetworkType[] values();
enum_constant public static final androidx.work.NetworkType CONNECTED;
enum_constant public static final androidx.work.NetworkType METERED;
enum_constant public static final androidx.work.NetworkType NOT_REQUIRED;
@@ -330,8 +322,6 @@
}
public enum OutOfQuotaPolicy {
- method public static androidx.work.OutOfQuotaPolicy valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.OutOfQuotaPolicy[] values();
enum_constant public static final androidx.work.OutOfQuotaPolicy DROP_WORK_REQUEST;
enum_constant public static final androidx.work.OutOfQuotaPolicy RUN_AS_NON_EXPEDITED_WORK_REQUEST;
}
@@ -452,9 +442,7 @@
}
public enum WorkInfo.State {
- method public final boolean isFinished();
- method public static androidx.work.WorkInfo.State valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.work.WorkInfo.State[] values();
+ method public boolean isFinished();
property public final boolean isFinished;
enum_constant public static final androidx.work.WorkInfo.State BLOCKED;
enum_constant public static final androidx.work.WorkInfo.State CANCELLED;
diff --git a/work/work-runtime/build.gradle b/work/work-runtime/build.gradle
index b9bb2b9..1878b3c 100644
--- a/work/work-runtime/build.gradle
+++ b/work/work-runtime/build.gradle
@@ -74,6 +74,7 @@
implementation("androidx.lifecycle:lifecycle-service:2.6.2")
api(libs.kotlinStdlib)
api(libs.kotlinCoroutinesAndroid)
+ androidTestImplementation(libs.kotlinCoroutinesTest)
androidTestImplementation(libs.multidex)
androidTestImplementation(libs.truth)
androidTestImplementation(libs.testExtJunit)
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
index 4185694..cfc03fa 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
@@ -17,12 +17,12 @@
package androidx.work
import android.content.Context
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.Observer
+import androidx.concurrent.futures.await
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
+import androidx.work.ListenableWorker.Result
import androidx.work.WorkInfo.State
import androidx.work.WorkManager.UpdateResult.APPLIED_FOR_NEXT_RUN
import androidx.work.WorkManager.UpdateResult.APPLIED_IMMEDIATELY
@@ -33,23 +33,26 @@
import androidx.work.impl.testutils.TestConstraintTracker
import androidx.work.impl.testutils.TestOverrideClock
import androidx.work.impl.testutils.TrackingWorkerFactory
-import androidx.work.impl.utils.taskexecutor.TaskExecutor
import androidx.work.impl.workers.ARGUMENT_CLASS_NAME
import androidx.work.impl.workers.ConstraintTrackingWorker
import androidx.work.testutils.GreedyScheduler
import androidx.work.testutils.TestEnv
import androidx.work.testutils.WorkManager
-import androidx.work.worker.LatchWorker
+import androidx.work.testutils.awaitWorkerEnqueued
+import androidx.work.testutils.awaitWorkerFinished
+import androidx.work.worker.CompletableWorker
import androidx.work.worker.RetryWorker
import androidx.work.worker.TestWorker
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import java.util.concurrent.CountDownLatch
-import java.util.concurrent.ExecutionException
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.DAYS
import java.util.concurrent.TimeUnit.HOURS
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,12 +81,12 @@
@Test
@MediumTest
- fun constraintsUpdate() {
+ fun constraintsUpdate() = runTest {
// requiresCharging constraint is faked, so it will never be satisfied
val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setConstraints(Constraints(requiresCharging = true))
.build()
- workManager.enqueue(oneTimeWorkRequest).result.get()
+ workManager.enqueue(oneTimeWorkRequest).result.await()
val requestId = oneTimeWorkRequest.id
val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
@@ -91,29 +94,29 @@
.build()
val operation = workManager.updateWork(updatedRequest)
- assertThat(operation.get()).isEqualTo(APPLIED_IMMEDIATELY)
+ assertThat(operation.await()).isEqualTo(APPLIED_IMMEDIATELY)
workManager.awaitSuccess(requestId)
}
@Test
@MediumTest
- fun updateRunningOneTimeWork() {
- val oneTimeWorkRequest = OneTimeWorkRequest.Builder(LatchWorker::class.java).build()
- workManager.enqueue(oneTimeWorkRequest).result.get()
- val worker = workerFactory.awaitWorker(oneTimeWorkRequest.id) as LatchWorker
+ fun updateRunningOneTimeWork() = runTest {
+ val oneTimeWorkRequest = OneTimeWorkRequest.Builder(CompletableWorker::class.java).build()
+ workManager.enqueue(oneTimeWorkRequest).result.await()
+ val worker = workerFactory.await(oneTimeWorkRequest.id) as CompletableWorker
// requiresCharging constraint is faked, so it will never be satisfied
val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setId(oneTimeWorkRequest.id)
.setConstraints(Constraints(requiresCharging = true))
.build()
- assertThat(workManager.updateWork(updatedRequest).get()).isEqualTo(APPLIED_FOR_NEXT_RUN)
- worker.mLatch.countDown()
+ assertThat(workManager.updateWork(updatedRequest).await()).isEqualTo(APPLIED_FOR_NEXT_RUN)
+ worker.result.complete(Result.success())
workManager.awaitSuccess(oneTimeWorkRequest.id)
}
@Test
@MediumTest
- fun failFinished() {
+ fun failFinished() = runTest {
val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
workManager.enqueue(oneTimeWorkRequest)
workManager.awaitSuccess(oneTimeWorkRequest.id)
@@ -121,53 +124,60 @@
.setId(oneTimeWorkRequest.id)
.setConstraints(Constraints(requiresCharging = true))
.build()
- assertThat(workManager.updateWork(updatedRequest).get()).isEqualTo(NOT_APPLIED)
+ assertThat(workManager.updateWork(updatedRequest).await()).isEqualTo(NOT_APPLIED)
}
@Test
@MediumTest
- fun failWorkDoesntExit() {
+ fun failWorkDoesntExit() = runTest {
val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
workManager.enqueue(oneTimeWorkRequest)
workManager.awaitSuccess(oneTimeWorkRequest.id)
val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setId(UUID.randomUUID()).build()
try {
- workManager.updateWork(updatedRequest).get()
+ workManager.updateWork(updatedRequest).await()
throw AssertionError()
- } catch (e: ExecutionException) {
- assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException::class.java)
+ } catch (e: IllegalArgumentException) {
+ // expected
}
}
@Test
@MediumTest
- fun updateTags() {
+ fun updateTags() = runTest {
val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setInitialDelay(10, DAYS)
.addTag("previous")
.build()
- workManager.enqueue(oneTimeWorkRequest).result.get()
+ workManager.enqueue(oneTimeWorkRequest).result.await()
val updatedWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setInitialDelay(10, DAYS)
.setId(oneTimeWorkRequest.id)
.addTag("test")
.build()
- assertThat(workManager.updateWork(updatedWorkRequest).get()).isEqualTo(APPLIED_IMMEDIATELY)
+ assertThat(workManager.updateWork(updatedWorkRequest).await())
+ .isEqualTo(APPLIED_IMMEDIATELY)
- val info = workManager.getWorkInfoById(oneTimeWorkRequest.id).get()
+ val info = workManager.getWorkInfoByIdFlow(oneTimeWorkRequest.id).first()
assertThat(info.tags).contains("test")
assertThat(info.tags).doesNotContain("previous")
}
+ // this test verifies scenario when tags for the worker
+ // is read at the same moment when Processor considers worker running,
+ // which is different from moment when WorkDatabase is updated.
+ // Otherwise we can run older version of the worker with new tags.
+ // This is the reason why it has special behavior when all execution
+ // on serialTaskExecutor is blocked.
@Test
@MediumTest
- fun updateTagsWhileRunning() {
+ fun updateTagsWhileRunning() = runTest {
val request = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setConstraints(Constraints(requiresCharging = true))
.addTag("original").build()
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
val serialExecutorBlocker = CountDownLatch(1)
// stop any execution on serialTaskExecutor
taskExecutor.serialTaskExecutor.execute {
@@ -183,116 +193,96 @@
// will add update task to the serialTaskExecutor queue
val updateResult = workManager.updateWork(updatedRequest)
serialExecutorBlocker.countDown()
- val worker = workerFactory.awaitWorker(request.id)
+ val worker = workerFactory.await(request.id)
assertThat(worker.tags).contains("original")
assertThat(worker.tags).doesNotContain("updated")
- assertThat(updateResult.get()).isEqualTo(APPLIED_FOR_NEXT_RUN)
+ assertThat(updateResult.await()).isEqualTo(APPLIED_FOR_NEXT_RUN)
}
@Test
@MediumTest
- fun updateWorkerClass() {
+ fun updateWorkerClass() = runTest {
// requiresCharging constraint is faked, so it will never be satisfied
val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setConstraints(Constraints(requiresCharging = true))
.build()
- workManager.enqueue(oneTimeWorkRequest).result.get()
+ workManager.enqueue(oneTimeWorkRequest).result.await()
val requestId = oneTimeWorkRequest.id
- val updatedRequest = OneTimeWorkRequest.Builder(LatchWorker::class.java)
+ val updatedRequest = OneTimeWorkRequest.Builder(CompletableWorker::class.java)
.setId(requestId)
.build()
- assertThat(workManager.updateWork(updatedRequest).get()).isEqualTo(APPLIED_IMMEDIATELY)
+ assertThat(workManager.updateWork(updatedRequest).await()).isEqualTo(APPLIED_IMMEDIATELY)
// verifying that new worker has been started
- val worker = workerFactory.awaitWorker(oneTimeWorkRequest.id) as LatchWorker
- worker.mLatch.countDown()
+ val worker = workerFactory.await(oneTimeWorkRequest.id) as CompletableWorker
+ worker.result.complete(Result.success())
workManager.awaitSuccess(requestId)
}
@Test
@MediumTest
- fun progressReset() {
+ fun progressReset() = runTest {
// requiresCharging constraint is faked, so it will be controlled in the test
val request = OneTimeWorkRequest.Builder(ProgressWorker::class.java)
.setConstraints(Constraints(requiresCharging = true))
.build()
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
fakeChargingTracker.constraintState = true
- val runningLatch = CountDownLatch(1)
- lateinit var runningObserver: Observer<WorkInfo>
- val liveData = workManager.getWorkInfoByIdLiveData(request.id)
- runningObserver = Observer {
- // not only running, but setProgressAsync call was made
- if (it.state == State.RUNNING && it.progress.size() != 0) {
- runningLatch.countDown()
- liveData.removeObserver(runningObserver)
- }
+ workManager.getWorkInfoByIdFlow(request.id).first {
+ it.state == State.RUNNING && it.progress.size() != 0
}
- taskExecutor.mainThreadExecutor.execute { liveData.observeForever(runningObserver) }
- assertThat(runningLatch.await(5, TimeUnit.SECONDS)).isTrue()
// will trigger worker to be stopped
fakeChargingTracker.state = false
+ val info = workManager.awaitWorkerEnqueued(request.id)
- // wait worker to be stopped
- val stoppedLatch = CountDownLatch(1)
- lateinit var stoppedObserver: Observer<WorkInfo>
- stoppedObserver = Observer {
- if (it.state == State.ENQUEUED) {
- stoppedLatch.countDown()
- liveData.removeObserver(stoppedObserver)
- }
- }
- taskExecutor.mainThreadExecutor.execute { liveData.observeForever(stoppedObserver) }
- assertThat(stoppedLatch.await(5, TimeUnit.SECONDS)).isTrue()
- val info = workManager.getWorkInfoById(request.id).get()
assertThat(info.progress).isEqualTo(TEST_DATA)
val updatedRequest = OneTimeWorkRequest.Builder(ProgressWorker::class.java)
.setId(request.id)
.addTag("bla")
.build()
- assertThat(workManager.updateWork(updatedRequest).get()).isEqualTo(APPLIED_IMMEDIATELY)
- val updatedInfo = workManager.getWorkInfoById(request.id).get()
+ assertThat(workManager.updateWork(updatedRequest).await()).isEqualTo(APPLIED_IMMEDIATELY)
+ val updatedInfo = workManager.getWorkInfoByIdFlow(request.id).first()
assertThat(updatedInfo.tags).contains("bla")
assertThat(updatedInfo.progress).isEqualTo(Data.EMPTY)
}
@Test
@MediumTest
- fun continuationLeafUpdate() {
+ fun continuationLeafUpdate() = runTest {
// requiresCharging constraint is faked, so it will never be satisfied
val step1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setConstraints(Constraints(requiresCharging = true)).build()
val step2 = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
- workManager.beginWith(step1).then(step2).enqueue().result.get()
+ workManager.beginWith(step1).then(step2).enqueue().result.await()
val updatedStep2 = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setId(step2.id).addTag("updated").build()
- assertThat(workManager.updateWork(updatedStep2).get()).isEqualTo(APPLIED_IMMEDIATELY)
- val workInfo = workManager.getWorkInfoById(step2.id).get()
+ assertThat(workManager.updateWork(updatedStep2).await()).isEqualTo(APPLIED_IMMEDIATELY)
+ val workInfo = workManager.getWorkInfoById(step2.id).await()
assertThat(workInfo.state).isEqualTo(State.BLOCKED)
assertThat(workInfo.tags).contains("updated")
}
@Test
@MediumTest
- fun continuationLeafRoot() {
+ fun continuationLeafRoot() = runTest {
// requiresCharging constraint is faked, so it will never be satisfied
val step1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setConstraints(Constraints(requiresCharging = true)).build()
val step2 = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
- workManager.beginWith(step1).then(step2).enqueue().result.get()
- val workInfo = workManager.getWorkInfoById(step2.id).get()
+ workManager.beginWith(step1).then(step2).enqueue().result.await()
+ val workInfo = workManager.getWorkInfoById(step2.id).await()
assertThat(workInfo.state).isEqualTo(State.BLOCKED)
val updatedStep1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setId(step1.id).build()
- assertThat(workManager.updateWork(updatedStep1).get()).isEqualTo(APPLIED_IMMEDIATELY)
+ assertThat(workManager.updateWork(updatedStep1).await()).isEqualTo(APPLIED_IMMEDIATELY)
workManager.awaitSuccess(step2.id)
}
@Test
@MediumTest
- fun chainsViaExistingPolicyLeafUpdate() {
+ fun chainsViaExistingPolicyLeafUpdate() = runTest {
// requiresCharging constraint is faked, so it will never be satisfied
val step1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setConstraints(Constraints(requiresCharging = true)).build()
@@ -301,110 +291,93 @@
workManager.enqueueUniqueWork("name", ExistingWorkPolicy.APPEND, step2)
val updatedStep2 = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setId(step2.id).addTag("updated").build()
- assertThat(workManager.updateWork(updatedStep2).get()).isEqualTo(APPLIED_IMMEDIATELY)
- val workInfo = workManager.getWorkInfoById(step2.id).get()
+ assertThat(workManager.updateWork(updatedStep2).await()).isEqualTo(APPLIED_IMMEDIATELY)
+ val workInfo = workManager.getWorkInfoById(step2.id).await()
assertThat(workInfo.state).isEqualTo(State.BLOCKED)
assertThat(workInfo.tags).contains("updated")
}
@Test
@MediumTest
- fun chainsViaExistingPolicyRootUpdate() {
+ fun chainsViaExistingPolicyRootUpdate() = runTest {
// requiresCharging constraint is faked, so it will never be satisfied
val step1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setConstraints(Constraints(requiresCharging = true)).build()
val step2 = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
workManager.enqueueUniqueWork("name", ExistingWorkPolicy.APPEND, step1)
workManager.enqueueUniqueWork("name", ExistingWorkPolicy.APPEND, step2)
- val workInfo = workManager.getWorkInfoById(step2.id).get()
+ val workInfo = workManager.getWorkInfoById(step2.id).await()
assertThat(workInfo.state).isEqualTo(State.BLOCKED)
val updatedStep1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setId(step1.id).build()
- assertThat(workManager.updateWork(updatedStep1).get()).isEqualTo(APPLIED_IMMEDIATELY)
+ assertThat(workManager.updateWork(updatedStep1).await()).isEqualTo(APPLIED_IMMEDIATELY)
workManager.awaitSuccess(step2.id)
}
@Test
@MediumTest
- fun oneTimeWorkToPeriodic() {
+ fun oneTimeWorkToPeriodic() = runTest {
// requiresCharging constraint is faked, so it will never be satisfied
val request = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setConstraints(Constraints(requiresCharging = true)).build()
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
val updatedRequest =
PeriodicWorkRequest.Builder(TestWorker::class.java, 1, DAYS)
.build()
try {
- workManager.updateWork(updatedRequest).get()
+ workManager.updateWork(updatedRequest).await()
throw AssertionError()
- } catch (e: ExecutionException) {
- assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException::class.java)
+ } catch (e: IllegalArgumentException) {
+ // expected
}
}
@Test
@MediumTest
- fun periodicWorkToOneTime() {
+ fun periodicWorkToOneTime() = runTest {
// requiresCharging constraint is faked, so it will never be satisfied
val request = PeriodicWorkRequest.Builder(TestWorker::class.java, 1, DAYS)
.setConstraints(Constraints(requiresCharging = true))
.build()
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
try {
- workManager.updateWork(updatedRequest).get()
+ workManager.updateWork(updatedRequest).await()
throw AssertionError()
- } catch (e: ExecutionException) {
- assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException::class.java)
+ } catch (e: IllegalArgumentException) {
+ // expected
}
}
@Test
@MediumTest
- fun updateRunningPeriodicWorkRequest() {
- val request = PeriodicWorkRequest.Builder(LatchWorker::class.java, 1, DAYS)
+ fun updateRunningPeriodicWorkRequest() = runTest {
+ val request = PeriodicWorkRequest.Builder(CompletableWorker::class.java, 1, DAYS)
.addTag("original").build()
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
val updatedRequest =
- PeriodicWorkRequest.Builder(LatchWorker::class.java, 1, DAYS)
+ PeriodicWorkRequest.Builder(CompletableWorker::class.java, 1, DAYS)
.setId(request.id).addTag("updated").build()
- val worker = workerFactory.awaitWorker(request.id) as LatchWorker
- assertThat(workManager.updateWork(updatedRequest).get()).isEqualTo(APPLIED_FOR_NEXT_RUN)
- val latch = CountDownLatch(1)
- taskExecutor.serialTaskExecutor.execute { latch.countDown() }
- assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue()
+ val worker = workerFactory.await(request.id) as CompletableWorker
+ assertThat(workManager.updateWork(updatedRequest).await()).isEqualTo(APPLIED_FOR_NEXT_RUN)
assertThat(worker.isStopped).isFalse()
assertThat(worker.tags).contains("original")
assertThat(worker.tags).doesNotContain("updated")
- worker.mLatch.countDown()
- val reenqueueLatch = CountDownLatch(1)
- val workInfoLD = workManager.getWorkInfoByIdLiveData(request.id)
- lateinit var observer: Observer<WorkInfo>
- observer = Observer {
- if (it.state == State.ENQUEUED) {
- reenqueueLatch.countDown()
- workInfoLD.removeObserver(observer)
- }
- }
- taskExecutor.mainThreadExecutor.execute { workInfoLD.observeForever(observer) }
- assertThat(reenqueueLatch.await(3, TimeUnit.SECONDS)).isTrue()
- val newTags = workManager.getWorkInfoById(request.id).get().tags
+ worker.result.complete(Result.success())
+ workManager.awaitWorkerEnqueued(request.id)
+ val newTags = workManager.getWorkInfoById(request.id).await().tags
assertThat(newTags).contains("updated")
assertThat(newTags).doesNotContain("original")
}
@MediumTest
@Test
- fun updatePeriodicWorkAfterFirstPeriod() {
+ fun updatePeriodicWorkAfterFirstPeriod() = runTest {
val request = PeriodicWorkRequest.Builder(TestWorker::class.java, 1, DAYS)
.addTag("original").build()
- val onExecutedLatch = CountDownLatch(1)
- env.processor.addExecutionListener { id, _ ->
- if (id.workSpecId == request.stringId) onExecutedLatch.countDown()
- }
- workManager.enqueue(request).result.get()
- workerFactory.awaitWorker(request.id)
- workManager.awaitReenqueued(request.id)
+ workManager.enqueue(request).result.await()
+ workerFactory.await(request.id)
+ workManager.awaitWorkerEnqueued(request.id)
val updatedRequest =
PeriodicWorkRequest.Builder(TestWorker::class.java, 1, DAYS)
@@ -412,9 +385,9 @@
.setConstraints(Constraints(requiresCharging = true))
.setId(request.id).addTag("updated").build()
- assertThat(workManager.updateWork(updatedRequest).get()).isEqualTo(APPLIED_IMMEDIATELY)
+ assertThat(workManager.updateWork(updatedRequest).await()).isEqualTo(APPLIED_IMMEDIATELY)
- val newTags = workManager.getWorkInfoById(request.id).get().tags
+ val newTags = workManager.getWorkInfoById(request.id).await().tags
assertThat(newTags).contains("updated")
assertThat(newTags).doesNotContain("original")
val workSpec = env.db.workSpecDao().getWorkSpec(request.stringId)!!
@@ -423,15 +396,15 @@
@MediumTest
@Test
- fun updateRetryingOneTimeWork() {
+ fun updateRetryingOneTimeWork() = runTest {
val request = OneTimeWorkRequest.Builder(RetryWorker::class.java)
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, DAYS)
.build()
workManager.enqueue(request)
// await worker to be created
- val worker1 = workerFactory.awaitWorker(request.id)
+ val worker1 = workerFactory.await(request.id)
assertThat(worker1.runAttemptCount).isEqualTo(0)
- workManager.awaitReenqueued(request.id)
+ workManager.awaitWorkerEnqueued(request.id)
// rewind time so can updated worker can run
val spec = workManager.workDatabase.workSpecDao().getWorkSpec(request.stringId)!!
val delta = spec.calculateNextRunTime() - System.currentTimeMillis()
@@ -443,19 +416,19 @@
val updated = OneTimeWorkRequest.Builder(TestWorker::class.java).setId(request.id)
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, DAYS)
.build()
- workManager.updateWork(updated).get()
+ workManager.updateWork(updated).await()
workManager.awaitSuccess(request.id)
- val worker2 = workerFactory.awaitWorker(request.id)
+ val worker2 = workerFactory.await(request.id)
assertThat(worker2.runAttemptCount).isEqualTo(1)
}
@MediumTest
@Test
- fun updateCorrectNextRunTime() {
+ fun updateCorrectNextRunTime() = runTest {
val request = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setInitialDelay(10, TimeUnit.MINUTES).build()
val enqueueTime = System.currentTimeMillis()
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
workManager.workDatabase.workSpecDao().setLastEnqueueTime(
request.stringId,
enqueueTime - TimeUnit.MINUTES.toMillis(5)
@@ -464,7 +437,7 @@
.setInitialDelay(20, TimeUnit.MINUTES)
.setId(request.id)
.build()
- workManager.updateWork(updated).get()
+ workManager.updateWork(updated).await()
val workSpec = workManager.workDatabase.workSpecDao().getWorkSpec(request.stringId)!!
val delta = workSpec.calculateNextRunTime() - enqueueTime
// enqueue time isn't very accurate but delta should be about 15 minutes, because
@@ -476,15 +449,15 @@
@Test
@MediumTest
@SdkSuppress(minSdkVersion = 23, maxSdkVersion = 25)
- fun testUpdatePeriodicWorker_preservesConstraintTrackingWorker() {
+ fun testUpdatePeriodicWorker_preservesConstraintTrackingWorker() = runTest {
val originRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setInitialDelay(10, HOURS).build()
- workManager.enqueue(originRequest).result.get()
+ workManager.enqueue(originRequest).result.await()
val updateRequest = OneTimeWorkRequest.Builder(RetryWorker::class.java)
.setId(originRequest.id).setInitialDelay(10, HOURS)
.setConstraints(Constraints(requiresBatteryNotLow = true))
.build()
- workManager.updateWork(updateRequest).get()
+ workManager.updateWork(updateRequest).await()
val workSpec = env.db.workSpecDao().getWorkSpec(originRequest.stringId)!!
assertThat(workSpec.workerClassName).isEqualTo(ConstraintTrackingWorker::class.java.name)
assertThat(workSpec.input.getString(ARGUMENT_CLASS_NAME))
@@ -493,33 +466,34 @@
@Test
@MediumTest
- fun updateWorkerGeneration() {
+ fun updateWorkerGeneration() = runTest {
val oneTimeWorkRequest = OneTimeWorkRequest.Builder(WorkerWithParam::class.java)
.setInitialDelay(10, DAYS)
.build()
- workManager.enqueue(oneTimeWorkRequest).result.get()
+ workManager.enqueue(oneTimeWorkRequest).result.await()
val updatedWorkRequest = OneTimeWorkRequest.Builder(WorkerWithParam::class.java)
.setId(oneTimeWorkRequest.id)
.build()
- assertThat(workManager.updateWork(updatedWorkRequest).get()).isEqualTo(APPLIED_IMMEDIATELY)
- val worker = workerFactory.awaitWorker(oneTimeWorkRequest.id) as WorkerWithParam
+ assertThat(workManager.updateWork(updatedWorkRequest).await())
+ .isEqualTo(APPLIED_IMMEDIATELY)
+ val worker = workerFactory.await(oneTimeWorkRequest.id) as WorkerWithParam
assertThat(worker.generation).isEqualTo(1)
- val workInfo = workManager.getWorkInfoById(oneTimeWorkRequest.id).get()
+ val workInfo = workManager.getWorkInfoById(oneTimeWorkRequest.id).await()
assertThat(workInfo.generation).isEqualTo(1)
}
@Test
@SmallTest
- fun updateNextScheduleTimeOverride() {
+ fun updateNextScheduleTimeOverride() = runTest {
testClock.currentTimeMillis = HOURS.toMillis(5)
val nextRunTimeMillis = HOURS.toMillis(10)
val request = PeriodicWorkRequest.Builder(
TestWorker::class.java, 1, DAYS
).setInitialDelay(2, DAYS).build()
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
workManager.updateWork(
PeriodicWorkRequest.Builder(
@@ -527,15 +501,15 @@
).setId(request.id)
.setNextScheduleTimeOverride(nextRunTimeMillis)
.build()
- ).get()
+ ).await()
- val workInfo = workManager.getWorkInfoById(request.id).get()
+ val workInfo = workManager.getWorkInfoById(request.id).await()
assertThat(workInfo.nextScheduleTimeMillis).isEqualTo(nextRunTimeMillis)
}
@Test
@SmallTest
- fun updateNextScheduleTimeOverride_multipleGenerations() {
+ fun updateNextScheduleTimeOverride_multipleGenerations() = runTest {
testClock.currentTimeMillis = HOURS.toMillis(5)
val overrideScheduleTimeMillis = HOURS.toMillis(10)
val overrideScheduleTimeMillis2 = HOURS.toMillis(12)
@@ -543,7 +517,7 @@
val request = PeriodicWorkRequest.Builder(
TestWorker::class.java, 1, DAYS
).setInitialDelay(2, DAYS).build()
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
workManager.updateWork(
PeriodicWorkRequest.Builder(
@@ -551,8 +525,8 @@
).setId(request.id)
.setNextScheduleTimeOverride(overrideScheduleTimeMillis)
.build()
- ).get()
- val workInfo = workManager.getWorkInfoById(request.id).get()
+ ).await()
+ val workInfo = workManager.getWorkInfoById(request.id).await()
assertThat(workInfo.nextScheduleTimeMillis).isEqualTo(overrideScheduleTimeMillis)
workManager.updateWork(
@@ -561,15 +535,15 @@
).setId(request.id)
.setNextScheduleTimeOverride(overrideScheduleTimeMillis2)
.build()
- ).get()
+ ).await()
- val workInfo2 = workManager.getWorkInfoById(request.id).get()
+ val workInfo2 = workManager.getWorkInfoById(request.id).await()
assertThat(workInfo2.nextScheduleTimeMillis).isEqualTo(overrideScheduleTimeMillis2)
}
@Test
@SmallTest
- fun updateNextScheduleTimeOverride_overridesBackoff() {
+ fun updateNextScheduleTimeOverride_overridesBackoff() = runTest {
testClock.currentTimeMillis = HOURS.toMillis(5)
val overrideScheduleTimeMillis = HOURS.toMillis(10)
@@ -579,7 +553,7 @@
.setInitialDelay(2, DAYS)
.build()
request.workSpec.runAttemptCount = 1
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
workManager.updateWork(
PeriodicWorkRequest.Builder(
@@ -587,9 +561,9 @@
).setId(request.id)
.setNextScheduleTimeOverride(overrideScheduleTimeMillis)
.build()
- ).get()
+ ).await()
- val workInfo = workManager.getWorkInfoById(request.id).get()
+ val workInfo = workManager.getWorkInfoById(request.id).await()
assertThat(workInfo.nextScheduleTimeMillis).isEqualTo(overrideScheduleTimeMillis)
val workSpec = env.db.workSpecDao().getWorkSpec(request.stringId)!!
// attemptCount is still kept, just not used in the schedule time calculation.
@@ -598,7 +572,7 @@
@Test
@SmallTest
- fun clearNextScheduleTimeOverride_incrementGeneration() {
+ fun clearNextScheduleTimeOverride_incrementGeneration() = runTest {
testClock.currentTimeMillis = HOURS.toMillis(5)
val overrideScheduleTimeMillis = HOURS.toMillis(10)
@@ -607,7 +581,7 @@
).setInitialDelay(2, DAYS)
.setNextScheduleTimeOverride(overrideScheduleTimeMillis)
.build()
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
workManager.updateWork(
PeriodicWorkRequest.Builder(
@@ -616,9 +590,9 @@
.clearNextScheduleTimeOverride()
.setInitialDelay(2, DAYS)
.build()
- ).get()
+ ).await()
- val workInfo = workManager.getWorkInfoById(request.id).get()
+ val workInfo = workManager.getWorkInfoById(request.id).await()
assertThat(workInfo.nextScheduleTimeMillis).isEqualTo(
testClock.currentTimeMillis + DAYS.toMillis(2)
)
@@ -631,13 +605,13 @@
@Test
@SmallTest
- fun clearNextScheduleTimeOverride_noExistingOverride_incrementGenerationAnyway() {
+ fun clearNextScheduleTimeOverride_noExistingOverride_incrementGenerationAnyway() = runTest {
testClock.currentTimeMillis = HOURS.toMillis(5)
val request = PeriodicWorkRequest.Builder(
TestWorker::class.java, 1, DAYS
).setInitialDelay(2, DAYS).build()
- workManager.enqueue(request).result.get()
+ workManager.enqueue(request).result.await()
workManager.updateWork(
PeriodicWorkRequest.Builder(
@@ -645,7 +619,7 @@
).setId(request.id)
.clearNextScheduleTimeOverride()
.build()
- ).get()
+ ).await()
val workSpec = env.db.workSpecDao().getWorkSpec(request.stringId)!!
assertThat(workSpec.nextScheduleTimeOverride).isEqualTo(Long.MAX_VALUE)
@@ -671,54 +645,20 @@
override fun doWork(): Result = Result.success()
}
-private fun WorkManagerImpl.awaitSuccess(id: UUID) =
- getWorkInfoByIdLiveData(id).awaitSuccess(workTaskExecutor)
-
-private fun WorkManagerImpl.awaitReenqueued(id: UUID) {
- val reenqueueLatch = CountDownLatch(1)
- val workInfoLD = getWorkInfoByIdLiveData(id)
- lateinit var observer: Observer<WorkInfo>
- observer = Observer {
- if (it.state == State.ENQUEUED) {
- reenqueueLatch.countDown()
- workInfoLD.removeObserver(observer)
- }
- }
- workTaskExecutor.mainThreadExecutor.execute { workInfoLD.observeForever(observer) }
- assertThat(reenqueueLatch.await(3, TimeUnit.SECONDS)).isTrue()
-}
-
-private fun LiveData<WorkInfo>.awaitSuccess(taskExecutor: TaskExecutor) {
- val latch = CountDownLatch(1)
- lateinit var observer: Observer<WorkInfo>
- var result = State.SUCCEEDED
- observer = Observer {
- if (it.state.isFinished) {
- result = it.state
- latch.countDown()
- this@awaitSuccess.removeObserver(observer)
- }
- }
- taskExecutor.mainThreadExecutor.execute {
- this@awaitSuccess.observeForever(observer)
- }
- assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue()
- assertThat(result).isEqualTo(State.SUCCEEDED)
+private suspend fun WorkManagerImpl.awaitSuccess(id: UUID) {
+ val state = awaitWorkerFinished(id).state
+ assertThat(state).isEqualTo(State.SUCCEEDED)
}
private val TEST_DATA = Data.Builder().put("key", "test").build()
class ProgressWorker(context: Context, workerParams: WorkerParameters) :
- Worker(context, workerParams) {
- private val latch = CountDownLatch(1)
- override fun doWork(): Result {
- setProgressAsync(TEST_DATA).get()
- latch.await()
+ CoroutineWorker(context, workerParams) {
+ // will never be completed actually, so worker has to be explicitly stopped
+ private val deferred = CompletableDeferred<Unit>()
+ override suspend fun doWork(): Result {
+ setProgress(TEST_DATA)
+ deferred.await()
return Result.retry()
}
-
- override fun onStopped() {
- super.onStopped()
- latch.countDown()
- }
}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt
index 2d07b5d..dfea3f9f 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt
@@ -60,7 +60,7 @@
workDatabase.workSpecDao().insertWorkSpec(work.workSpec)
lateinit var worker: TestWrapperWorker
val workerWrapper = workerWrapper(work.stringId) { worker = it }
- workerWrapper.run()
+ val future = workerWrapper.launch()
while (taskExecutor.serialTaskExecutor.hasPendingTask() ||
backgroundExecutor.hasPendingTask()
@@ -70,7 +70,7 @@
}
workerWrapper.interrupt(0)
drainAll()
- assertThat(workerWrapper.future.isDone).isTrue()
+ assertThat(future.isDone).isTrue()
assertThat(worker.startWorkWasCalled).isFalse()
}
@@ -83,7 +83,7 @@
workDatabase.workSpecDao().insertWorkSpec(work.workSpec)
lateinit var worker: TestWrapperWorker
val workerWrapper = workerWrapper(work.stringId) { worker = it }
- workerWrapper.run()
+ val future = workerWrapper.launch()
drainAll()
assertThat(worker.getForegroundInfoAsyncWasCalled).isTrue()
assertThat(worker.startWorkWasCalled).isFalse()
@@ -96,7 +96,7 @@
workerWrapper.interrupt(0)
drainAll()
assertThat(worker.startWorkWasCalled).isFalse()
- assertThat(workerWrapper.future.isDone).isTrue()
+ assertThat(future.isDone).isTrue()
}
private fun drainAll() {
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt
index 1f17847..930f2d7 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt
@@ -117,39 +117,36 @@
val request2 = OneTimeWorkRequest.Builder(StopLatchWorker::class.java).build()
insertWork(request1)
insertWork(request2)
- var listenerCalled = false
- val listener = ExecutionListener { id, _ ->
- if (!listenerCalled) {
- listenerCalled = true
- assertEquals(request1.workSpec.id, id.workSpecId)
+ class CountDownListener(val expectedId: String) : ExecutionListener {
+ val latch = CountDownLatch(1)
+ override fun onExecuted(id: WorkGenerationalId, needsReschedule: Boolean) {
+ if (id.workSpecId == expectedId) {
+ latch.countDown()
+ }
}
}
- processor.addExecutionListener(listener)
+ val firstListener = CountDownListener(request1.workSpec.id)
+ processor.addExecutionListener(firstListener)
val startStopToken = StartStopToken(WorkGenerationalId(request1.workSpec.id, 0))
processor.startWork(startStopToken)
val firstWorker = factory.awaitWorker(request1.id)
- val blockedThread = Executors.newSingleThreadExecutor()
- blockedThread.execute {
- // gonna stall for 10 seconds
+ Executors.newSingleThreadExecutor().execute {
+ // wil result in long running onStop call, but it will block task thread
processor.stopWork(startStopToken, 0)
}
assertTrue((firstWorker as StopLatchWorker).awaitOnStopCall())
- // onStop call results in onExecuted. It happens on "main thread", which is instant
- // in this case.
- assertTrue(listenerCalled)
- processor.removeExecutionListener(listener)
- listenerCalled = false
- val executionFinished = CountDownLatch(1)
- processor.addExecutionListener { _, _ -> executionFinished.countDown() }
+
+ val secondListener = CountDownListener(request2.workSpec.id)
+ processor.addExecutionListener(secondListener)
// This would have previously failed trying to acquire a lock
processor.startWork(StartStopToken(WorkGenerationalId(request2.workSpec.id, 0)))
- val secondWorker = factory.awaitWorker(request2.id)
- (secondWorker as StopLatchWorker).countDown()
- assertTrue(executionFinished.await(3, TimeUnit.SECONDS))
firstWorker.countDown()
- blockedThread.shutdown()
- assertTrue(blockedThread.awaitTermination(3, TimeUnit.SECONDS))
+ val secondWorker = factory.awaitWorker(request2.id)
+ assertTrue(firstListener.latch.await(3, TimeUnit.SECONDS))
+ (secondWorker as StopLatchWorker).countDown()
+ assertTrue(secondListener.latch.await(3, TimeUnit.SECONDS))
+ firstWorker.countDown()
assertTrue(context.intents.isEmpty())
}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index 8510b51..4185246 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -172,7 +172,6 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(SUCCEEDED));
}
@@ -184,7 +183,7 @@
insertWork(work);
createBuilder(work.getStringId())
.build()
- .run();
+ .launch();
WorkSpec latestWorkSpec = mWorkSpecDao.getWorkSpec(work.getStringId());
assertThat(latestWorkSpec.runAttemptCount, is(1));
}
@@ -196,7 +195,7 @@
insertWork(work);
createBuilder(work.getStringId())
.build()
- .run();
+ .launch();
WorkSpec latestWorkSpec = mWorkSpecDao.getWorkSpec(work.getStringId());
assertThat(latestWorkSpec.runAttemptCount, is(1));
}
@@ -209,7 +208,6 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@@ -241,7 +239,7 @@
WorkerWrapper workerWrapper = createBuilder(work.getStringId())
.withWorker(usedWorker)
.build();
- workerWrapper.run();
+ workerWrapper.launch();
}
@Test
@@ -253,7 +251,6 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(true));
}
@@ -266,7 +263,6 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(CANCELLED));
}
@@ -279,7 +275,6 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@@ -293,7 +288,6 @@
WorkerWrapper workerWrapper = createBuilder(work.getStringId())
.build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@@ -305,7 +299,6 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@@ -324,7 +317,7 @@
previousId = work.getStringId();
}
WorkerWrapper workerWrapper = createBuilder(firstWorkId).build();
- workerWrapper.setFailedAndResolve(new ListenableWorker.Result.Failure());
+ workerWrapper.setFailed(new ListenableWorker.Result.Failure());
assertThat(mWorkSpecDao.getState(firstWorkId), is(FAILED));
assertThat(mWorkSpecDao.getState(previousId), is(FAILED));
}
@@ -338,7 +331,6 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(true));
}
@@ -363,7 +355,7 @@
}
createBuilder(prerequisiteWork.getStringId())
- .build().run();
+ .build().launch();
List<Data> arguments = mWorkSpecDao.getInputsFromPrerequisites(work.getStringId());
assertThat(arguments.size(), is(1));
@@ -405,9 +397,9 @@
}
// Run the prerequisites.
- createBuilder(prerequisiteWork1.getStringId()).build().run();
+ createBuilder(prerequisiteWork1.getStringId()).build().launch();
- createBuilder(prerequisiteWork2.getStringId()).build().run();
+ createBuilder(prerequisiteWork2.getStringId()).build().launch();
TrackingWorkerFactory factory = new TrackingWorkerFactory();
Configuration configuration = new Configuration.Builder(mConfiguration)
@@ -423,7 +415,7 @@
mDatabase.workTagDao().getTagsForWorkSpecId(id)
).build();
// Create and run the dependent work.
- workerWrapper.run();
+ workerWrapper.launch();
ListenableWorker worker = factory.awaitWorker(UUID.fromString(id));
Data input = worker.getInputData();
@@ -455,7 +447,7 @@
long beforeUnblockedTime = System.currentTimeMillis();
- createBuilder(prerequisiteWork.getStringId()).build().run();
+ createBuilder(prerequisiteWork.getStringId()).build().launch();
WorkSpec workSpec = mWorkSpecDao.getWorkSpec(work.getStringId());
assertThat(workSpec.lastEnqueueTime, is(greaterThanOrEqualTo(beforeUnblockedTime)));
@@ -490,7 +482,7 @@
createBuilder(prerequisiteWork.getStringId())
.build()
- .run();
+ .launch();
assertThat(mWorkSpecDao.getState(prerequisiteWork.getStringId()), is(SUCCEEDED));
assertThat(mWorkSpecDao.getState(work.getStringId()),
@@ -527,7 +519,7 @@
createBuilder(prerequisiteWork.getStringId())
.build()
- .run();
+ .launch();
assertThat(mWorkSpecDao.getState(prerequisiteWork.getStringId()), is(FAILED));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
@@ -553,7 +545,7 @@
createBuilder(retryWork.getStringId())
.build()
- .run();
+ .launch();
WorkSpec workSpec = mWorkSpecDao.getWorkSpec(retryWork.getStringId());
// The run attempt count should remain the same
@@ -574,7 +566,7 @@
createBuilder(periodicWork.getStringId())
.build()
- .run();
+ .launch();
WorkSpec updatedWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId());
assertThat(updatedWorkSpec.calculateNextRunTime(), greaterThan(periodStartTimeMillis));
@@ -594,7 +586,7 @@
createBuilder(periodicWork.getStringId())
.build()
- .run();
+ .launch();
WorkSpec updatedWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId());
assertThat(updatedWorkSpec.calculateNextRunTime(), greaterThan(periodStartTimeMillis));
@@ -613,7 +605,6 @@
insertWork(periodicWork);
WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId);
assertThat(listener.mResult, is(false));
@@ -634,7 +625,6 @@
insertWork(periodicWork);
WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId);
assertThat(listener.mResult, is(false));
@@ -655,7 +645,6 @@
insertWork(periodicWork);
WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId);
assertThat(listener.mResult, is(true));
@@ -681,7 +670,6 @@
insertWork(periodicWork);
WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
// Should get rescheduled
assertThat(listener.mResult, is(true));
}
@@ -703,7 +691,6 @@
insertWork(periodicWork);
WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
// Should get rescheduled because flex should be respected.
assertThat(listener.mResult, is(true));
}
@@ -727,7 +714,7 @@
// Try to run when the normal period would have happened
mTestClock.currentTimeMillis = lastEnqueueTimeMillis + intervalDurationMillis + 1;
- createBuilder(periodicWork.getStringId()).build().run();
+ createBuilder(periodicWork.getStringId()).build().launch();
// Didn't actually run or do anything, since it's too soon to run
WorkSpec firstTryWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId());
@@ -740,7 +727,7 @@
// Try again at the override time
long actualWorkRunTime = nextScheduleTimeOverrideMillis;
mTestClock.currentTimeMillis = actualWorkRunTime;
- createBuilder(periodicWork.getStringId()).build().run();
+ createBuilder(periodicWork.getStringId()).build().launch();
// Override is cleared and we're scheduled for now + period
WorkSpec afterRunWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId());
@@ -771,7 +758,7 @@
mTestClock.currentTimeMillis = nextScheduleTimeOverrideMillis;
insertWork(periodicWork);
- createBuilder(periodicWork.getStringId()).build().run();
+ createBuilder(periodicWork.getStringId()).build().launch();
// Override is cleared and we're rescheduled according to the backoff policy
WorkSpec afterRunWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId());
@@ -991,7 +978,6 @@
WorkerWrapper workerWrapper =
createBuilder(periodicWork.getStringId()).withWorker(worker).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- mExecutorService.submit(workerWrapper);
return listener;
}
@@ -1156,7 +1142,7 @@
new WorkerParameters.RuntimeExtras(),
1,
0,
- mSynchronousExecutor,
+ mExecutorService,
Dispatchers.getDefault(),
mWorkTaskExecutor,
mConfiguration.getWorkerFactory(),
@@ -1167,7 +1153,7 @@
WorkerWrapper workerWrapper =
createBuilder(work.getStringId()).withWorker(worker).build();
- mExecutorService.submit(workerWrapper);
+ workerWrapper.launch();
worker.doWorkLatch.await();
workerWrapper.interrupt(STOP_REASON_CONSTRAINT_CHARGING);
assertThat(worker.isStopped(), is(true));
@@ -1181,7 +1167,7 @@
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(ExceptionWorker.class).build();
insertWork(work);
- createBuilder(work.getStringId()).build().run();
+ createBuilder(work.getStringId()).build().launch();
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@@ -1194,7 +1180,7 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
+ workerWrapper.launch();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@@ -1207,7 +1193,6 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
assertThat(mWorkerExceptionHandler.mWorkerClassName,
@@ -1228,7 +1213,6 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
assertThat(mWorkerExceptionHandler.mWorkerClassName,
@@ -1251,8 +1235,7 @@
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- assertThat(mWorkSpecDao.getState(work.getStringId()), is(ENQUEUED));
- workerWrapper.run();
+
assertThat(mWorkSpecDao.getState(work.getStringId()), is(RUNNING));
workerWrapper.interrupt(0);
assertThat(listener.mResult, is(true));
@@ -1288,7 +1271,6 @@
mDatabase.workTagDao().getWorkSpecIdsWithTag(work.getStringId())
).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
assertThat(mWorkerExceptionHandler.mWorkerClassName,
@@ -1328,7 +1310,6 @@
).build();
FutureListener listener = createAndAddFutureListener(workerWrapper);
- workerWrapper.run();
assertThat(listener.mResult, is(false));
assertThat(mWorkSpecDao.getState(id), is(SUCCEEDED));
workerWrapper.interrupt(0);
@@ -1359,7 +1340,7 @@
).build();
workerWrapper.interrupt(0);
- workerWrapper.run();
+ workerWrapper.launch();
WorkSpec workSpec = mWorkSpecDao.getWorkSpec(work.getStringId());
assertThat(workSpec.scheduleRequestedAt, is(-1L));
}
@@ -1372,7 +1353,7 @@
work.getWorkSpec().workerClassName = "Bad class name";
insertWork(work);
WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
- workerWrapper.run();
+ workerWrapper.launch();
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@@ -1389,11 +1370,6 @@
}
@Nullable
- private LatchWorker getLatchWorker(WorkRequest work) {
- return getLatchWorker(work, mExecutorService);
- }
-
- @Nullable
private LatchWorker getLatchWorker(WorkRequest work, ExecutorService executorService) {
return (LatchWorker) mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
@@ -1414,7 +1390,7 @@
}
private FutureListener createAndAddFutureListener(WorkerWrapper workerWrapper) {
- ListenableFuture<Boolean> future = workerWrapper.getFuture();
+ ListenableFuture<Boolean> future = workerWrapper.launch();
FutureListener listener = new FutureListener(future);
future.addListener(listener, mSynchronousExecutor);
return listener;
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTestKt.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTestKt.kt
index 23c1141..ccf0709 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTestKt.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTestKt.kt
@@ -16,16 +16,27 @@
package androidx.work.impl
+import android.annotation.SuppressLint
import android.content.Context
+import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+import android.util.Log
import androidx.concurrent.futures.CallbackToFutureAdapter.Completer
import androidx.concurrent.futures.CallbackToFutureAdapter.getFuture
import androidx.concurrent.futures.await
+import androidx.core.app.NotificationChannelCompat
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
import androidx.work.Configuration
+import androidx.work.DirectExecutor
+import androidx.work.ForegroundInfo
import androidx.work.ListenableWorker
import androidx.work.ListenableWorker.Result.Success
import androidx.work.OneTimeWorkRequest
+import androidx.work.OutOfQuotaPolicy
+import androidx.work.WorkInfo
import androidx.work.WorkInfo.State.ENQUEUED
import androidx.work.WorkInfo.State.RUNNING
import androidx.work.WorkerParameters
@@ -47,7 +58,9 @@
@MediumTest
class WorkerWrapperTestKt {
val factory = TrackingWorkerFactory()
- val configuration = Configuration.Builder().setWorkerFactory(factory).build()
+ val configuration = Configuration.Builder()
+ .setMinimumLoggingLevel(Log.DEBUG)
+ .setWorkerFactory(factory).build()
val testEnv = TestEnv(configuration)
@Test
@@ -55,11 +68,11 @@
val workRequest = OneTimeWorkRequest.from(CompletableWorker::class.java)
testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
val workerWrapper = WorkerWrapper(workRequest.workSpec)
- testEnv.taskExecutor.serialTaskExecutor.execute(workerWrapper)
+ val future = workerWrapper.launch()
val completableWorker = factory.await(workRequest.id) as CompletableWorker
testEnv.db.workSpecDao().delete(workRequest.stringId)
completableWorker.result.complete(Success())
- assertThat(workerWrapper.future.await()).isFalse()
+ assertThat(future.await()).isFalse()
assertThat(testEnv.db.workSpecDao().getState(workRequest.stringId)).isNull()
}
@@ -68,12 +81,12 @@
val workRequest = OneTimeWorkRequest.from(DoWorkAwareWorker::class.java)
testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
val workerWrapper = WorkerWrapper(workRequest.workSpec)
- testEnv.taskExecutor.serialTaskExecutor.execute(workerWrapper)
+ val future = workerWrapper.launch()
val worker = factory.await(workRequest.id) as DoWorkAwareWorker
worker.doWorkEvent.await()
assertThat(testEnv.db.workSpecDao().getState(workRequest.stringId)).isEqualTo(RUNNING)
worker.resultCompleter.set(Success())
- assertThat(workerWrapper.future.await()).isFalse()
+ assertThat(future.await()).isFalse()
}
@Test
@@ -81,11 +94,11 @@
val workRequest = OneTimeWorkRequest.from(DoWorkAwareWorker::class.java)
testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
val workerWrapper = WorkerWrapper(workRequest.workSpec)
- testEnv.taskExecutor.serialTaskExecutor.execute(workerWrapper)
+ val future = workerWrapper.launch()
val worker = factory.await(workRequest.id) as DoWorkAwareWorker
worker.doWorkEvent.await()
workerWrapper.interrupt(0)
- assertThat(workerWrapper.future.await()).isTrue()
+ assertThat(future.await()).isTrue()
assertThat(testEnv.db.workSpecDao().getState(workRequest.stringId)).isEqualTo(ENQUEUED)
}
@@ -100,12 +113,12 @@
mainThreadBlocker.await()
workerWrapper.interrupt(0)
}
- testEnv.taskExecutor.serialTaskExecutor.execute(workerWrapper)
+ val future = workerWrapper.launch()
factory.await(workRequest.id)
// worker is created, but can't start work because main thread is blocked
// this call will unblock main thread, but interrupt worker
mainThreadBlocker.countDown()
- assertThat(workerWrapper.future.await()).isTrue()
+ assertThat(future.await()).isTrue()
// tricky moment, currently due to the race Worker can go through
// running state. Exact order would be:
// - WorkerWrapper reaches trySetRunning, but doesn't enter it
@@ -125,16 +138,73 @@
testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
val workerWrapper = WorkerWrapper(workRequest.workSpec)
- testEnv.taskExecutor.serialTaskExecutor.execute(workerWrapper)
+ workerWrapper.launch()
val worker = factory.await(workRequest.id) as CompletableWorker
assertThat(worker.runAttemptCount).isEqualTo(10)
worker.result.complete(Success())
}
+ @Test
+ fun stopReason_available_in_synchronous_listener_of_startWork() = runBlocking {
+ val workRequest = OneTimeWorkRequest.from(StopReasonAtCancellationWorker::class.java)
+
+ testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
+ val workerWrapper = WorkerWrapper(workRequest.workSpec)
+ val future = workerWrapper.launch()
+ val worker = factory.await(workRequest.id) as StopReasonAtCancellationWorker
+ worker.startWorkDeferred.await()
+ val mainThreadDeferred = CompletableDeferred<Unit>()
+ // making sure that task running ListenableWorker.startWork on the main thread is fully done
+ // and it posted resulting tasks to task executor
+ testEnv.taskExecutor.mainThreadExecutor.execute { mainThreadDeferred.complete(Unit) }
+
+ waitTaskExecutorIdle()
+ mainThreadDeferred.await()
+ workerWrapper.interrupt(WorkInfo.STOP_REASON_TIMEOUT)
+ future.await()
+ assertThat(worker.stopReasonAtCancellation).isEqualTo(WorkInfo.STOP_REASON_TIMEOUT)
+ }
+
+ @SdkSuppress(maxSdkVersion = 30)
+ @Test
+ fun onStopped_called_in_between_getForegroundAsync_and_startWork() = runBlocking {
+ val workRequest = OneTimeWorkRequest.Builder(TestForegroundWithStopWorker::class.java)
+ .setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST).build()
+
+ testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
+ val workerWrapper = WorkerWrapper(workRequest.workSpec)
+ val mainThreadBlocker = CountDownLatch(1)
+ testEnv.taskExecutor.mainThreadExecutor.execute { mainThreadBlocker.await() }
+ val future = workerWrapper.launch()
+ val worker = factory.await(workRequest.id) as TestForegroundWithStopWorker
+ worker.stopBlock = { workerWrapper.interrupt(WorkInfo.STOP_REASON_TIMEOUT) }
+ // allowing main thread to run. getForegroundInfoAsync will be called and completed.
+ // Afterwards worker will be stopped, before calling startWork()
+ mainThreadBlocker.countDown()
+ future.await()
+ assertThat(worker.isStopped).isEqualTo(true)
+ }
+
fun WorkerWrapper(spec: WorkSpec) = WorkerWrapper.Builder(
testEnv.context, configuration, testEnv.taskExecutor,
NoOpForegroundProcessor, testEnv.db, spec, emptyList()
).build()
+
+ private suspend fun waitTaskExecutorIdle() {
+ val deferred = CompletableDeferred<Unit>()
+ val taskExecutor = testEnv.taskExecutor.serialTaskExecutor
+
+ lateinit var idleRunnable: Runnable
+ idleRunnable = Runnable {
+ if (taskExecutor.hasPendingTasks()) {
+ taskExecutor.execute(idleRunnable)
+ } else {
+ deferred.complete(Unit)
+ }
+ }
+ taskExecutor.execute(idleRunnable)
+ return deferred.await()
+ }
}
class DoWorkAwareWorker(
@@ -159,3 +229,55 @@
onStopEvent.complete(Unit)
}
}
+
+class StopReasonAtCancellationWorker(
+ appContext: Context,
+ workerParams: WorkerParameters
+) : ListenableWorker(appContext, workerParams) {
+ private lateinit var completer: Completer<Result>
+ var stopReasonAtCancellation: Int = WorkInfo.STOP_REASON_NOT_STOPPED
+ val startWorkDeferred = CompletableDeferred<Unit>()
+
+ @SuppressLint("NewApi")
+ override fun startWork(): ListenableFuture<Result> = getFuture {
+ completer = it
+ "startWork"
+ }.also {
+ it.addListener({ stopReasonAtCancellation = stopReason }, DirectExecutor.INSTANCE)
+ startWorkDeferred.complete(Unit)
+ }
+}
+
+class TestForegroundWithStopWorker(
+ appContext: Context,
+ workerParams: WorkerParameters
+) : ListenableWorker(appContext, workerParams) {
+ var stopBlock: () -> Unit = {}
+ override fun startWork(): ListenableFuture<Result> = getFuture {
+ it.setException(IllegalStateException("Should not be called"))
+ "TestForegroundBlockingMainWorker"
+ }
+
+ override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> = getFuture {
+ val channel = NotificationChannelCompat
+ .Builder("test", NotificationManagerCompat.IMPORTANCE_DEFAULT)
+ .setName("hello")
+ .build()
+ NotificationManagerCompat.from(applicationContext).createNotificationChannel(channel)
+ val notification = NotificationCompat.Builder(applicationContext, "test")
+ .setOngoing(true)
+ .setTicker("ticker")
+ .setContentText("content text")
+ .setSmallIcon(androidx.core.R.drawable.notification_bg)
+ .build()
+ val info = ForegroundInfo(1, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
+ val mainExecutor = taskExecutor.mainThreadExecutor
+ // synchronously resolve it, also posting to main executor a task to stop worker
+ // it will happen before a task that will call startWork().
+ it.set(info)
+ mainExecutor.execute {
+ stopBlock()
+ }
+ "TestForegroundBlockingMainWorker getAsync"
+ }
+}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
deleted file mode 100644
index eff1a9d6..0000000
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.work.impl.foreground
-
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import androidx.work.Configuration
-import androidx.work.OneTimeWorkRequest
-import androidx.work.impl.Scheduler
-import androidx.work.impl.WorkDatabase
-import androidx.work.impl.WorkManagerImpl
-import androidx.work.impl.WorkerWrapper
-import androidx.work.impl.schedulers
-import androidx.work.impl.utils.futures.SettableFuture
-import androidx.work.impl.utils.taskexecutor.TaskExecutor
-import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
-import androidx.work.worker.StopAwareForegroundWorker
-import androidx.work.worker.TestForegroundWorker
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.ExecutorService
-import java.util.concurrent.Executors
-import java.util.concurrent.TimeUnit
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.spy
-
-@RunWith(AndroidJUnit4::class)
-@LargeTest
-class WorkerWrapperForegroundTest {
- private lateinit var context: Context
- private lateinit var handler: Handler
- private lateinit var config: Configuration
- private lateinit var executor: ExecutorService
- private lateinit var internalExecutor: ExecutorService
- private lateinit var taskExecutor: TaskExecutor
- private lateinit var workDatabase: WorkDatabase
- private lateinit var workManager: WorkManagerImpl
- private lateinit var foregroundProcessor: ForegroundProcessor
-
- @Before
- fun setUp() {
- context = spy(ApplicationProvider.getApplicationContext<Context>())
- // Prevent startService here to avoid notifications during tests
- val componentName = ComponentName(context, SystemForegroundService::class.java)
- doReturn(componentName).`when`(context).startService(any<Intent>())
- doReturn(context).`when`(context).applicationContext
-
- executor = Executors.newSingleThreadExecutor()
- internalExecutor = Executors.newSingleThreadExecutor()
- handler = Handler(Looper.getMainLooper())
- config = Configuration.Builder()
- .setExecutor(executor)
- .setMinimumLoggingLevel(Log.DEBUG)
- .build()
-
- taskExecutor = WorkManagerTaskExecutor(internalExecutor)
-
- workDatabase = WorkDatabase.create(
- context, taskExecutor.serialTaskExecutor, config.clock, true)
- workManager = WorkManagerImpl(
- context = context,
- configuration = config,
- workTaskExecutor = taskExecutor,
- workDatabase = workDatabase,
- schedulersCreator = schedulers(mock(Scheduler::class.java))
- )
- WorkManagerImpl.setDelegate(workManager)
- // Foreground processor
- foregroundProcessor = mock(ForegroundProcessor::class.java)
- }
-
- @Test
- fun testWorkerWrapper_doesNotResolveBackingJobImmediately() {
- val request = OneTimeWorkRequest.Builder(StopAwareForegroundWorker::class.java)
- .build()
-
- workDatabase.workSpecDao().insertWorkSpec(request.workSpec)
-
- val wrapper = WorkerWrapper.Builder(
- context,
- config,
- taskExecutor,
- foregroundProcessor,
- workDatabase,
- workDatabase.workSpecDao().getWorkSpec(request.stringId)!!,
- emptyList()
- ).build()
-
- wrapper.run()
- val future = wrapper.future as SettableFuture<Boolean>
- assertThat(future.isDone, `is`(false))
- wrapper.interrupt(0)
- assertThat(future.isDone, `is`(true))
- }
-
- @Test
- fun testWorkerWrapper_resolvesBackingJob_whenWorkerCompletes() {
- val request = OneTimeWorkRequest.Builder(TestForegroundWorker::class.java)
- .build()
-
- workDatabase.workSpecDao().insertWorkSpec(request.workSpec)
- val wrapper = WorkerWrapper.Builder(
- context,
- config,
- taskExecutor,
- foregroundProcessor,
- workDatabase,
- workDatabase.workSpecDao().getWorkSpec(request.stringId)!!,
- emptyList()
- ).build()
-
- wrapper.run()
- val future = wrapper.future as SettableFuture<Boolean>
- val latch = CountDownLatch(1)
- future.addListener({
- assertThat(future.isDone, `is`(true))
- latch.countDown()
- },
- executor
- )
-
- latch.await(5, TimeUnit.SECONDS)
- assertThat(latch.count, `is`(0L))
- }
-}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.kt
index 8673062..7fb6887 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.kt
@@ -84,7 +84,7 @@
@Test
fun testFailingWorker() = runBlocking {
val workerWrapper = create(ExceptionWorker::class)
- taskExecutor.serialTaskExecutor.execute(workerWrapper)
+ workerWrapper.launch()
workerFactory.await(workerWrapper.workSpecId)
assertThat(workManager.awaitNotRunning(workerWrapper)).isEqualTo(State.FAILED)
}
@@ -92,9 +92,9 @@
@Test
fun testSelfCancellingWorker() = runBlocking {
val workerWrapper = create(SelfCancellingWorker::class)
- taskExecutor.serialTaskExecutor.execute(workerWrapper)
+ val future = workerWrapper.launch()
val worker = awaitWorker<SelfCancellingWorker>(workerWrapper)
- workerWrapper.future.await()
+ future.await()
assertThat(workManager.awaitNotRunning(workerWrapper)).isEqualTo(State.FAILED)
assertThat(worker.stopCounter).isEqualTo(0)
}
@@ -104,18 +104,18 @@
// charging constraint isn't satisfied
fakeChargingTracker.constraintState = false
val workerWrapper = create(TestWorker::class)
- taskExecutor.serialTaskExecutor.execute(workerWrapper)
+ val future = workerWrapper.launch()
workerFactory.await(workerWrapper.workSpecId)
- workerWrapper.future.await()
+ future.await()
assertThat(workManager.awaitNotRunning(workerWrapper)).isEqualTo(State.ENQUEUED)
}
@Test
fun testConstraintTrackingWorker_onConstraintsMet() = runBlocking {
val workerWrapper = create(EchoingWorker::class)
- taskExecutor.serialTaskExecutor.execute(workerWrapper)
+ val future = workerWrapper.launch()
workerFactory.await(workerWrapper.workSpecId)
- workerWrapper.future.await()
+ future.await()
assertThat(workManager.awaitNotRunning(workerWrapper)).isEqualTo(State.SUCCEEDED)
val outputData = workManager.getWorkInfoById(workerWrapper.workSpecId).await().outputData
assertThat(outputData.getBoolean(TEST_ARGUMENT_NAME, false)).isTrue()
@@ -124,10 +124,10 @@
@Test
fun testConstraintTrackingWorker_onConstraintsChanged() = runBlocking {
val workerWrapper = create(CompletableWorker::class)
- taskExecutor.serialTaskExecutor.execute(workerWrapper)
+ val future = workerWrapper.launch()
workerFactory.await(workerWrapper.workSpecId)
fakeChargingTracker.constraintState = false
- workerWrapper.future.await()
+ future.await()
val state = workManager.awaitNotRunning(workerWrapper)
assertThat(state).isEqualTo(State.ENQUEUED)
}
@@ -135,13 +135,12 @@
@Test
fun testConstraintTrackingWorker_stopPropagated() = runBlocking {
val workerWrapper = create(DoWorkAwareWorker::class)
- taskExecutor.serialTaskExecutor.execute(workerWrapper)
-
+ val future = workerWrapper.launch()
val worker = awaitWorker<DoWorkAwareWorker>(workerWrapper)
worker.doWorkEvent.await()
launch { workerWrapper.interrupt(0) }
- workerWrapper.future.await()
+ future.await()
assertThat(workManager.awaitNotRunning(workerWrapper)).isEqualTo(State.ENQUEUED)
// WorkerWrapper future is resolved before cancellation is fully propagated in coroutines
withTimeoutOrNull(300) { worker.onStopEvent.await() }
@@ -151,8 +150,8 @@
@Test
fun test_runOnMain() = runBlocking {
val workerWrapper = create(ThreadAssertingWorker::class)
- taskExecutor.serialTaskExecutor.execute(workerWrapper)
- workerWrapper.future.await()
+ val future = workerWrapper.launch()
+ future.await()
assertThat(workManager.awaitNotRunning(workerWrapper)).isEqualTo(State.SUCCEEDED)
}
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/testutils/WorkManagerExt.kt b/work/work-runtime/src/androidTest/java/androidx/work/testutils/WorkManagerExt.kt
index 84e2bf5..3558523 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/testutils/WorkManagerExt.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/testutils/WorkManagerExt.kt
@@ -21,10 +21,8 @@
import java.util.UUID
import kotlinx.coroutines.flow.first
-suspend fun WorkManager.awaitWorkerFinished(id: UUID): WorkInfo = getWorkInfoByIdFlow(id).first {
- it.state.isFinished
-}
+suspend fun WorkManager.awaitWorkerFinished(id: UUID): WorkInfo =
+ getWorkInfoByIdFlow(id).first { it.state.isFinished }
-suspend fun WorkManager.awaitWorkerEnqueued(id: UUID): WorkInfo = getWorkInfoByIdFlow(id).first {
- it.state == WorkInfo.State.ENQUEUED
-}
+suspend fun WorkManager.awaitWorkerEnqueued(id: UUID): WorkInfo =
+ getWorkInfoByIdFlow(id).first { it.state == WorkInfo.State.ENQUEUED }
diff --git a/work/work-runtime/src/main/java/androidx/work/ListenableWorker.java b/work/work-runtime/src/main/java/androidx/work/ListenableWorker.java
index 4a66bf2..8621e91 100644
--- a/work/work-runtime/src/main/java/androidx/work/ListenableWorker.java
+++ b/work/work-runtime/src/main/java/androidx/work/ListenableWorker.java
@@ -38,6 +38,7 @@
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* A class that can perform work asynchronously in {@link WorkManager}. For most cases, we
@@ -65,7 +66,7 @@
private @NonNull Context mAppContext;
private @NonNull WorkerParameters mWorkerParams;
- private volatile int mStopReason = STOP_REASON_NOT_STOPPED;
+ private final AtomicInteger mStopReason = new AtomicInteger(STOP_REASON_NOT_STOPPED);
private boolean mUsed;
@@ -269,7 +270,7 @@
* @return {@code true} if the work operation has been interrupted
*/
public final boolean isStopped() {
- return mStopReason != STOP_REASON_NOT_STOPPED;
+ return mStopReason.get() != STOP_REASON_NOT_STOPPED;
}
/**
@@ -283,15 +284,16 @@
@StopReason
@RequiresApi(31)
public final int getStopReason() {
- return mStopReason;
+ return mStopReason.get();
}
/**
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public final void stop(int reason) {
- mStopReason = reason;
- onStopped();
+ if (mStopReason.compareAndSet(STOP_REASON_NOT_STOPPED, reason)) {
+ onStopped();
+ }
}
/**
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/Processor.java b/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
index 104e4c6..95a2a95 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
@@ -166,7 +166,7 @@
tags)
.withRuntimeExtras(runtimeExtras)
.build();
- ListenableFuture<Boolean> future = workWrapper.getFuture();
+ ListenableFuture<Boolean> future = workWrapper.launch();
future.addListener(
() -> {
boolean needsReschedule;
@@ -184,7 +184,6 @@
set.add(startStopToken);
mWorkRuns.put(workSpecId, set);
}
- mWorkTaskExecutor.getSerialTaskExecutor().execute(workWrapper);
Logger.get().debug(TAG, getClass().getSimpleName() + ": processing " + id);
return true;
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt b/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt
new file mode 100644
index 0000000..014b025
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/impl/UnfinishedWorkListener.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.impl
+
+import android.content.Context
+import androidx.work.impl.background.systemalarm.RescheduleReceiver
+import androidx.work.impl.utils.PackageManagerHelper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+internal fun CoroutineScope.launchUnfinishedWorkListener(appContext: Context, db: WorkDatabase) =
+ db.workSpecDao().hasUnfinishedWorkFlow()
+ .conflate()
+ .distinctUntilChanged()
+ .onEach { hasUnfinishedWork ->
+ PackageManagerHelper.setComponentEnabled(
+ appContext, RescheduleReceiver::class.java, hasUnfinishedWork
+ )
+ }.launchIn(this)
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 4f081b4..6e62747 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -20,14 +20,16 @@
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.text.TextUtils.isEmpty;
+import static androidx.work.ListenableFutureKt.executeAsync;
+import static androidx.work.impl.UnfinishedWorkListenerKt.launchUnfinishedWorkListener;
import static androidx.work.impl.WorkManagerImplExtKt.createWorkManager;
+import static androidx.work.impl.WorkManagerImplExtKt.createWorkManagerScope;
import static androidx.work.impl.WorkerUpdater.enqueueUniquelyNamedPeriodic;
import static androidx.work.impl.foreground.SystemForegroundDispatcher.createCancelWorkIntent;
import static androidx.work.impl.model.RawWorkInfoDaoKt.getWorkInfoPojosFlow;
import static androidx.work.impl.model.WorkSpecDaoKt.getWorkStatusPojoFlowDataForIds;
import static androidx.work.impl.model.WorkSpecDaoKt.getWorkStatusPojoFlowForName;
import static androidx.work.impl.model.WorkSpecDaoKt.getWorkStatusPojoFlowForTag;
-import static androidx.work.ListenableFutureKt.executeAsync;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -78,6 +80,7 @@
import java.util.List;
import java.util.UUID;
+import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.flow.Flow;
/**
@@ -105,11 +108,14 @@
private BroadcastReceiver.PendingResult mRescheduleReceiverResult;
private volatile RemoteWorkManager mRemoteWorkManager;
private final Trackers mTrackers;
+ /**
+ * Job for the scope of the whole WorkManager
+ */
+ private final CoroutineScope mWorkManagerScope;
private static WorkManagerImpl sDelegatedInstance = null;
private static WorkManagerImpl sDefaultInstance = null;
private static final Object sLock = new Object();
-
/**
* @param delegate The delegate for {@link WorkManagerImpl} for testing; {@code null} to use the
* default instance
@@ -244,12 +250,13 @@
mTrackers = trackers;
mConfiguration = configuration;
mSchedulers = schedulers;
+ mWorkManagerScope = createWorkManagerScope(mWorkTaskExecutor);
mPreferenceUtils = new PreferenceUtils(mWorkDatabase);
Schedulers.registerRescheduling(schedulers, mProcessor,
workTaskExecutor.getSerialTaskExecutor(), mWorkDatabase, configuration);
-
// Checks for app force stops.
mWorkTaskExecutor.executeOnTaskThread(new ForceStopRunnable(context, this));
+ launchUnfinishedWorkListener(mWorkManagerScope, mContext, workDatabase);
}
/**
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImplExt.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImplExt.kt
index d99874f..0121173 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImplExt.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImplExt.kt
@@ -23,6 +23,7 @@
import androidx.work.impl.constraints.trackers.Trackers
import androidx.work.impl.utils.taskexecutor.TaskExecutor
import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
+import kotlinx.coroutines.CoroutineScope
@JvmName("createWorkManager")
@JvmOverloads
@@ -90,3 +91,7 @@
workTaskExecutor
),
)
+
+@JvmName("createWorkManagerScope")
+internal fun WorkManagerScope(taskExecutor: TaskExecutor) =
+ CoroutineScope(taskExecutor.taskCoroutineDispatcher)
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
index 3b63786..2fb5869 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.kt
@@ -19,28 +19,25 @@
import android.content.Context
import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
-import androidx.annotation.WorkerThread
-import androidx.concurrent.futures.await
import androidx.work.Clock
import androidx.work.Configuration
import androidx.work.Data
+import androidx.work.DirectExecutor
import androidx.work.ListenableWorker
import androidx.work.ListenableWorker.Result.Failure
import androidx.work.Logger
import androidx.work.WorkInfo
import androidx.work.WorkerExceptionInfo
import androidx.work.WorkerParameters
-import androidx.work.impl.background.systemalarm.RescheduleReceiver
+import androidx.work.impl.WorkerWrapper.Resolution.ResetWorkerStatus
import androidx.work.impl.foreground.ForegroundProcessor
import androidx.work.impl.model.DependencyDao
import androidx.work.impl.model.WorkGenerationalId
import androidx.work.impl.model.WorkSpec
import androidx.work.impl.model.WorkSpecDao
import androidx.work.impl.model.generationalId
-import androidx.work.impl.utils.PackageManagerHelper
import androidx.work.impl.utils.WorkForegroundUpdater
import androidx.work.impl.utils.WorkProgressUpdater
-import androidx.work.impl.utils.futures.SettableFuture
import androidx.work.impl.utils.safeAccept
import androidx.work.impl.utils.taskexecutor.TaskExecutor
import androidx.work.impl.utils.workForeground
@@ -53,21 +50,27 @@
import java.util.concurrent.Callable
import java.util.concurrent.CancellationException
import java.util.concurrent.ExecutionException
+import java.util.concurrent.Future
+import kotlin.coroutines.coroutineContext
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
/**
* A runnable that looks up the [WorkSpec] from the database for a given id, instantiates
* its Worker, and then calls it.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class WorkerWrapper internal constructor(builder: Builder) : Runnable {
+class WorkerWrapper internal constructor(builder: Builder) {
val workSpec: WorkSpec = builder.workSpec
private val appContext: Context = builder.appContext
private val workSpecId: String = workSpec.id
private val runtimeExtras: WorkerParameters.RuntimeExtras = builder.runtimeExtras
- private var worker: ListenableWorker? = builder.worker
+ private val builderWorker: ListenableWorker? = builder.worker
private val workTaskExecutor: TaskExecutor = builder.workTaskExecutor
private val configuration: Configuration = builder.configuration
@@ -77,39 +80,56 @@
private val workSpecDao: WorkSpecDao = workDatabase.workSpecDao()
private val dependencyDao: DependencyDao = workDatabase.dependencyDao()
private val tags: List<String> = builder.tags
- private var workDescription: String? = null
+ private val workDescription: String = createWorkDescription(tags)
- private val _future: SettableFuture<Boolean> = SettableFuture.create()
-
- private val workerResultFuture: SettableFuture<ListenableWorker.Result> =
- SettableFuture.create()
-
- @Volatile
- private var interrupted = WorkInfo.STOP_REASON_NOT_STOPPED
+ private val workerJob = Job()
val workGenerationalId: WorkGenerationalId
get() = workSpec.generationalId()
- val future: ListenableFuture<Boolean>
- get() = _future
- @WorkerThread
- override fun run() {
- workDescription = createWorkDescription(tags)
- runWorker()
+ fun launch(): ListenableFuture<Boolean> = launchFuture(
+ workTaskExecutor.taskCoroutineDispatcher + Job()
+ ) {
+ val resolution: Resolution = try {
+ // we're wrapping runWorker in separate job, so we can always run post processing
+ // without a fear of being cancelled.
+ withContext(workerJob) {
+ runWorker()
+ }
+ } catch (workerStoppedException: WorkerStoppedException) {
+ ResetWorkerStatus(workerStoppedException.reason)
+ } catch (e: CancellationException) {
+ // means that worker was self-cancelled, which we treat as failure
+ Resolution.Failed()
+ } catch (throwable: Throwable) {
+ loge(TAG, throwable) { "Unexpected error in WorkerWrapper" }
+ Resolution.Failed()
+ }
+ workDatabase.runInTransaction(Callable {
+ when (resolution) {
+ is Resolution.Finished -> onWorkFinished(resolution.result)
+ is Resolution.Failed -> {
+ setFailed(resolution.result)
+ false
+ }
+ is ResetWorkerStatus -> resetWorkerStatus(resolution.reason)
+ }
+ })
}
- private fun runWorker() {
- if (tryCheckForInterruptionAndResolve()) {
- return
- }
+ private sealed class Resolution {
+ class ResetWorkerStatus(val reason: Int = WorkInfo.STOP_REASON_NOT_STOPPED) : Resolution()
+ class Failed(val result: ListenableWorker.Result = Failure()) : Resolution()
+ class Finished(val result: ListenableWorker.Result) : Resolution()
+ }
+ private suspend fun runWorker(): Resolution {
// Needed for nested transactions, such as when we're in a dependent work request when
// using a SynchronousExecutor.
val shouldExit = workDatabase.runInTransaction(Callable {
// Do a quick check to make sure we don't need to bail out in case this work is already
// running, finished, or is blocked.
if (workSpec.state !== WorkInfo.State.ENQUEUED) {
- resolveIncorrectStatus()
logd(TAG) {
"${workSpec.workerClassName} is not in ENQUEUED state. Nothing more to do"
}
@@ -138,14 +158,13 @@
// For AlarmManager implementation we need to reschedule this kind of Work.
// This is not a problem for JobScheduler because we will only reschedule
// work if JobScheduler is unaware of a jobId.
- resolve(true)
return@Callable true
}
}
return@Callable false
})
- if (shouldExit) return
+ if (shouldExit) return ResetWorkerStatus()
// Merge inputs. This can be potentially expensive code, so this should not be done inside
// a database transaction.
@@ -158,8 +177,7 @@
inputMergerFactory.createInputMergerWithDefaultFallback(inputMergerClassName)
if (inputMerger == null) {
loge(TAG) { "Could not create Input Merger ${workSpec.inputMergerClassName}" }
- setFailedAndResolve(Failure())
- return
+ return Resolution.Failed()
}
val inputs = listOf(workSpec.input) + workSpecDao.getInputsFromPrerequisites(workSpecId)
inputMerger.merge(inputs)
@@ -181,7 +199,7 @@
// Not always creating a worker here, as the WorkerWrapper.Builder can set a worker override
// in test mode.
- val worker = worker
+ val worker = builderWorker
?: try {
configuration.workerFactory.createWorkerWithDefaultFallback(
appContext,
@@ -195,190 +213,142 @@
WorkerExceptionInfo(workSpec.workerClassName, params, e),
TAG
)
- setFailedAndResolve(Failure())
- return
+ return Resolution.Failed()
}
worker.setUsed()
- this.worker = worker
+ // we specifically use coroutineContext[Job] instead of workerJob
+ // because it will be complete once withContext finishes.
+ // This way if worker has successfully finished and then
+ // interrupt() is called, then it is ignored, because
+ // job is already completed.
+ val job = coroutineContext[Job]!!
+
+ // worker stopping is complicated process.
+ // Historical behavior that we are trying to preserve is that
+ // worker.onStopped is always called in case of stoppage since the worker is instantiated,
+ // no matter if other methods such as startWork or getForegroundInfoAsync were called.
+ //
+ // Another important behavior is that worker should be marked as stopped before
+ // calling .cancel() on the future returned from the startWork(). So the listeners of this
+ // future could check what was the stop reason via `getStopReason()`, including listeners
+ // that were added with the direct executor.
+ // worker.stop() could be safely called multiple times, (only first one is effective),
+ // and we rely on this property.
+ // The completion listener below is for the cases when
+ // 1. getForegroundInfoAsync / startWork weren't called yet at all
+ // 2. when WorkerWrapper received stop signal when getForegroundInfoAsync() completed
+ // and startWork() hasn't been called yet.
+ // 3. startWork's future was completed, but job was cancelled before we actually received
+ // a notification about future's completion. (it is the natural race between stop signal
+ // and future completion, that we can't avoid. In this case worker will be decided as
+ // stopped and re-enqueued for another attempt)
+ job.invokeOnCompletion {
+ if (it is WorkerStoppedException) {
+ worker.stop(it.reason)
+ }
+ }
// Try to set the work to the running state. Note that this may fail because another thread
// may have modified the DB since we checked last at the top of this function.
- if (trySetRunning()) {
- if (tryCheckForInterruptionAndResolve()) {
- return
- }
- val foregroundUpdater = params.foregroundUpdater
- val mainDispatcher = workTaskExecutor.getMainThreadExecutor().asCoroutineDispatcher()
- val future = launchFuture(mainDispatcher + Job()) {
+ if (!trySetRunning()) {
+ return ResetWorkerStatus()
+ }
+
+ if (job.isCancelled) {
+ // doesn't matter job is cancelled anyway
+ return ResetWorkerStatus()
+ }
+
+ val foregroundUpdater = params.foregroundUpdater
+ val mainDispatcher = workTaskExecutor.getMainThreadExecutor().asCoroutineDispatcher()
+ try {
+ val result = withContext(mainDispatcher) {
workForeground(appContext, workSpec, worker, foregroundUpdater, workTaskExecutor)
logd(TAG) { "Starting work for ${workSpec.workerClassName}" }
- worker.startWork().await()
+ // *important* we can't pass future around suspension points
+ // because we will lose cancellation, so we have to await
+ // right here on the main thread.
+ worker.startWork().awaitWithin(worker)
}
- workerResultFuture.setFuture(future)
- // Avoid synthetic accessors.
- val workDescription = workDescription
- workerResultFuture.addListener({
- var result: ListenableWorker.Result = Failure()
- try {
- // If the ListenableWorker returns a null result treat it as a failure.
- val futureResult = workerResultFuture.get()
- result = if (futureResult == null) {
- loge(TAG) {
- workSpec.workerClassName +
- " returned a null result. Treating it as a failure."
- }
- Failure()
- } else {
- logd(TAG) { "${workSpec.workerClassName} returned a $futureResult." }
- futureResult
- }
- } catch (exception: CancellationException) {
- // Cancellations need to be treated with care here because innerFuture
- // cancellations will bubble up, and we need to gracefully handle that.
- logi(TAG, exception) { "$workDescription was cancelled" }
- } catch (exception: Exception) {
- val exceptionToReport = if (exception is ExecutionException) {
- exception.cause ?: exception
- } else {
- exception
- }
- loge(TAG, exceptionToReport) {
- "$workDescription failed because it threw an exception/error"
- }
- configuration.workerExecutionExceptionHandler?.safeAccept(
- WorkerExceptionInfo(workSpec.workerClassName, params, exceptionToReport),
- TAG
- )
- } finally {
- onWorkFinished(result)
- }
- }, workTaskExecutor.getSerialTaskExecutor())
- } else {
- resolveIncorrectStatus()
+ return Resolution.Finished(result)
+ } catch (cancellation: CancellationException) {
+ logi(TAG, cancellation) { "$workDescription was cancelled" }
+ throw cancellation
+ } catch (throwable: Throwable) {
+ loge(TAG, throwable) {
+ "$workDescription failed because it threw an exception/error"
+ }
+ configuration.workerExecutionExceptionHandler?.safeAccept(
+ WorkerExceptionInfo(workSpec.workerClassName, params, throwable),
+ TAG
+ )
+ return Resolution.Failed()
}
}
- private fun onWorkFinished(result: ListenableWorker.Result) {
- if (!tryCheckForInterruptionAndResolve()) {
- workDatabase.runInTransaction {
- val state = workSpecDao.getState(workSpecId)
- workDatabase.workProgressDao().delete(workSpecId)
- if (state == null) {
- // state can be null here with a REPLACE on beginUniqueWork().
- // Treat it as a failure, and rescheduleAndResolve() will
- // turn into a no-op. We still need to notify potential observers
- // holding on to wake locks on our behalf.
- resolve(false)
- } else if (state === WorkInfo.State.RUNNING) {
- handleResult(result)
- } else if (!state.isFinished) {
- // counting this is stopped with unknown reason
- interrupted = WorkInfo.STOP_REASON_UNKNOWN
- rescheduleAndResolve()
- }
- }
+ private fun onWorkFinished(result: ListenableWorker.Result): Boolean {
+ val state = workSpecDao.getState(workSpecId)
+ workDatabase.workProgressDao().delete(workSpecId)
+ return if (state == null) {
+ // state can be null here with a REPLACE on beginUniqueWork().
+ // Treat it as a failure, and rescheduleAndResolve() will
+ // turn into a no-op. We still need to notify potential observers
+ // holding on to wake locks on our behalf.
+ false
+ } else if (state === WorkInfo.State.RUNNING) {
+ handleResult(result)
+ } else if (!state.isFinished) {
+ // counting this is stopped with unknown reason
+ reschedule(WorkInfo.STOP_REASON_UNKNOWN)
+ } else {
+ false
}
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun interrupt(stopReason: Int) {
- interrupted = stopReason
- // Resolve WorkerWrapper's future so we do the right thing and setup a reschedule
- // if necessary. mInterrupted is always true here, we don't really care about the return
- // value.
- tryCheckForInterruptionAndResolve()
- // Propagate the cancellations to the inner future.
- workerResultFuture.cancel(true)
- // Worker can be null if run() hasn't been called yet
- // only call stop if it wasn't completed normally.
- val worker = worker
- if (worker != null && workerResultFuture.isCancelled()) {
- worker.stop(stopReason)
- } else {
- logd(TAG) { "WorkSpec $workSpec is already done. Not interrupting." }
- }
+ workerJob.cancel(WorkerStoppedException(stopReason))
}
- private fun resolveIncorrectStatus() {
- val status = workSpecDao.getState(workSpecId)
- if (status === WorkInfo.State.RUNNING) {
+ private fun resetWorkerStatus(stopReason: Int): Boolean {
+ val state = workSpecDao.getState(workSpecId)
+ return if (state != null && !state.isFinished) {
logd(TAG) {
- "Status for $workSpecId is RUNNING; not doing any work and " +
+ "Status for $workSpecId is $state; not doing any work and " +
"rescheduling for later execution"
}
- resolve(true)
+ // Set state to ENQUEUED again.
+ // Reset scheduled state so it's picked up by background schedulers again.
+ // We want to preserve time when work was enqueued so just explicitly set enqueued
+ // instead using markEnqueuedState. Similarly, don't change any override time.
+ workSpecDao.setState(WorkInfo.State.ENQUEUED, workSpecId)
+ workSpecDao.setStopReason(workSpecId, stopReason)
+ workSpecDao.markWorkSpecScheduled(workSpecId, WorkSpec.SCHEDULE_NOT_REQUESTED_YET)
+ true
} else {
- logd(TAG) { "Status for $workSpecId is $status ; not doing any work" }
- resolve(false)
+ logd(TAG) { "Status for $workSpecId is $state ; not doing any work" }
+ false
}
}
- private fun tryCheckForInterruptionAndResolve(): Boolean {
- // Interruptions can happen when:
- // An explicit cancel* signal
- // A change in constraint, which causes WorkManager to stop the Worker.
- // Worker exceeding a 10 min execution window.
- // One scheduler completing a Worker, and telling other Schedulers to cleanup.
- if (interrupted != WorkInfo.STOP_REASON_NOT_STOPPED) {
- logd(TAG) { "Work interrupted for $workDescription" }
- val currentState = workSpecDao.getState(workSpecId)
- if (currentState == null) {
- // This can happen because of a beginUniqueWork(..., REPLACE, ...). Notify the
- // listeners so we can clean up any wake locks, etc.
- resolve(false)
- } else {
- resolve(!currentState.isFinished)
- }
- return true
- }
- return false
- }
-
- private fun resolve(needsReschedule: Boolean) {
- workDatabase.runInTransaction {
- // IMPORTANT: We are using a transaction here as to ensure that we have some guarantees
- // about the state of the world before we disable RescheduleReceiver.
-
- // Check to see if there is more work to be done. If there is no more work, then
- // disable RescheduleReceiver. Using a transaction here, as there could be more than
- // one thread looking at the list of eligible WorkSpecs.
- val hasUnfinishedWork = workDatabase.workSpecDao().hasUnfinishedWork()
- if (!hasUnfinishedWork) {
- PackageManagerHelper.setComponentEnabled(
- appContext, RescheduleReceiver::class.java, false
- )
- }
- if (needsReschedule) {
- // Set state to ENQUEUED again.
- // Reset scheduled state so it's picked up by background schedulers again.
- // We want to preserve time when work was enqueued so just explicitly set enqueued
- // instead using markEnqueuedState. Similarly, don't change any override time.
- workSpecDao.setState(WorkInfo.State.ENQUEUED, workSpecId)
- workSpecDao.setStopReason(workSpecId, interrupted)
- workSpecDao.markWorkSpecScheduled(workSpecId, WorkSpec.SCHEDULE_NOT_REQUESTED_YET)
- }
- }
- _future.set(needsReschedule)
- }
-
- private fun handleResult(result: ListenableWorker.Result?) {
- if (result is ListenableWorker.Result.Success) {
+ private fun handleResult(result: ListenableWorker.Result?): Boolean {
+ return if (result is ListenableWorker.Result.Success) {
logi(TAG) { "Worker result SUCCESS for $workDescription" }
if (workSpec.isPeriodic) {
- resetPeriodicAndResolve()
+ resetPeriodic()
} else {
- setSucceededAndResolve(result)
+ setSucceeded(result)
}
} else if (result is ListenableWorker.Result.Retry) {
logi(TAG) { "Worker result RETRY for $workDescription" }
- rescheduleAndResolve()
+ reschedule(WorkInfo.STOP_REASON_NOT_STOPPED)
} else {
logi(TAG) { "Worker result FAILURE for $workDescription" }
if (workSpec.isPeriodic) {
- resetPeriodicAndResolve()
+ resetPeriodic()
} else {
// we have here either failure or null
- setFailedAndResolve(result ?: Failure())
+ setFailed(result ?: Failure())
}
}
}
@@ -396,18 +366,17 @@
)
@VisibleForTesting
- fun setFailedAndResolve(result: ListenableWorker.Result) {
- resolve(false) {
- iterativelyFailWorkAndDependents(workSpecId)
- val failure = result as Failure
- // Update Data as necessary.
- val output = failure.outputData
- workSpecDao.resetWorkSpecNextScheduleTimeOverride(
- workSpecId,
- workSpec.nextScheduleTimeOverrideGeneration
- )
- workSpecDao.setOutput(workSpecId, output)
- }
+ fun setFailed(result: ListenableWorker.Result): Boolean {
+ iterativelyFailWorkAndDependents(workSpecId)
+ val failure = result as Failure
+ // Update Data as necessary.
+ val output = failure.outputData
+ workSpecDao.resetWorkSpecNextScheduleTimeOverride(
+ workSpecId,
+ workSpec.nextScheduleTimeOverrideGeneration
+ )
+ workSpecDao.setOutput(workSpecId, output)
+ return false
}
private fun iterativelyFailWorkAndDependents(workSpecId: String) {
@@ -422,65 +391,55 @@
}
}
- private fun rescheduleAndResolve() {
- resolve(true) {
- workSpecDao.setState(WorkInfo.State.ENQUEUED, workSpecId)
- workSpecDao.setLastEnqueueTime(workSpecId, clock.currentTimeMillis())
- workSpecDao.resetWorkSpecNextScheduleTimeOverride(
- workSpecId,
- workSpec.nextScheduleTimeOverrideGeneration
- )
- workSpecDao.markWorkSpecScheduled(workSpecId, WorkSpec.SCHEDULE_NOT_REQUESTED_YET)
- }
+ private fun reschedule(stopReason: Int): Boolean {
+ workSpecDao.setState(WorkInfo.State.ENQUEUED, workSpecId)
+ workSpecDao.setLastEnqueueTime(workSpecId, clock.currentTimeMillis())
+ workSpecDao.resetWorkSpecNextScheduleTimeOverride(
+ workSpecId,
+ workSpec.nextScheduleTimeOverrideGeneration
+ )
+ workSpecDao.markWorkSpecScheduled(workSpecId, WorkSpec.SCHEDULE_NOT_REQUESTED_YET)
+ workSpecDao.setStopReason(workSpecId, stopReason)
+ return true
}
- private fun resetPeriodicAndResolve() {
- resolve(false) {
- // The system clock may have been changed such that the lastEnqueueTime was in the past.
- // Therefore we always use the current time to determine the next run time of a Worker.
- // This way, the Schedulers will correctly schedule the next instance of the
- // PeriodicWork in the future. This happens in calculateNextRunTime() in WorkSpec.
- workSpecDao.setLastEnqueueTime(workSpecId, clock.currentTimeMillis())
- workSpecDao.setState(WorkInfo.State.ENQUEUED, workSpecId)
- workSpecDao.resetWorkSpecRunAttemptCount(workSpecId)
- workSpecDao.resetWorkSpecNextScheduleTimeOverride(
- workSpecId,
- workSpec.nextScheduleTimeOverrideGeneration
- )
- workSpecDao.incrementPeriodCount(workSpecId)
- workSpecDao.markWorkSpecScheduled(workSpecId, WorkSpec.SCHEDULE_NOT_REQUESTED_YET)
- }
+ private fun resetPeriodic(): Boolean {
+ // The system clock may have been changed such that the lastEnqueueTime was in the past.
+ // Therefore we always use the current time to determine the next run time of a Worker.
+ // This way, the Schedulers will correctly schedule the next instance of the
+ // PeriodicWork in the future. This happens in calculateNextRunTime() in WorkSpec.
+ workSpecDao.setLastEnqueueTime(workSpecId, clock.currentTimeMillis())
+ workSpecDao.setState(WorkInfo.State.ENQUEUED, workSpecId)
+ workSpecDao.resetWorkSpecRunAttemptCount(workSpecId)
+ workSpecDao.resetWorkSpecNextScheduleTimeOverride(
+ workSpecId,
+ workSpec.nextScheduleTimeOverrideGeneration
+ )
+ workSpecDao.incrementPeriodCount(workSpecId)
+ workSpecDao.markWorkSpecScheduled(workSpecId, WorkSpec.SCHEDULE_NOT_REQUESTED_YET)
+ return false
}
- private fun setSucceededAndResolve(result: ListenableWorker.Result) {
- resolve(false) {
- workSpecDao.setState(WorkInfo.State.SUCCEEDED, workSpecId)
- val success = result as ListenableWorker.Result.Success
- // Update Data as necessary.
- val output = success.outputData
- workSpecDao.setOutput(workSpecId, output)
+ private fun setSucceeded(result: ListenableWorker.Result): Boolean {
+ workSpecDao.setState(WorkInfo.State.SUCCEEDED, workSpecId)
+ val success = result as ListenableWorker.Result.Success
+ // Update Data as necessary.
+ val output = success.outputData
+ workSpecDao.setOutput(workSpecId, output)
- // Unblock Dependencies and set Period Start Time
- val currentTimeMillis = clock.currentTimeMillis()
- val dependentWorkIds = dependencyDao.getDependentWorkIds(workSpecId)
- for (dependentWorkId in dependentWorkIds) {
- if (workSpecDao.getState(dependentWorkId) === WorkInfo.State.BLOCKED &&
- dependencyDao.hasCompletedAllPrerequisites(dependentWorkId)
- ) {
- logi(TAG) { "Setting status to enqueued for $dependentWorkId" }
- workSpecDao.setState(WorkInfo.State.ENQUEUED, dependentWorkId)
- workSpecDao.setLastEnqueueTime(dependentWorkId, currentTimeMillis)
- }
+ // Unblock Dependencies and set Period Start Time
+ val currentTimeMillis = clock.currentTimeMillis()
+ val dependentWorkIds = dependencyDao.getDependentWorkIds(workSpecId)
+ for (dependentWorkId in dependentWorkIds) {
+ if (workSpecDao.getState(dependentWorkId) === WorkInfo.State.BLOCKED &&
+ dependencyDao.hasCompletedAllPrerequisites(dependentWorkId)
+ ) {
+ logi(TAG) { "Setting status to enqueued for $dependentWorkId" }
+ workSpecDao.setState(WorkInfo.State.ENQUEUED, dependentWorkId)
+ workSpecDao.setLastEnqueueTime(dependentWorkId, currentTimeMillis)
}
}
- }
-
- private fun resolve(reschedule: Boolean, block: () -> Unit) {
- try {
- workDatabase.runInTransaction(block)
- } finally {
- resolve(reschedule)
- }
+ return false
}
private fun createWorkDescription(tags: List<String>) =
@@ -537,3 +496,77 @@
}
private val TAG = Logger.tagWithPrefix("WorkerWrapper")
+
+// copy of await() function but with specific cancellation propagation.
+// it is needed that we specifically want to call .stop() on worker itself before
+// calling cancel() of the future.
+internal suspend fun <T> ListenableFuture<T>.awaitWithin(worker: ListenableWorker): T {
+ try {
+ if (isDone) return getUninterruptibly(this)
+ } catch (e: ExecutionException) {
+ // ExecutionException is the only kind of exception that can be thrown from a gotten
+ // Future, other than CancellationException. Cancellation is propagated upward so that
+ // the coroutine running this suspend function may process it.
+ // Any other Exception showing up here indicates a very fundamental bug in a
+ // Future implementation.
+ throw e.nonNullCause()
+ }
+
+ return suspendCancellableCoroutine { cont: CancellableContinuation<T> ->
+ addListener(
+ ToContinuation(this, cont),
+ DirectExecutor.INSTANCE
+ )
+ cont.invokeOnCancellation {
+ if (it is WorkerStoppedException) {
+ worker.stop(it.reason)
+ }
+ cancel(false)
+ }
+ }
+}
+
+private class WorkerStoppedException(val reason: Int) : CancellationException()
+
+private class ToContinuation<T>(
+ val futureToObserve: ListenableFuture<T>,
+ val continuation: CancellableContinuation<T>
+) : Runnable {
+ override fun run() {
+ if (futureToObserve.isCancelled) {
+ continuation.cancel()
+ } else {
+ try {
+ continuation.resumeWith(
+ Result.success(getUninterruptibly(futureToObserve))
+ )
+ } catch (e: ExecutionException) {
+ // ExecutionException is the only kind of exception that can be thrown from a gotten
+ // Future. Anything else showing up here indicates a very fundamental bug in a
+ // Future implementation.
+ continuation.resumeWithException(e.nonNullCause())
+ }
+ }
+ }
+}
+
+private fun <V> getUninterruptibly(future: Future<V>): V {
+ var interrupted = false
+ try {
+ while (true) {
+ try {
+ return future.get()
+ } catch (e: InterruptedException) {
+ interrupted = true
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt()
+ }
+ }
+}
+
+private fun ExecutionException.nonNullCause(): Throwable {
+ return this.cause!!
+}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
index 1501125..1c15845 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
@@ -348,7 +348,7 @@
* @return `true` if there is pending work.
*/
@Query("SELECT COUNT(*) > 0 FROM workspec WHERE state NOT IN $COMPLETED_STATES LIMIT 1")
- fun hasUnfinishedWork(): Boolean
+ fun hasUnfinishedWorkFlow(): Flow<Boolean>
/**
* Marks a [WorkSpec] as scheduled.
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
index d32e717..41933ab9 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
@@ -28,7 +28,6 @@
import static androidx.work.impl.utils.EnqueueUtilsKt.checkContentUriTriggerWorkerLimits;
import static androidx.work.impl.utils.EnqueueUtilsKt.wrapInConstraintTrackingWorkerIfNeeded;
-import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@@ -42,7 +41,6 @@
import androidx.work.impl.WorkContinuationImpl;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
-import androidx.work.impl.background.systemalarm.RescheduleReceiver;
import androidx.work.impl.model.Dependency;
import androidx.work.impl.model.DependencyDao;
import androidx.work.impl.model.WorkName;
@@ -75,9 +73,6 @@
}
boolean needsScheduling = addToDatabase(workContinuation);
if (needsScheduling) {
- // Enable RescheduleReceiver, only when there are Worker's that need scheduling.
- final Context context = workContinuation.getWorkManagerImpl().getApplicationContext();
- PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
scheduleWorkInBackground(workContinuation);
}
}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkForeground.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkForeground.kt
index 66fe9ee..ea2ae82 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkForeground.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkForeground.kt
@@ -21,6 +21,7 @@
import androidx.work.ForegroundUpdater
import androidx.work.ListenableWorker
import androidx.work.Logger
+import androidx.work.impl.awaitWithin
import androidx.work.impl.model.WorkSpec
import androidx.work.impl.utils.taskexecutor.TaskExecutor
import androidx.work.logd
@@ -38,7 +39,7 @@
val dispatcher = taskExecutor.mainThreadExecutor.asCoroutineDispatcher()
withContext(dispatcher) {
- val foregroundInfo = worker.getForegroundInfoAsync().await()
+ val foregroundInfo = worker.getForegroundInfoAsync().awaitWithin(worker)
if (foregroundInfo == null) {
val message =
"Worker was marked important (${spec.workerClassName}) " +