Merge "Reduce XElement API surface" into androidx-master-dev
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7cdf3e3..b390ac1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -52,7 +52,7 @@
 
 We recommend cloning using blob filter to reduce checkout size:
 ```bash
-git clone --filter=blob:none https://github.com/YOUR_USERNAME/androidx.git
+git clone https://github.com/YOUR_USERNAME/androidx.git
 ```
 
 Let’s assume that you want to make a contribution to Room. The first step is to launch Android Studio and import the Room project.
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchManagerTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchManagerTest.java
index aaa0939..a7fd8ad 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchManagerTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchManagerTest.java
@@ -442,7 +442,7 @@
 
         // Set number of results per page is 7.
         SearchResults searchResults = mDb1.query("body",
-                SearchSpec.newBuilder()
+                new SearchSpec.Builder()
                         .setTermMatchType(SearchSpec.TERM_MATCH_TYPE_EXACT_ONLY)
                         .setNumPerPage(7)
                         .build());
@@ -980,7 +980,7 @@
             AppSearchManager instance, String queryExpression, String... schemaTypes)
             throws Exception {
         SearchResults searchResults = instance.query(queryExpression,
-                SearchSpec.newBuilder()
+                new SearchSpec.Builder()
                         .setSchemaTypes(schemaTypes)
                         .setTermMatchType(SearchSpec.TERM_MATCH_TYPE_EXACT_ONLY)
                         .build());
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchResultsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchResultsTest.java
index b44f9e2..3829dc7 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchResultsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchResultsTest.java
@@ -26,7 +26,7 @@
 public class SearchResultsTest {
     @Test
     public void buildSearchSpecWithoutTermMatchType() {
-        assertThrows(RuntimeException.class, () -> SearchSpec.newBuilder()
+        assertThrows(RuntimeException.class, () -> new SearchSpec.Builder()
                 .setSchemaTypes("testSchemaType")
                 .build());
     }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
index c5ae289..17a8ef2 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
@@ -16,15 +16,13 @@
 
 package androidx.appsearch.app;
 
+import android.os.Bundle;
+
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.appsearch.exceptions.IllegalSearchSpecException;
-
-import com.google.android.icing.proto.ResultSpecProto;
-import com.google.android.icing.proto.ScoringSpecProto;
-import com.google.android.icing.proto.SearchSpecProto;
-import com.google.android.icing.proto.TermMatchType;
+import androidx.core.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -38,36 +36,27 @@
 // TODO(sidchhabra) : AddResultSpec fields for Snippets etc.
 public final class SearchSpec {
 
-    private final SearchSpecProto mSearchSpecProto;
-    private final ResultSpecProto mResultSpecProto;
-    private final ScoringSpecProto mScoringSpecProto;
+    static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
+    static final String SCHEMA_TYPES_FIELD = "schemaType";
+    static final String NUM_PER_PAGE_FIELD = "numPerPage";
+    static final String RANKING_STRATEGY_FIELD = "rankingStrategy";
+    static final String ORDER_FILED = "order";
+    static final String NUM_TO_SNIPPET_FIELD = "numToSnippet";
+    static final String NUM_MATCHED_PER_PROPERTY_FIELD = "numMatchedPerProperty";
+    static final String MAX_SNIPPET_FIELD = "maxSnippet";
+    static final int DEFAULT_NUM_PER_PAGE = 10;
 
-    SearchSpec(@NonNull SearchSpecProto searchSpecProto,
-            @NonNull ResultSpecProto resultSpecProto, @NonNull ScoringSpecProto scoringSpecProto) {
-        mSearchSpecProto = searchSpecProto;
-        mResultSpecProto = resultSpecProto;
-        mScoringSpecProto = scoringSpecProto;
+    private final Bundle mBundle;
+
+    SearchSpec(@NonNull Bundle bundle) {
+        Preconditions.checkNotNull(bundle);
+        mBundle = bundle;
     }
 
-    /** Creates a new {@link SearchSpec.Builder}. */
+    /** Returns the {@link Bundle} populated by this builder. */
     @NonNull
-    public static SearchSpec.Builder newBuilder() {
-        return new SearchSpec.Builder();
-    }
-
-    @NonNull
-    SearchSpecProto getSearchSpecProto() {
-        return mSearchSpecProto;
-    }
-
-    @NonNull
-    ResultSpecProto getResultSpecProto() {
-        return mResultSpecProto;
-    }
-
-    @NonNull
-    ScoringSpecProto getScoringSpecProto() {
-        return mScoringSpecProto;
+    Bundle getBundle() {
+        return mBundle;
     }
 
     /** Term Match Type for the query. */
@@ -127,13 +116,13 @@
     /** Builder for {@link SearchSpec objects}. */
     public static final class Builder {
 
-        private final SearchSpecProto.Builder mSearchSpecBuilder = SearchSpecProto.newBuilder();
-        private final ResultSpecProto.Builder mResultSpecBuilder = ResultSpecProto.newBuilder();
-        private final ScoringSpecProto.Builder mScoringSpecBuilder = ScoringSpecProto.newBuilder();
-        private final ResultSpecProto.SnippetSpecProto.Builder mSnippetSpecBuilder =
-                ResultSpecProto.SnippetSpecProto.newBuilder();
+        private final Bundle mBundle;
+        private boolean mBuilt = false;
 
-        Builder() {
+        /** Creates a new {@link SearchSpec.Builder}. */
+        public Builder() {
+            mBundle = new Bundle();
+            mBundle.putInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
         }
 
         /**
@@ -141,13 +130,10 @@
          */
         @NonNull
         public Builder setTermMatchType(@TermMatchTypeCode int termMatchTypeCode) {
-            TermMatchType.Code termMatchTypeCodeProto =
-                    TermMatchType.Code.forNumber(termMatchTypeCode);
-            if (termMatchTypeCodeProto == null) {
-                throw new IllegalArgumentException("Invalid term match type: "
-                        + termMatchTypeCode);
-            }
-            mSearchSpecBuilder.setTermMatchType(termMatchTypeCodeProto);
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Preconditions.checkArgumentInRange(termMatchTypeCode, TERM_MATCH_TYPE_EXACT_ONLY,
+                    TERM_MATCH_TYPE_PREFIX, "Term match type");
+            mBundle.putInt(TERM_MATCH_TYPE_FIELD, termMatchTypeCode);
             return this;
         }
 
@@ -158,29 +144,32 @@
          */
         @NonNull
         public Builder setSchemaTypes(@NonNull String... schemaTypes) {
-            for (String schemaType : schemaTypes) {
-                mSearchSpecBuilder.addSchemaTypeFilters(schemaType);
-            }
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBundle.putStringArray(SCHEMA_TYPES_FIELD, schemaTypes);
             return this;
         }
 
-        /** Sets the number of results per page in the returned object. */
+        /**
+         * Sets the number of results per page in the returned object.
+         * <p> The default number of results per page is 10.
+         */
         @NonNull
         public SearchSpec.Builder setNumPerPage(int numPerPage) {
-            mResultSpecBuilder.setNumPerPage(numPerPage);
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            if (numPerPage <= 0) {
+                throw new IllegalArgumentException("Invalid number per page :" + numPerPage);
+            }
+            mBundle.putInt(NUM_PER_PAGE_FIELD, numPerPage);
             return this;
         }
 
         /** Sets ranking strategy for AppSearch results.*/
         @NonNull
         public Builder setRankingStrategy(@RankingStrategyCode int rankingStrategy) {
-            ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto =
-                    ScoringSpecProto.RankingStrategy.Code.forNumber(rankingStrategy);
-            if (rankingStrategyCodeProto == null) {
-                throw new IllegalArgumentException("Invalid result ranking strategy: "
-                        + rankingStrategyCodeProto);
-            }
-            mScoringSpecBuilder.setRankBy(rankingStrategyCodeProto);
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Preconditions.checkArgumentInRange(rankingStrategy, RANKING_STRATEGY_NONE,
+                    RANKING_STRATEGY_CREATION_TIMESTAMP, "Result ranking strategy");
+            mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy);
             return this;
         }
 
@@ -191,13 +180,10 @@
          */
         @NonNull
         public Builder setOrder(@OrderCode int order) {
-            ScoringSpecProto.Order.Code orderCodeProto =
-                    ScoringSpecProto.Order.Code.forNumber(order);
-            if (orderCodeProto == null) {
-                throw new IllegalArgumentException("Invalid result ranking order: "
-                        + orderCodeProto);
-            }
-            mScoringSpecBuilder.setOrderBy(orderCodeProto);
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Preconditions.checkArgumentInRange(order, ORDER_DESCENDING, ORDER_ASCENDING,
+                    "Result ranking order");
+            mBundle.putInt(ORDER_FILED, order);
             return this;
         }
 
@@ -209,7 +195,8 @@
          */
         @NonNull
         public SearchSpec.Builder setNumToSnippet(int numToSnippet) {
-            mSnippetSpecBuilder.setNumToSnippet(numToSnippet);
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBundle.putInt(NUM_TO_SNIPPET_FIELD, numToSnippet);
             return this;
         }
 
@@ -221,7 +208,8 @@
          */
         @NonNull
         public SearchSpec.Builder setNumMatchesPerProperty(int numMatchesPerProperty) {
-            mSnippetSpecBuilder.setNumMatchesPerProperty(numMatchesPerProperty);
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBundle.putInt(NUM_MATCHED_PER_PROPERTY_FIELD, numMatchesPerProperty);
             return this;
         }
 
@@ -237,7 +225,8 @@
          */
         @NonNull
         public SearchSpec.Builder setMaxSnippetSize(int maxSnippetSize) {
-            mSnippetSpecBuilder.setMaxWindowBytes(maxSnippetSize);
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBundle.putInt(MAX_SNIPPET_FIELD, maxSnippetSize);
             return this;
         }
 
@@ -248,12 +237,12 @@
          */
         @NonNull
         public SearchSpec build() {
-            if (mSearchSpecBuilder.getTermMatchType() == TermMatchType.Code.UNKNOWN) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            if (!mBundle.containsKey(TERM_MATCH_TYPE_FIELD)) {
                 throw new IllegalSearchSpecException("Missing termMatchType field.");
             }
-            mResultSpecBuilder.setSnippetSpec(mSnippetSpecBuilder);
-            return new SearchSpec(mSearchSpecBuilder.build(), mResultSpecBuilder.build(),
-                    mScoringSpecBuilder.build());
+            mBuilt = true;
+            return new SearchSpec(mBundle);
         }
     }
 }
diff --git a/appsearch/local-backend/src/main/java/androidx/appsearch/app/SearchSpecToProtoConverter.java b/appsearch/local-backend/src/main/java/androidx/appsearch/app/SearchSpecToProtoConverter.java
index 1d15bc6..82ebebe 100644
--- a/appsearch/local-backend/src/main/java/androidx/appsearch/app/SearchSpecToProtoConverter.java
+++ b/appsearch/local-backend/src/main/java/androidx/appsearch/app/SearchSpecToProtoConverter.java
@@ -16,6 +16,8 @@
 
 package androidx.appsearch.app;
 
+import android.os.Bundle;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.core.util.Preconditions;
@@ -23,6 +25,9 @@
 import com.google.android.icing.proto.ResultSpecProto;
 import com.google.android.icing.proto.ScoringSpecProto;
 import com.google.android.icing.proto.SearchSpecProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import java.util.Arrays;
 
 /**
  * Translates a {@link SearchSpec} into icing search protos.
@@ -36,20 +41,63 @@
     @NonNull
     public static SearchSpecProto toSearchSpecProto(@NonNull SearchSpec spec) {
         Preconditions.checkNotNull(spec);
-        return spec.getSearchSpecProto();
+        Bundle bundle = spec.getBundle();
+        SearchSpecProto.Builder protoBuilder = SearchSpecProto.newBuilder();
+
+        TermMatchType.Code termMatchTypeCode = TermMatchType.Code.forNumber(
+                bundle.getInt(SearchSpec.TERM_MATCH_TYPE_FIELD));
+        if (termMatchTypeCode == null || termMatchTypeCode.equals(TermMatchType.Code.UNKNOWN)) {
+            throw new IllegalArgumentException("Invalid term match type: " + termMatchTypeCode);
+        }
+
+        protoBuilder.setTermMatchType(termMatchTypeCode);
+        String[] schemaTypes = bundle.getStringArray(SearchSpec.SCHEMA_TYPES_FIELD);
+        if (schemaTypes != null) {
+            protoBuilder.addAllSchemaTypeFilters(Arrays.asList(schemaTypes));
+        }
+        return protoBuilder.build();
     }
 
     /** Extracts {@link ResultSpecProto} information from a {@link SearchSpec}. */
     @NonNull
     public static ResultSpecProto toResultSpecProto(@NonNull SearchSpec spec) {
         Preconditions.checkNotNull(spec);
-        return spec.getResultSpecProto();
+        Bundle bundle = spec.getBundle();
+        return ResultSpecProto.newBuilder()
+                .setNumPerPage(bundle.getInt(
+                        SearchSpec.NUM_PER_PAGE_FIELD, SearchSpec.DEFAULT_NUM_PER_PAGE))
+                .setSnippetSpec(ResultSpecProto.SnippetSpecProto.newBuilder()
+                        .setNumToSnippet(bundle.getInt(SearchSpec.NUM_TO_SNIPPET_FIELD))
+                        .setNumMatchesPerProperty(
+                                bundle.getInt(SearchSpec.NUM_MATCHED_PER_PROPERTY_FIELD))
+                        .setMaxWindowBytes(bundle.getInt(SearchSpec.MAX_SNIPPET_FIELD)))
+                .build();
+
     }
 
     /** Extracts {@link ScoringSpecProto} information from a {@link SearchSpec}. */
     @NonNull
     public static ScoringSpecProto toScoringSpecProto(@NonNull SearchSpec spec) {
         Preconditions.checkNotNull(spec);
-        return spec.getScoringSpecProto();
+        Bundle bundle = spec.getBundle();
+        ScoringSpecProto.Builder protoBuilder = ScoringSpecProto.newBuilder();
+        ScoringSpecProto.Order.Code orderCodeProto =
+                ScoringSpecProto.Order.Code.forNumber(bundle.getInt(SearchSpec.ORDER_FILED));
+        if (orderCodeProto == null) {
+            throw new IllegalArgumentException("Invalid result ranking order: "
+                    + orderCodeProto);
+        }
+        protoBuilder.setOrderBy(orderCodeProto);
+
+        ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto =
+                ScoringSpecProto.RankingStrategy.Code.forNumber(
+                        bundle.getInt(SearchSpec.RANKING_STRATEGY_FIELD));
+        if (rankingStrategyCodeProto == null) {
+            throw new IllegalArgumentException("Invalid result ranking strategy: "
+                    + rankingStrategyCodeProto);
+        }
+        protoBuilder.setRankBy(rankingStrategyCodeProto);
+
+        return protoBuilder.build();
     }
 }
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 72964f7..d8d999d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -88,7 +88,7 @@
 const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
 const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.2.9"
 const val RX_JAVA3 = "io.reactivex.rxjava3:rxjava:3.0.0"
-const val SKIKO_VERSION = "0.1.5"
+const val SKIKO_VERSION = "0.1.6"
 const val SKIKO = "org.jetbrains.skiko:skiko-jvm:$SKIKO_VERSION"
 const val SKIKO_LINUX = "org.jetbrains.skiko:skiko-jvm-runtime-linux:$SKIKO_VERSION"
 const val SKIKO_MACOS = "org.jetbrains.skiko:skiko-jvm-runtime-macos:$SKIKO_VERSION"
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java
index dbdd729..a7f2261 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageSaveLocationValidator.java
@@ -23,6 +23,7 @@
 import androidx.annotation.NonNull;
 
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 
@@ -45,7 +46,6 @@
      * @return true if the image capture result can be saved to the specified storage option,
      * false otherwise.
      */
-    @SuppressWarnings("ConstantConditions")
     static boolean isValid(final @NonNull ImageCapture.OutputFileOptions outputFileOptions) {
         if (isSaveToFile(outputFileOptions)) {
             return canSaveToFile(outputFileOptions.getFile());
@@ -70,10 +70,12 @@
     }
 
     private static boolean canSaveToFile(@NonNull final File file) {
-        try {
-            return file.canWrite();
-        } catch (SecurityException exception) {
-            Logger.e(TAG, "Error while verifying if image capture file is writable", exception);
+        // Try opening a write stream to the output file. If this succeeds, the image save
+        // destination is valid. Otherwise, it's invalid.
+        try (FileOutputStream ignored = new FileOutputStream(file)) {
+            return true;
+        } catch (IOException exception) {
+            Logger.e(TAG, "Failed to open a write stream to " + file.toString(), exception);
             return false;
         }
     }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java
index 11593c5..928636d 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageSaveLocationValidatorTest.java
@@ -62,20 +62,22 @@
     }
 
     @Test
-    public void cannotSaveToReadOnlyFile() throws IOException {
-        final File saveLocation = File.createTempFile("test", ".jpg");
-        saveLocation.setReadOnly();
+    public void canSaveToFileInCache() {
+        final File cacheDir = ApplicationProvider.getApplicationContext().getCacheDir();
+        final String fileName = System.currentTimeMillis() + ".jpg";
+        final File saveLocation = new File(cacheDir, fileName);
         saveLocation.deleteOnExit();
         final ImageCapture.OutputFileOptions outputOptions =
                 new ImageCapture.OutputFileOptions.Builder(saveLocation).build();
 
-        assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isFalse();
+        assertThat(ImageSaveLocationValidator.isValid(outputOptions)).isTrue();
     }
 
     @Test
-    public void cannotSaveToInvalidFile() throws IOException {
+    public void cannotSaveToReadOnlyFile() throws IOException {
         final File saveLocation = File.createTempFile("test", ".jpg");
-        saveLocation.delete();
+        saveLocation.setReadOnly();
+        saveLocation.deleteOnExit();
         final ImageCapture.OutputFileOptions outputOptions =
                 new ImageCapture.OutputFileOptions.Builder(saveLocation).build();
 
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewTransformationDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewTransformationDeviceTest.kt
index c292d8f..7605453 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewTransformationDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewTransformationDeviceTest.kt
@@ -50,7 +50,7 @@
 
         // 1:1 crop rect.
         private val FIT_SURFACE_SIZE = Size(60, 60)
-        private val FIT_CROP_RECT = Rect(0, 0, 60, 60)
+        private val MISMATCHED_CROP_RECT = Rect(0, 0, 60, 60)
         private const val FLOAT_ERROR = 1e-3f
     }
 
@@ -208,33 +208,92 @@
     }
 
     @Test
-    fun fitStart_surfacePositionIsStart() {
-        assertFitTranslationX(PreviewView.ScaleType.FIT_START, LayoutDirection.LTR, 0f)
+    fun mismatchedCropRect_fitStart() {
+        assertForMismatchedCropRect(
+            PreviewView.ScaleType.FIT_START,
+            LayoutDirection.LTR,
+            PREVIEW_VIEW_SIZE.height.toFloat() / MISMATCHED_CROP_RECT.height(),
+            0f,
+            0f
+        )
     }
 
     @Test
-    fun fitCenter_surfacePositionIsCenter() {
-        assertFitTranslationX(PreviewView.ScaleType.FIT_CENTER, LayoutDirection.LTR, 100f)
+    fun mismatchedCropRect_fitCenter() {
+        assertForMismatchedCropRect(
+            PreviewView.ScaleType.FIT_CENTER,
+            LayoutDirection.LTR,
+            PREVIEW_VIEW_SIZE.height.toFloat() / MISMATCHED_CROP_RECT.height(),
+            100f,
+            0f
+        )
     }
 
     @Test
-    fun fitEnd_surfacePositionIsEnd() {
-        assertFitTranslationX(PreviewView.ScaleType.FIT_END, LayoutDirection.LTR, 200f)
+    fun mismatchedCropRect_fitEnd() {
+        assertForMismatchedCropRect(
+            PreviewView.ScaleType.FIT_END,
+            LayoutDirection.LTR,
+            PREVIEW_VIEW_SIZE.height.toFloat() / MISMATCHED_CROP_RECT.height(),
+            200f,
+            0f
+        )
     }
 
     @Test
-    fun fitStartWithRTL_behavesLikeFitEndWithLTR() {
-        assertFitTranslationX(PreviewView.ScaleType.FIT_START, LayoutDirection.RTL, 200f)
+    fun mismatchedCropRect_fillStart() {
+        assertForMismatchedCropRect(
+            PreviewView.ScaleType.FILL_START,
+            LayoutDirection.LTR,
+            PREVIEW_VIEW_SIZE.width.toFloat() / MISMATCHED_CROP_RECT.width(),
+            0f,
+            0f
+        )
     }
 
-    private fun assertFitTranslationX(
+    @Test
+    fun mismatchedCropRect_fillCenter() {
+        assertForMismatchedCropRect(
+            PreviewView.ScaleType.FILL_CENTER,
+            LayoutDirection.LTR,
+            PREVIEW_VIEW_SIZE.width.toFloat() / MISMATCHED_CROP_RECT.width(),
+            0f,
+            -100f
+        )
+    }
+
+    @Test
+    fun mismatchedCropRect_fillEnd() {
+        assertForMismatchedCropRect(
+            PreviewView.ScaleType.FILL_END,
+            LayoutDirection.LTR,
+            PREVIEW_VIEW_SIZE.width.toFloat() / MISMATCHED_CROP_RECT.width(),
+            0f,
+            -200f
+        )
+    }
+
+    @Test
+    fun mismatchedCropRect_fitStartWithRtl_actsLikeFitEnd() {
+        assertForMismatchedCropRect(
+            PreviewView.ScaleType.FIT_START,
+            LayoutDirection.RTL,
+            PREVIEW_VIEW_SIZE.height.toFloat() / MISMATCHED_CROP_RECT.height(),
+            200f,
+            0f
+        )
+    }
+
+    private fun assertForMismatchedCropRect(
         scaleType: PreviewView.ScaleType,
         layoutDirection: Int,
-        translationX: Float
+        scale: Float,
+        translationX: Float,
+        translationY: Float
     ) {
         // Arrange.
         mPreviewTransform.setTransformationInfo(
-            SurfaceRequest.TransformationInfo.of(FIT_CROP_RECT, 90, Surface.ROTATION_90),
+            SurfaceRequest.TransformationInfo.of(MISMATCHED_CROP_RECT, 90, Surface.ROTATION_90),
             FIT_SURFACE_SIZE
         )
         mPreviewTransform.scaleType = scaleType
@@ -243,10 +302,9 @@
         mPreviewTransform.transformView(PREVIEW_VIEW_SIZE, layoutDirection, mView)
 
         // Assert.
-        val scale: Float = PREVIEW_VIEW_SIZE.height.toFloat() / FIT_CROP_RECT.height()
         assertThat(mView.scaleX).isWithin(FLOAT_ERROR).of(scale)
         assertThat(mView.scaleY).isWithin(FLOAT_ERROR).of(scale)
         assertThat(mView.translationX).isWithin(FLOAT_ERROR).of(translationX)
-        assertThat(mView.translationY).isWithin(FLOAT_ERROR).of(0f)
+        assertThat(mView.translationY).isWithin(FLOAT_ERROR).of(translationY)
     }
 }
\ No newline at end of file
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.java
index dc2454d..8fc0720 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.java
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.Manifest;
 import android.content.Context;
 import android.graphics.Bitmap;
 
@@ -39,13 +38,13 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
-import androidx.test.rule.GrantPermissionRule;
 
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.ExecutionException;
@@ -61,8 +60,7 @@
     public final ActivityTestRule<FakeActivity> mActivityRule =
             new ActivityTestRule<>(FakeActivity.class);
     @Rule
-    public final GrantPermissionRule mPermissionRule = GrantPermissionRule.grant(
-            Manifest.permission.CAMERA);
+    public final TestRule mUseCamera = CameraUtil.grantCameraPermissionAndPreTest();
 
     private static final int CAMERA_LENS = CameraSelector.LENS_FACING_BACK;
     private ProcessCameraProvider mCameraProvider;
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt
index 1bd466d..bdfd39b 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt
@@ -34,13 +34,13 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.GrantPermissionRule
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.junit.rules.TestRule
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 import java.util.concurrent.CountDownLatch
@@ -65,7 +65,7 @@
     private lateinit var mCameraProvider: ProcessCameraProvider
 
     @get:Rule
-    var mCameraPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.CAMERA)
+    val mUseCamera: TestRule = CameraUtil.grantCameraPermissionAndPreTest()
 
     @Suppress("DEPRECATION")
     @get:Rule
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java
index 83bf616..37246b9 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java
@@ -33,7 +33,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.Manifest;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.Intent;
@@ -62,6 +61,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.CameraUtil;
 import androidx.camera.testing.SurfaceFormatUtil;
 import androidx.camera.testing.fakes.FakeActivity;
 import androidx.camera.testing.fakes.FakeCamera;
@@ -74,13 +74,13 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
-import androidx.test.rule.GrantPermissionRule;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.junit.After;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.CountDownLatch;
@@ -98,8 +98,7 @@
     private static final Size DEFAULT_SURFACE_SIZE = new Size(640, 480);
 
     @Rule
-    public final GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
-            Manifest.permission.CAMERA);
+    public TestRule mUseCamera = CameraUtil.grantCameraPermissionAndPreTest();
 
     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     @Rule
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java
index 4f17274..23e1b26 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java
@@ -20,6 +20,11 @@
 import static android.graphics.Paint.DITHER_FLAG;
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
 
+import static androidx.camera.view.PreviewView.ScaleType.FILL_CENTER;
+import static androidx.camera.view.PreviewView.ScaleType.FIT_CENTER;
+import static androidx.camera.view.PreviewView.ScaleType.FIT_END;
+import static androidx.camera.view.PreviewView.ScaleType.FIT_START;
+
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
@@ -86,8 +91,7 @@
 
     private static final String TAG = "PreviewTransform";
 
-    private static final PreviewView.ScaleType DEFAULT_SCALE_TYPE =
-            PreviewView.ScaleType.FILL_CENTER;
+    private static final PreviewView.ScaleType DEFAULT_SCALE_TYPE = FILL_CENTER;
 
     // Each vertex is represented by a pair of (x, y) which is 2 slots in a float array.
     private static final int FLOAT_NUMBER_PER_VERTEX = 2;
@@ -220,13 +224,14 @@
         float[] previewViewCropRectVertexes;
         if (isCropRectAspectRatioMatchPreviewView(previewViewSize)) {
             // If crop rect has the same aspect ratio as PreviewView, scale the crop rect to fill
-            // the entire PreviewView. This happens if the scale type is FILL_*.
+            // the entire PreviewView. This happens if the scale type is FILL_* AND a
+            // PreviewView-based viewport is used.
             previewViewCropRectVertexes = sizeToVertexes(previewViewSize);
         } else {
-            // If crop rect's aspect ratio doesn't match PreviewView, fit the crop rect into the
-            // PreviewView. This happens if the scale type is FIT_*.
-            RectF previewViewCropRect = getPreviewViewCropRectForFitTypes(previewViewSize,
-                    layoutDirection);
+            // If the aspect ratios don't match, it could be 1) scale type is FIT_*, 2) the
+            // Viewport is not based on the PreviewView or 3) both.
+            RectF previewViewCropRect = getPreviewViewCropRectForMismatchedAspectRatios(
+                    previewViewSize, layoutDirection);
             previewViewCropRectVertexes = rectToVertexes(previewViewCropRect);
         }
         float[] rotatedPreviewViewCropRectVertexes = createRotatedVertexes(
@@ -241,23 +246,67 @@
     }
 
     /**
-     * Gets the crop rect coordinates in {@link PreviewView} for FIT_* types.
+     * Gets the crop rect in {@link PreviewView} coordinates for the case where crop rect's aspect
+     * ratio doesn't match {@link PreviewView}'s aspect ratio.
+     *
+     * <p> When aspect ratios don't match, additional calculation is needed to figure out how to
+     * fit crop rect into the{@link PreviewView}.
      */
-    RectF getPreviewViewCropRectForFitTypes(Size previewViewSize, int layoutOrientation) {
-        Matrix matrix = new Matrix();
+    RectF getPreviewViewCropRectForMismatchedAspectRatios(Size previewViewSize,
+            int layoutDirection) {
         RectF previewViewRect = new RectF(0, 0, previewViewSize.getWidth(),
                 previewViewSize.getHeight());
         SizeF rotatedCropRectSize = getRotatedCropRectSize();
-        RectF sourceCropRect = new RectF(0, 0, rotatedCropRectSize.getWidth(),
+        RectF rotatedSurfaceCropRect = new RectF(0, 0, rotatedCropRectSize.getWidth(),
                 rotatedCropRectSize.getHeight());
-        // Get the mapping that fits crop rect to preview based on the fit type.
-        matrix.setRectToRect(sourceCropRect, previewViewRect, getMatrixFitScaleType());
-        // Use the mapping to map the crop rect.
-        matrix.mapRect(sourceCropRect);
-        if (layoutOrientation == LayoutDirection.RTL) {
-            return flipHorizontally(sourceCropRect, (float) previewViewSize.getWidth() / 2);
+        Matrix matrix = new Matrix();
+        setMatrixRectToRect(matrix, rotatedSurfaceCropRect, previewViewRect, mScaleType);
+        matrix.mapRect(rotatedSurfaceCropRect);
+        if (layoutDirection == LayoutDirection.RTL) {
+            return flipHorizontally(rotatedSurfaceCropRect, (float) previewViewSize.getWidth() / 2);
         }
-        return sourceCropRect;
+        return rotatedSurfaceCropRect;
+    }
+
+    /**
+     * Set the matrix that maps the source rectangle to the destination rectangle.
+     *
+     * <p> This static method is an extension of {@link Matrix#setRectToRect} with an additional
+     * support for FILL_* types.
+     */
+    private static void setMatrixRectToRect(Matrix matrix, RectF source, RectF destination,
+            PreviewView.ScaleType scaleType) {
+        Matrix.ScaleToFit matrixScaleType;
+        switch (scaleType) {
+            case FIT_CENTER:
+                // Fallthrough.
+            case FILL_CENTER:
+                matrixScaleType = Matrix.ScaleToFit.CENTER;
+                break;
+            case FIT_END:
+                // Fallthrough.
+            case FILL_END:
+                matrixScaleType = Matrix.ScaleToFit.END;
+                break;
+            case FIT_START:
+                // Fallthrough.
+            case FILL_START:
+                matrixScaleType = Matrix.ScaleToFit.START;
+                break;
+            default:
+                Logger.e(TAG, "Unexpected crop rect: " + scaleType);
+                matrixScaleType = Matrix.ScaleToFit.FILL;
+        }
+        boolean isFitTypes =
+                scaleType == FIT_CENTER || scaleType == FIT_START || scaleType == FIT_END;
+        if (isFitTypes) {
+            matrix.setRectToRect(source, destination, matrixScaleType);
+        } else {
+            // android.graphics.Matrix doesn't support fill scale types. The workaround is
+            // mapping inversely from destination to source, then invert the matrix.
+            matrix.setRectToRect(destination, source, matrixScaleType);
+            matrix.invert(matrix);
+        }
     }
 
     /**
@@ -272,23 +321,6 @@
     }
 
     /**
-     * {@link PreviewView.ScaleType} to {@link Matrix.ScaleToFit} conversion.
-     */
-    private Matrix.ScaleToFit getMatrixFitScaleType() {
-        switch (mScaleType) {
-            case FIT_CENTER:
-                return Matrix.ScaleToFit.CENTER;
-            case FIT_END:
-                return Matrix.ScaleToFit.END;
-            case FIT_START:
-                return Matrix.ScaleToFit.START;
-            default:
-                Logger.w(TAG, "Unexpected crop rect. Please use PreviewView.getViewPort().");
-                return Matrix.ScaleToFit.CENTER;
-        }
-    }
-
-    /**
      * Returns crop rect size with target rotation applied.
      */
     private SizeF getRotatedCropRectSize() {
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
index f44b9c3..8b16ab1 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
@@ -19,6 +19,7 @@
 import android.net.Uri
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCaptureException
+import androidx.camera.testing.CameraUtil
 import androidx.camera.view.PreviewView
 import androidx.fragment.app.testing.FragmentScenario
 import androidx.lifecycle.Lifecycle
@@ -34,6 +35,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.ExpectedException
+import org.junit.rules.TestRule
 import org.junit.runner.RunWith
 import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit
@@ -51,8 +53,10 @@
     val thrown: ExpectedException = ExpectedException.none()
 
     @get:Rule
+    val useCamera: TestRule = CameraUtil.grantCameraPermissionAndPreTest()
+
+    @get:Rule
     val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
-        android.Manifest.permission.CAMERA,
         android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
         android.Manifest.permission.RECORD_AUDIO
     )
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt
index d1a6296..6024366 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt
@@ -38,6 +38,7 @@
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.junit.rules.TestRule
 import org.junit.runner.RunWith
 
 @LargeTest
@@ -45,10 +46,14 @@
 class CameraViewFragmentTest {
 
     @get:Rule
+    val useCamera: TestRule = CameraUtil.grantCameraPermissionAndPreTest()
+
+    @get:Rule
     val permissionRule: GrantPermissionRule =
-        GrantPermissionRule.grant(android.Manifest.permission.CAMERA,
+        GrantPermissionRule.grant(
             android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
-            android.Manifest.permission.RECORD_AUDIO)
+            android.Manifest.permission.RECORD_AUDIO
+        )
 
     @Before
     fun setup() {
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.java b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.java
index 61d9d3c..5246d91 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.java
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.java
@@ -39,6 +39,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.CountDownLatch;
@@ -53,8 +54,8 @@
     private static final int PREVIEW_UPDATE_COUNT = 30;
 
     @Rule
-    public GrantPermissionRule mCameraPermissionRule =
-            GrantPermissionRule.grant(android.Manifest.permission.CAMERA);
+    public TestRule mUseCamera = CameraUtil.grantCameraPermissionAndPreTest();
+
     @Rule
     public GrantPermissionRule mStoragePermissionRule =
             GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index 92a5917..5b3d3a2 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -1,6 +1,10 @@
 // Signature format: 3.0
 package androidx.compose.animation.core {
 
+  public final class AndroidAnimationClockKt {
+    method public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function0<? extends androidx.compose.animation.core.AnimationClockObservable> p);
+  }
+
   public abstract class AnimatedFloat extends androidx.compose.animation.core.BaseAnimatedValue<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
     ctor public AnimatedFloat(androidx.compose.animation.core.AnimationClockObservable clock, float visibilityThreshold);
     method public final float getMax();
@@ -27,10 +31,6 @@
     method public static void fling(androidx.compose.animation.core.AnimatedFloat, float startVelocity, androidx.compose.animation.core.FloatDecayAnimationSpec decay = androidx.compose.animation.core.ExponentialDecay(), kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.compose.animation.core.TargetAnimation> adjustTarget, kotlin.jvm.functions.Function3<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,? super java.lang.Float,kotlin.Unit>? onEnd = null);
   }
 
-  public final class AnimationClockKt {
-    method public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function0<? extends androidx.compose.animation.core.AnimationClockObservable> p);
-  }
-
   public interface AnimationClockObservable {
     method public void subscribe(androidx.compose.animation.core.AnimationClockObserver observer);
     method public void unsubscribe(androidx.compose.animation.core.AnimationClockObserver observer);
diff --git a/compose/animation/animation-core/api/public_plus_experimental_current.txt b/compose/animation/animation-core/api/public_plus_experimental_current.txt
index 92a5917..5b3d3a2 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_current.txt
@@ -1,6 +1,10 @@
 // Signature format: 3.0
 package androidx.compose.animation.core {
 
+  public final class AndroidAnimationClockKt {
+    method public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function0<? extends androidx.compose.animation.core.AnimationClockObservable> p);
+  }
+
   public abstract class AnimatedFloat extends androidx.compose.animation.core.BaseAnimatedValue<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
     ctor public AnimatedFloat(androidx.compose.animation.core.AnimationClockObservable clock, float visibilityThreshold);
     method public final float getMax();
@@ -27,10 +31,6 @@
     method public static void fling(androidx.compose.animation.core.AnimatedFloat, float startVelocity, androidx.compose.animation.core.FloatDecayAnimationSpec decay = androidx.compose.animation.core.ExponentialDecay(), kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.compose.animation.core.TargetAnimation> adjustTarget, kotlin.jvm.functions.Function3<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,? super java.lang.Float,kotlin.Unit>? onEnd = null);
   }
 
-  public final class AnimationClockKt {
-    method public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function0<? extends androidx.compose.animation.core.AnimationClockObservable> p);
-  }
-
   public interface AnimationClockObservable {
     method public void subscribe(androidx.compose.animation.core.AnimationClockObserver observer);
     method public void unsubscribe(androidx.compose.animation.core.AnimationClockObserver observer);
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 92a5917..5b3d3a2 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -1,6 +1,10 @@
 // Signature format: 3.0
 package androidx.compose.animation.core {
 
+  public final class AndroidAnimationClockKt {
+    method public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function0<? extends androidx.compose.animation.core.AnimationClockObservable> p);
+  }
+
   public abstract class AnimatedFloat extends androidx.compose.animation.core.BaseAnimatedValue<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
     ctor public AnimatedFloat(androidx.compose.animation.core.AnimationClockObservable clock, float visibilityThreshold);
     method public final float getMax();
@@ -27,10 +31,6 @@
     method public static void fling(androidx.compose.animation.core.AnimatedFloat, float startVelocity, androidx.compose.animation.core.FloatDecayAnimationSpec decay = androidx.compose.animation.core.ExponentialDecay(), kotlin.jvm.functions.Function1<? super java.lang.Float,androidx.compose.animation.core.TargetAnimation> adjustTarget, kotlin.jvm.functions.Function3<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,? super java.lang.Float,kotlin.Unit>? onEnd = null);
   }
 
-  public final class AnimationClockKt {
-    method public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function0<? extends androidx.compose.animation.core.AnimationClockObservable> p);
-  }
-
   public interface AnimationClockObservable {
     method public void subscribe(androidx.compose.animation.core.AnimationClockObserver observer);
     method public void unsubscribe(androidx.compose.animation.core.AnimationClockObserver observer);
diff --git a/compose/animation/animation-core/src/androidMain/kotlin/androidx/compose/animation/core/AndroidAnimationClock.kt b/compose/animation/animation-core/src/androidMain/kotlin/androidx/compose/animation/core/AndroidAnimationClock.kt
index bd12127..e2d9347 100644
--- a/compose/animation/animation-core/src/androidMain/kotlin/androidx/compose/animation/core/AndroidAnimationClock.kt
+++ b/compose/animation/animation-core/src/androidMain/kotlin/androidx/compose/animation/core/AndroidAnimationClock.kt
@@ -22,6 +22,12 @@
 import android.view.Choreographer
 import java.util.concurrent.CountDownLatch
 
+/** @suppress */
+@InternalAnimationApi
+var rootAnimationClockFactory: () -> AnimationClockObservable = { DefaultAnimationClock() }
+    // @TestOnly
+    set
+
 /**
  * Default Choreographer based clock that pushes a new frame to all subscribers on each
  * Choreographer tick, until all subscribers have unsubscribed. An instance of this clock will be
@@ -32,7 +38,7 @@
  * synchronously on the main thread. If this poses a problem, consider initializing this clock on
  * the main thread itself.
  */
-actual class DefaultAnimationClock actual constructor() : BaseAnimationClock() {
+class DefaultAnimationClock : BaseAnimationClock() {
 
     private val mainChoreographer: Choreographer
 
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationClock.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationClock.kt
index e5734ec..6ee51af 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationClock.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationClock.kt
@@ -18,14 +18,6 @@
 
 import androidx.compose.ui.util.annotation.CallSuper
 
-expect class DefaultAnimationClock() : BaseAnimationClock
-
-/** @suppress */
-@InternalAnimationApi
-var rootAnimationClockFactory: () -> AnimationClockObservable = { DefaultAnimationClock() }
-    // @TestOnly
-    set
-
 /**
  * A custom clock whose frame time can be manually updated via mutating [clockTimeMillis].
  * Observers will be called immediately with the current time when they are subscribed. Use
diff --git a/compose/animation/animation-core/src/desktopMain/kotlin/androidx/compose/animation/core/DefaultAnimationClock.kt b/compose/animation/animation-core/src/desktopMain/kotlin/androidx/compose/animation/core/DefaultAnimationClock.kt
deleted file mode 100644
index eef5dd2..0000000
--- a/compose/animation/animation-core/src/desktopMain/kotlin/androidx/compose/animation/core/DefaultAnimationClock.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.animation.core
-
-import androidx.compose.runtime.dispatch.DesktopUiDispatcher
-
-actual class DefaultAnimationClock(
-    fps: Int,
-    private val dispatcher: DesktopUiDispatcher
-) : BaseAnimationClock() {
-
-    // TODO: detect actual display refresh rate? what to do with displays with
-    //  different refresh rates?
-    actual constructor() : this(60, DesktopUiDispatcher.Dispatcher)
-
-    val delay = 1_000 / fps
-
-    @Volatile
-    private var scheduled = false
-
-    private fun frameCallback(time: Long) {
-        scheduled = false
-        dispatchTime(time / 1000000)
-    }
-
-    override fun subscribe(observer: AnimationClockObserver) {
-        if (!scheduled) {
-            dispatcher.scheduleCallbackWithDelay(delay, ::frameCallback)
-            scheduled = true
-        }
-        super.subscribe(observer)
-    }
-
-    override fun dispatchTime(frameTimeMillis: Long) {
-        super.dispatchTime(frameTimeMillis)
-        scheduled = if (hasObservers()) {
-            dispatcher.scheduleCallbackWithDelay(delay, ::frameCallback)
-            true
-        } else {
-            false
-        }
-    }
-}
\ No newline at end of file
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
index 7e8f4a9..1a62aa5 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
@@ -30,7 +30,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.RowScope.weight
+import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.Stack
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
@@ -157,7 +157,7 @@
     alignment: Alignment,
     visible: Boolean
 ) {
-    Stack(Modifier.fillMaxHeight().weight(1f)) {
+    Stack(with(RowScope) { Modifier.fillMaxHeight().weight(1f) }) {
 
         val animationAlignment = if (oppositeDirection) opposite(alignment) else alignment
         val enter = when (animationAlignment) {
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
index e7152d9..a4027e7 100644
--- a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
@@ -44,7 +44,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope.align
+import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.Stack
@@ -166,7 +166,7 @@
     var expanded by remember { mutableStateOf(true) }
     FloatingActionButton(
         onClick = { expanded = !expanded },
-        modifier = Modifier.align(Alignment.CenterHorizontally)
+        modifier = with(ColumnScope) { Modifier.align(Alignment.CenterHorizontally) }
     ) {
         Row(Modifier.padding(start = 12.dp, end = 12.dp)) {
             Icon(Icons.Default.Favorite, Modifier.align(Alignment.CenterVertically))
diff --git a/compose/desktop/desktop/build.gradle b/compose/desktop/desktop/build.gradle
index aa33496..dfb6564 100644
--- a/compose/desktop/desktop/build.gradle
+++ b/compose/desktop/desktop/build.gradle
@@ -47,14 +47,13 @@
             api(KOTLIN_COROUTINES_CORE)
 
             api(SKIKO)
-
-            implementation(KOTLIN_COROUTINES_SWING)
         }
 
         jvmTest {
             resources.srcDirs += new File(SupportConfigKt.getExternalProjectPath(project), "noto-fonts/other/")
             resources.srcDirs += "src/jvmTest/res"
             dependencies {
+                implementation(KOTLIN_COROUTINES_TEST)
                 implementation(SKIKO_CURRENT_OS)
                 implementation project(":ui:ui-test")
                 implementation(JUNIT)
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
index f13e30d..5e42c4a 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
@@ -15,7 +15,11 @@
  */
 package androidx.compose.desktop.examples.example1
 
+import androidx.compose.animation.animate
+import androidx.compose.animation.core.TweenSpec
 import androidx.compose.desktop.AppWindow
+import androidx.compose.desktop.Window
+import androidx.compose.foundation.Box
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.Text
 import androidx.compose.foundation.background
@@ -26,16 +30,17 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.preferredHeight
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.ExperimentalLazyDsl
 import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.text.InlineTextContent
 import androidx.compose.foundation.text.appendInlineContent
 import androidx.compose.material.Button
 import androidx.compose.material.CircularProgressIndicator
 import androidx.compose.material.ExtendedFloatingActionButton
+import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Scaffold
 import androidx.compose.material.Slider
 import androidx.compose.material.TextField
@@ -55,8 +60,6 @@
 import androidx.compose.ui.text.PlaceholderVerticalAlign
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.annotatedString
-import androidx.compose.ui.text.font.fontFamily
-import androidx.compose.ui.text.platform.font
 import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.text.style.TextDecoration.Companion.Underline
 import androidx.compose.ui.unit.IntSize
@@ -66,10 +69,8 @@
 
 private const val title = "Desktop Compose Elements"
 
-val italicFont = fontFamily(font("Noto Italic", "NotoSans-Italic.ttf"))
-
 fun main() {
-    AppWindow(title, IntSize(1024, 768)).show {
+    AppWindow(title, IntSize(1024, 850)).show {
         Scaffold(
             topBar = {
                 TopAppBar(
@@ -116,7 +117,9 @@
         Text(
             text = annotatedString {
                 append("The quick ")
-                appendInlineContent(inlineIndicatorId)
+                if (animation.value) {
+                    appendInlineContent(inlineIndicatorId)
+                }
                 pushStyle(SpanStyle(
                     color = Color(0xff964B00),
                     shadow = Shadow(Color.Green, offset = Offset(1f, 1f))
@@ -195,11 +198,10 @@
                     "    smaller.quickSort() + pivot + greater.quickSort()\n" +
                     "   }\n" +
                     "}",
-            modifier = Modifier.padding(10.dp),
-            fontFamily = italicFont
+            modifier = Modifier.padding(10.dp)
         )
 
-        Button(onClick = {
+        Button(modifier = Modifier.padding(4.dp), onClick = {
             amount.value++
         }) {
             Text("Base")
@@ -209,16 +211,27 @@
             modifier = Modifier.padding(vertical = 10.dp),
             verticalAlignment = Alignment.CenterVertically
         ) {
-            Button(
-                onClick = {
-                    animation.value = !animation.value
-                }) {
-                Text("Toggle")
+            Row {
+                Button(
+                    modifier = Modifier.padding(4.dp),
+                    onClick = {
+                        animation.value = !animation.value
+                    }) {
+                    Text("Toggle")
+                }
+
+                Button(
+                    modifier = Modifier.padding(4.dp),
+                    onClick = {
+                        Window(size = IntSize(400, 200)) {
+                            Animations(isCircularEnabled = animation.value)
+                        }
+                    }) {
+                    Text("Window")
+                }
             }
 
-            if (animation.value) {
-                CircularProgressIndicator()
-            }
+            Animations(isCircularEnabled = animation.value)
         }
 
         Slider(value = amount.value.toFloat() / 100f,
@@ -238,15 +251,30 @@
     }
 }
 
+@Composable
+fun Animations(isCircularEnabled: Boolean) = Row {
+    if (isCircularEnabled) {
+        CircularProgressIndicator(Modifier.padding(10.dp))
+    }
+
+    val enabled = remember { mutableStateOf(true) }
+    val color = animate(
+        if (enabled.value) Color.Green else Color.Red,
+        animSpec = TweenSpec(durationMillis = 2000)
+    )
+
+    MaterialTheme {
+        Box(
+            Modifier.size(70.dp).clickable { enabled.value = !enabled.value },
+            backgroundColor = color
+        )
+    }
+}
+
 @OptIn(ExperimentalLazyDsl::class)
 @Composable
 private fun RightColumn(modifier: Modifier) = LazyColumn(modifier) {
-    items((1..100).toList()) { x ->
-        LazyRow {
-            items((0..23).toList()) { y ->
-                val str = if (y == 0) x.toString() else ('a' + y).toString()
-                Text("$str ")
-            }
-        }
+    items((1..10000).toList()) { x ->
+        Text(x.toString())
     }
 }
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt
index 97f74a1..a120643 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt
@@ -21,7 +21,6 @@
 import androidx.compose.foundation.ContentGravity
 import androidx.compose.foundation.Text
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope.align
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
@@ -57,7 +56,7 @@
         modifier = Modifier.fillMaxSize(),
         color = Color.White
     ) {
-        Column(Modifier.align(Alignment.CenterHorizontally)) {
+        Column {
             Spacer(modifier = Modifier.height(50.dp))
             Row(modifier = Modifier.preferredHeight(40.dp)) {
                 Button("Popup", { popupState.value = true })
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/AppFrame.kt b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/AppFrame.kt
index e3266fd..2ee20f0 100644
--- a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/AppFrame.kt
+++ b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/AppFrame.kt
@@ -42,6 +42,9 @@
     var isCentered: Boolean = true
         protected set
 
+    var isClosed: Boolean = false
+        protected set
+
     val onDismissEvents = mutableListOf<() -> Unit>()
 
     abstract fun setPosition(x: Int, y: Int)
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/AppWindow.kt b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/AppWindow.kt
index ce6fee7..220ef86d 100644
--- a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/AppWindow.kt
+++ b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/AppWindow.kt
@@ -61,6 +61,7 @@
                     if (defaultCloseOperation != WindowConstants.DO_NOTHING_ON_CLOSE) {
                         onDismissEvents.forEach { it.invoke() }
                         AppManager.removeWindow(parent)
+                        isClosed = true
                     }
                 }
             })
@@ -166,15 +167,11 @@
         window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSING))
     }
 
-    internal override fun dispose() {
-        window.apply {
-            disposeCanvas()
-            dispose()
-        }
+    override fun dispose() {
         invoker?.unlockWindow()
     }
 
-    internal override fun lockWindow() {
+    override fun lockWindow() {
         window.apply {
             defaultCloseOperation = WindowConstants.DO_NOTHING_ON_CLOSE
             setFocusableWindowState(false)
@@ -184,7 +181,7 @@
         invoker?.connectPair(this)
     }
 
-    internal override fun unlockWindow() {
+    override fun unlockWindow() {
         window.apply {
             defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
             setFocusableWindowState(true)
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/ComposeWindow.kt b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/ComposeWindow.kt
index acbd3cc..523d2aa 100644
--- a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/ComposeWindow.kt
+++ b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/ComposeWindow.kt
@@ -15,14 +15,11 @@
  */
 package androidx.compose.desktop
 
-import androidx.compose.runtime.dispatch.DesktopUiDispatcher
 import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
 import androidx.compose.ui.input.mouse.MouseScrollEvent
 import androidx.compose.ui.input.mouse.MouseScrollUnit
 import androidx.compose.ui.platform.DesktopOwners
 import org.jetbrains.skija.Canvas
-import org.jetbrains.skiko.SkiaLayer
-import org.jetbrains.skiko.SkiaRenderer
 import java.awt.event.ComponentAdapter
 import java.awt.event.ComponentEvent
 import java.awt.event.InputMethodEvent
@@ -43,7 +40,7 @@
     }
 
     val parent: AppFrame
-    private val layer: SkiaLayer = SkiaLayer()
+    private val layer = FrameSkiaLayer()
 
     var owners: DesktopOwners? = null
         set(value) {
@@ -53,7 +50,7 @@
 
     constructor(parent: AppFrame) : super() {
         this.parent = parent
-        contentPane.add(layer)
+        contentPane.add(layer.wrapped)
 
         addComponentListener(object : ComponentAdapter() {
             override fun componentResized(e: ComponentEvent) {
@@ -63,104 +60,83 @@
         initCanvas()
     }
 
-    fun updateLayer() {
+    private fun updateLayer() {
         if (!isVisible) {
             return
         }
         layer.updateLayer()
     }
 
-    fun redrawLayer() {
+    fun needRedrawLayer() {
         if (!isVisible) {
             return
         }
-        layer.redrawLayer()
+        layer.needRedrawLayer()
+    }
+
+    override fun dispose() {
+        layer.dispose()
+        super.dispose()
     }
 
     private fun initCanvas() {
-        layer.addInputMethodListener(object : InputMethodListener {
+        layer.wrapped.addInputMethodListener(object : InputMethodListener {
             override fun caretPositionChanged(p0: InputMethodEvent?) {
                 TODO("Implement input method caret change")
             }
 
-            override fun inputMethodTextChanged(
-                event: InputMethodEvent
-            ) = DesktopUiDispatcher.Dispatcher.lockCallbacks {
+            override fun inputMethodTextChanged(event: InputMethodEvent) {
                 owners?.onInputMethodTextChanged(event)
             }
         })
 
-        layer.addMouseListener(object : MouseAdapter() {
+        layer.wrapped.addMouseListener(object : MouseAdapter() {
             override fun mouseClicked(event: MouseEvent) = Unit
 
-            override fun mousePressed(
-                event: MouseEvent
-            ) = DesktopUiDispatcher.Dispatcher.lockCallbacks {
+            override fun mousePressed(event: MouseEvent) {
                 owners?.onMousePressed(event.x, event.y)
             }
 
-            override fun mouseReleased(
-                event: MouseEvent
-            ) = DesktopUiDispatcher.Dispatcher.lockCallbacks {
+            override fun mouseReleased(event: MouseEvent) {
                 owners?.onMouseReleased(event.x, event.y)
             }
         })
-        layer.addMouseMotionListener(object : MouseMotionAdapter() {
-            override fun mouseDragged(
-                event: MouseEvent
-            ) = DesktopUiDispatcher.Dispatcher.lockCallbacks {
+        layer.wrapped.addMouseMotionListener(object : MouseMotionAdapter() {
+            override fun mouseDragged(event: MouseEvent) {
                 owners?.onMouseDragged(event.x, event.y)
             }
         })
-        layer.addMouseWheelListener { event ->
-            DesktopUiDispatcher.Dispatcher.lockCallbacks {
-                owners?.onMouseScroll(event.x, event.y, event.toComposeEvent())
-            }
+        layer.wrapped.addMouseWheelListener { event ->
+            owners?.onMouseScroll(event.x, event.y, event.toComposeEvent())
         }
-        layer.addKeyListener(object : KeyAdapter() {
-            override fun keyPressed(
-                event: KeyEvent
-            ) = DesktopUiDispatcher.Dispatcher.lockCallbacks {
+        layer.wrapped.addKeyListener(object : KeyAdapter() {
+            override fun keyPressed(event: KeyEvent) {
                 owners?.onKeyPressed(event.keyCode, event.keyChar)
             }
 
-            override fun keyReleased(
-                event: KeyEvent
-            ) = DesktopUiDispatcher.Dispatcher.lockCallbacks {
+            override fun keyReleased(event: KeyEvent) {
                 owners?.onKeyReleased(event.keyCode, event.keyChar)
             }
 
-            override fun keyTyped(
-                event: KeyEvent
-            ) = DesktopUiDispatcher.Dispatcher.lockCallbacks {
+            override fun keyTyped(event: KeyEvent) {
                 owners?.onKeyTyped(event.keyChar)
             }
         })
     }
 
-    fun disposeCanvas() {
-        layer.disposeLayer()
-        layer.updateLayer()
-        layer.renderer!!.onDispose()
-    }
-
     override fun setVisible(value: Boolean) {
-        super.setVisible(value)
-        updateLayer()
+        if (value != isVisible) {
+            super.setVisible(value)
+            updateLayer()
+            needRedrawLayer()
+        }
     }
 }
 
-private class OwnersRenderer(private val owners: DesktopOwners) : SkiaRenderer {
-    override fun onDispose() = Unit
-    override fun onInit() = Unit
-    override fun onReshape(width: Int, height: Int) = Unit
-
-    override fun onRender(canvas: Canvas, width: Int, height: Int) {
+private class OwnersRenderer(private val owners: DesktopOwners) : FrameSkiaLayer.Renderer {
+    override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
         try {
-            Thread.currentThread().contextClassLoader = ClassLoader.getSystemClassLoader()
-            DesktopUiDispatcher.Dispatcher.lockCallbacks {
-                owners.onRender(canvas, width, height)
-            }
+            owners.onRender(canvas, width, height, nanoTime)
         } catch (e: Throwable) {
             e.printStackTrace(System.err)
             if (System.getProperty("compose.desktop.ignore.errors") == null) {
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/FrameDispatcher.kt b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/FrameDispatcher.kt
new file mode 100644
index 0000000..96803b3
--- /dev/null
+++ b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/FrameDispatcher.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.desktop
+
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.yield
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Dispatch frame after call of [scheduleFrame]
+ *
+ * Frames executed not more frequent than [framesPerSecond]
+ */
+internal class FrameDispatcher(
+    private val onFrame: suspend (nanoTime: Long) -> Unit,
+    private val framesPerSecond: () -> Int,
+    private val nanoTime: () -> Long = System::nanoTime,
+    context: CoroutineContext = Dispatchers.Main
+) {
+    private var needFrame = CompletableDeferred<Unit>()
+
+    private val job = GlobalScope.launch(context) {
+        while (true) {
+            needFrame.await()
+            needFrame = CompletableDeferred()
+
+            val frameNanoTime = nanoTime()
+            onFrame(frameNanoTime)
+
+            val elapsed = nanoTime() - frameNanoTime
+            val refreshRate = framesPerSecond()
+            val singleFrameNanos = 1_000_000_000 / refreshRate
+            val needToWaitMillis = maxOf(0, singleFrameNanos - elapsed) / 1_000_000
+            delayOrYield(needToWaitMillis)
+        }
+    }
+
+    private suspend fun delayOrYield(millis: Long) {
+        if (millis > 0) {
+            delay(millis)
+        } else {
+            yield()
+        }
+    }
+
+    fun cancel() {
+        job.cancel()
+    }
+
+    fun scheduleFrame() {
+        needFrame.complete(Unit)
+    }
+}
\ No newline at end of file
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/FrameSkiaLayer.kt b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/FrameSkiaLayer.kt
new file mode 100644
index 0000000..bce3980
--- /dev/null
+++ b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/FrameSkiaLayer.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.desktop
+
+import org.jetbrains.skija.Canvas
+import org.jetbrains.skija.Picture
+import org.jetbrains.skija.PictureRecorder
+import org.jetbrains.skija.Rect
+import org.jetbrains.skiko.SkiaLayer
+import org.jetbrains.skiko.SkiaRenderer
+import java.awt.DisplayMode
+
+internal class FrameSkiaLayer {
+    var renderer: Renderer? = null
+
+    private var isDisposed = false
+    private var frameNanoTime = 0L
+    private val frameDispatcher = FrameDispatcher(
+        onFrame = { onFrame(it) },
+        framesPerSecond = ::getFramesPerSecond
+    )
+
+    @Volatile private var picture: Picture? = null
+    private val pictureRecorder = PictureRecorder()
+
+    private fun onFrame(nanoTime: Long) {
+        this.frameNanoTime = nanoTime
+        wrapped.redrawLayer()
+    }
+
+    val wrapped = object : SkiaLayer() {
+        override fun redrawLayer() {
+            preparePicture(frameNanoTime)
+            super.redrawLayer()
+        }
+    }
+
+    init {
+        wrapped.renderer = object : SkiaRenderer {
+            override fun onRender(canvas: Canvas, width: Int, height: Int) {
+                if (picture != null) {
+                    canvas.drawPicture(picture)
+                }
+            }
+
+            override fun onDispose() = Unit
+            override fun onInit() = Unit
+            override fun onReshape(width: Int, height: Int) = Unit
+        }
+    }
+
+    // We draw into picture, because SkiaLayer.draw can be called from the other thread,
+    // but onRender should be called in AWT thread. Picture doesn't add any visible overhead on
+    // CPU/RAM.
+    private fun preparePicture(frameTimeNanos: Long) {
+        val bounds = Rect.makeWH(wrapped.width.toFloat(), wrapped.height.toFloat())
+        val pictureCanvas = pictureRecorder.beginRecording(bounds)
+        renderer?.onRender(pictureCanvas, wrapped.width, wrapped.height, frameTimeNanos)
+        picture?.close()
+        picture = pictureRecorder.finishRecordingAsPicture()
+    }
+
+    fun reinit() {
+        check(!isDisposed)
+        wrapped.reinit()
+    }
+
+    private fun getFramesPerSecond(): Int {
+        val refreshRate = wrapped.graphicsConfiguration.device.displayMode.refreshRate
+        return if (refreshRate != DisplayMode.REFRESH_RATE_UNKNOWN) refreshRate else 60
+    }
+
+    fun updateLayer() {
+        check(!isDisposed)
+        wrapped.updateLayer()
+    }
+
+    fun dispose() {
+        check(!isDisposed)
+        frameDispatcher.cancel()
+        wrapped.disposeLayer()
+        wrapped.updateLayer()
+        picture?.close()
+        pictureRecorder.close()
+        isDisposed = true
+    }
+
+    fun needRedrawLayer() {
+        check(!isDisposed)
+        frameDispatcher.scheduleFrame()
+    }
+
+    interface Renderer {
+        fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long)
+    }
+}
\ No newline at end of file
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/Wrapper.kt b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/Wrapper.kt
index c92f9bb8..a23d2f2 100644
--- a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/Wrapper.kt
+++ b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/Wrapper.kt
@@ -22,7 +22,7 @@
 import androidx.compose.ui.platform.setContent
 
 fun ComposeWindow.setContent(content: @Composable () -> Unit): Composition {
-    val owners = DesktopOwners(this, this::redrawLayer)
+    val owners = DesktopOwners(this, this::needRedrawLayer)
     val owner = DesktopOwner(owners)
     val composition = owner.setContent(content)
 
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/ui/window/AppDialog.kt b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/ui/window/AppDialog.kt
index 15c42f1..e41551e 100644
--- a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/ui/window/AppDialog.kt
+++ b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/ui/window/AppDialog.kt
@@ -53,6 +53,8 @@
     }
 
     onDispose {
-        dialog.dispose()
+        if (!dialog.isClosed) {
+            dialog.close()
+        }
     }
 }
diff --git a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/desktop/FrameDispatcherTest.kt b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/desktop/FrameDispatcherTest.kt
new file mode 100644
index 0000000..a40d6fc7
--- /dev/null
+++ b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/desktop/FrameDispatcherTest.kt
@@ -0,0 +1,219 @@
+/*
+ * 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.desktop
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class FrameDispatcherTest {
+    var frameIndex = 0
+    var frameTime = 0L
+
+    var frameDuration = 0L
+
+    suspend fun onFrame(nanoTime: Long) {
+        frameIndex++
+        frameTime = nanoTime / 1_000_000
+        delay(frameDuration)
+    }
+
+    fun TestCoroutineScope.testFrameDispatcher() = FrameDispatcher(
+        ::onFrame,
+        framesPerSecond = { 100 }, // one frame is 10 milliseconds
+        nanoTime = { currentTime * 1_000_000 },
+        coroutineContext
+    )
+
+    @Test
+    fun `don't schedule`() = runBlockingTest {
+        val timer = testFrameDispatcher()
+
+        runCurrent()
+        assertEquals(0, currentTime)
+        assertEquals(0, frameIndex)
+        assertEquals(0, frameTime)
+
+        advanceTimeBy(10_000)
+        assertEquals(0, frameIndex)
+        assertEquals(0, frameTime)
+
+        timer.cancel()
+    }
+
+    @Test
+    fun `schedule one time`() = runBlockingTest {
+        val timer = testFrameDispatcher()
+
+        advanceTimeBy(1234)
+        timer.scheduleFrame()
+        assertEquals(1, frameIndex)
+        assertEquals(1234, frameTime)
+
+        advanceTimeBy(10_000)
+        assertEquals(1, frameIndex)
+        assertEquals(1234, frameTime)
+
+        timer.cancel()
+    }
+
+    @Test
+    fun `schedule multiple times`() = runBlockingTest {
+        val timer = testFrameDispatcher()
+
+        advanceTimeBy(10_000)
+        timer.scheduleFrame()
+
+        timer.scheduleFrame()
+        timer.scheduleFrame()
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+
+        advanceTimeBy(9)
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+
+        advanceTimeBy(1)
+        assertEquals(2, frameIndex)
+        assertEquals(10_010, frameTime)
+
+        advanceTimeBy(10_000)
+        assertEquals(2, frameIndex)
+        assertEquals(10_010, frameTime)
+
+        timer.cancel()
+    }
+
+    @Test
+    fun `schedule after short delay`() = runBlockingTest {
+        val timer = testFrameDispatcher()
+
+        advanceTimeBy(10_000)
+        timer.scheduleFrame()
+
+        advanceTimeBy(5)
+        timer.scheduleFrame()
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+
+        advanceTimeBy(4)
+        timer.scheduleFrame()
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+
+        advanceTimeBy(1)
+        timer.scheduleFrame()
+        assertEquals(2, frameIndex)
+        assertEquals(10_010, frameTime)
+
+        timer.cancel()
+    }
+
+    @Test
+    fun `schedule after long delay`() = runBlockingTest {
+        val timer = testFrameDispatcher()
+
+        advanceTimeBy(10_000)
+        timer.scheduleFrame()
+
+        advanceTimeBy(10_000)
+        timer.scheduleFrame()
+        assertEquals(2, frameIndex)
+        assertEquals(20_000, frameTime)
+
+        timer.cancel()
+    }
+
+    @Test
+    fun `schedule after short frame`() = runBlockingTest {
+        val timer = testFrameDispatcher()
+        frameDuration = 7
+
+        advanceTimeBy(10_000)
+        timer.scheduleFrame()
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+
+        timer.scheduleFrame()
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+
+        advanceTimeBy(9)
+        timer.scheduleFrame()
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+
+        advanceTimeBy(1)
+        assertEquals(2, frameIndex)
+        assertEquals(10_010, frameTime)
+
+        advanceTimeBy(10_000)
+        assertEquals(2, frameIndex)
+        assertEquals(10_010, frameTime)
+
+        timer.cancel()
+    }
+
+    @Test
+    fun `schedule after long frame`() = runBlockingTest {
+        val timer = testFrameDispatcher()
+        frameDuration = 13
+
+        advanceTimeBy(10_000)
+        timer.scheduleFrame()
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+
+        timer.scheduleFrame()
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+
+        advanceTimeBy(12)
+        timer.scheduleFrame()
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+
+        advanceTimeBy(1)
+        assertEquals(2, frameIndex)
+        assertEquals(10_013, frameTime)
+
+        advanceTimeBy(10_000)
+        assertEquals(2, frameIndex)
+        assertEquals(10_013, frameTime)
+
+        timer.cancel()
+    }
+
+    @Test
+    fun cancel() = runBlockingTest {
+        val timer = testFrameDispatcher()
+
+        advanceTimeBy(10_000)
+        timer.scheduleFrame()
+
+        timer.scheduleFrame()
+        timer.scheduleFrame()
+        timer.cancel()
+        advanceTimeBy(10_000)
+        assertEquals(1, frameIndex)
+        assertEquals(10_000, frameTime)
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-layout/api/current.txt b/compose/foundation/foundation-layout/api/current.txt
index 348d88b..245230d 100644
--- a/compose/foundation/foundation-layout/api/current.txt
+++ b/compose/foundation/foundation-layout/api/current.txt
@@ -86,13 +86,16 @@
     method @androidx.compose.runtime.Composable public static inline void Column(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement = Arrangement.Top, androidx.compose.ui.Alignment.Horizontal horizontalAlignment = Alignment.Start, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> children);
   }
 
-  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public final class ColumnScope {
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal alignment);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.VerticalAlignmentLine alignmentLine);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
-    method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal align);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
-    field public static final androidx.compose.foundation.layout.ColumnScope INSTANCE;
+  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ColumnScope {
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal alignment);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.VerticalAlignmentLine alignmentLine);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
+    method @Deprecated @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal align);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
+    field public static final androidx.compose.foundation.layout.ColumnScope.Companion Companion;
+  }
+
+  public static final class ColumnScope.Companion implements androidx.compose.foundation.layout.ColumnScope {
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstrainScope {
@@ -417,13 +420,16 @@
     method @androidx.compose.runtime.Composable public static inline void Row(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement = Arrangement.Start, androidx.compose.ui.Alignment.Vertical verticalAlignment = Alignment.Top, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> children);
   }
 
-  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public final class RowScope {
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical alignment);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.HorizontalAlignmentLine alignmentLine);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
-    method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical align);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
-    field public static final androidx.compose.foundation.layout.RowScope INSTANCE;
+  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface RowScope {
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical alignment);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.HorizontalAlignmentLine alignmentLine);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
+    method @Deprecated @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical align);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
+    field public static final androidx.compose.foundation.layout.RowScope.Companion Companion;
+  }
+
+  public static final class RowScope.Companion implements androidx.compose.foundation.layout.RowScope {
   }
 
   public enum SizeMode {
@@ -441,11 +447,14 @@
     method @androidx.compose.runtime.Composable public static void Stack(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.Alignment alignment = Alignment.TopStart, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.StackScope,kotlin.Unit> children);
   }
 
-  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public final class StackScope {
-    ctor public StackScope();
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment alignment);
-    method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment align);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier matchParentSize(androidx.compose.ui.Modifier);
+  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface StackScope {
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment alignment);
+    method @Deprecated @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment align);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier matchParentSize(androidx.compose.ui.Modifier);
+    field public static final androidx.compose.foundation.layout.StackScope.Companion Companion;
+  }
+
+  public static final class StackScope.Companion implements androidx.compose.foundation.layout.StackScope {
   }
 
   public final class State extends androidx.constraintlayout.core.state.State {
diff --git a/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt b/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
index 348d88b..245230d 100644
--- a/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
@@ -86,13 +86,16 @@
     method @androidx.compose.runtime.Composable public static inline void Column(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement = Arrangement.Top, androidx.compose.ui.Alignment.Horizontal horizontalAlignment = Alignment.Start, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> children);
   }
 
-  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public final class ColumnScope {
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal alignment);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.VerticalAlignmentLine alignmentLine);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
-    method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal align);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
-    field public static final androidx.compose.foundation.layout.ColumnScope INSTANCE;
+  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ColumnScope {
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal alignment);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.VerticalAlignmentLine alignmentLine);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
+    method @Deprecated @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal align);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
+    field public static final androidx.compose.foundation.layout.ColumnScope.Companion Companion;
+  }
+
+  public static final class ColumnScope.Companion implements androidx.compose.foundation.layout.ColumnScope {
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstrainScope {
@@ -417,13 +420,16 @@
     method @androidx.compose.runtime.Composable public static inline void Row(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement = Arrangement.Start, androidx.compose.ui.Alignment.Vertical verticalAlignment = Alignment.Top, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> children);
   }
 
-  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public final class RowScope {
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical alignment);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.HorizontalAlignmentLine alignmentLine);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
-    method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical align);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
-    field public static final androidx.compose.foundation.layout.RowScope INSTANCE;
+  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface RowScope {
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical alignment);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.HorizontalAlignmentLine alignmentLine);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
+    method @Deprecated @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical align);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
+    field public static final androidx.compose.foundation.layout.RowScope.Companion Companion;
+  }
+
+  public static final class RowScope.Companion implements androidx.compose.foundation.layout.RowScope {
   }
 
   public enum SizeMode {
@@ -441,11 +447,14 @@
     method @androidx.compose.runtime.Composable public static void Stack(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.Alignment alignment = Alignment.TopStart, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.StackScope,kotlin.Unit> children);
   }
 
-  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public final class StackScope {
-    ctor public StackScope();
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment alignment);
-    method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment align);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier matchParentSize(androidx.compose.ui.Modifier);
+  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface StackScope {
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment alignment);
+    method @Deprecated @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment align);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier matchParentSize(androidx.compose.ui.Modifier);
+    field public static final androidx.compose.foundation.layout.StackScope.Companion Companion;
+  }
+
+  public static final class StackScope.Companion implements androidx.compose.foundation.layout.StackScope {
   }
 
   public final class State extends androidx.constraintlayout.core.state.State {
diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt
index e2e2300..19273b4 100644
--- a/compose/foundation/foundation-layout/api/restricted_current.txt
+++ b/compose/foundation/foundation-layout/api/restricted_current.txt
@@ -88,13 +88,16 @@
     field @kotlin.PublishedApi internal static final androidx.compose.ui.node.LayoutNode.MeasureBlocks DefaultColumnMeasureBlocks;
   }
 
-  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public final class ColumnScope {
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal alignment);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.VerticalAlignmentLine alignmentLine);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
-    method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal align);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
-    field public static final androidx.compose.foundation.layout.ColumnScope INSTANCE;
+  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ColumnScope {
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal alignment);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.VerticalAlignmentLine alignmentLine);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
+    method @Deprecated @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Horizontal align);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
+    field public static final androidx.compose.foundation.layout.ColumnScope.Companion Companion;
+  }
+
+  public static final class ColumnScope.Companion implements androidx.compose.foundation.layout.ColumnScope {
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstrainScope {
@@ -422,13 +425,16 @@
     field @kotlin.PublishedApi internal static final androidx.compose.ui.node.LayoutNode.MeasureBlocks DefaultRowMeasureBlocks;
   }
 
-  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public final class RowScope {
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical alignment);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.HorizontalAlignmentLine alignmentLine);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
-    method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical align);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
-    field public static final androidx.compose.foundation.layout.RowScope INSTANCE;
+  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface RowScope {
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical alignment);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, androidx.compose.ui.HorizontalAlignmentLine alignmentLine);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier alignWithSiblings(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.Measured,java.lang.Integer> alignmentLineBlock);
+    method @Deprecated @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment.Vertical align);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=3.4E38, fromInclusive=false) float weight, boolean fill = true);
+    field public static final androidx.compose.foundation.layout.RowScope.Companion Companion;
+  }
+
+  public static final class RowScope.Companion implements androidx.compose.foundation.layout.RowScope {
   }
 
   public enum SizeMode {
@@ -446,11 +452,14 @@
     method @androidx.compose.runtime.Composable public static void Stack(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.Alignment alignment = Alignment.TopStart, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.StackScope,kotlin.Unit> children);
   }
 
-  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public final class StackScope {
-    ctor public StackScope();
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment alignment);
-    method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment align);
-    method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier matchParentSize(androidx.compose.ui.Modifier);
+  @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface StackScope {
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier align(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment alignment);
+    method @Deprecated @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier gravity(androidx.compose.ui.Modifier, androidx.compose.ui.Alignment align);
+    method @androidx.compose.runtime.Stable public default androidx.compose.ui.Modifier matchParentSize(androidx.compose.ui.Modifier);
+    field public static final androidx.compose.foundation.layout.StackScope.Companion Companion;
+  }
+
+  public static final class StackScope.Companion implements androidx.compose.foundation.layout.StackScope {
   }
 
   public final class State extends androidx.constraintlayout.core.state.State {
diff --git a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/RelativePaddingFromSamples.kt b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/RelativePaddingFromSamples.kt
index 1f11695..81b12b4 100644
--- a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/RelativePaddingFromSamples.kt
+++ b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/RelativePaddingFromSamples.kt
@@ -22,13 +22,21 @@
 import androidx.compose.foundation.text.FirstBaseline
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
+import androidx.compose.ui.platform.DensityAmbient
+import androidx.compose.ui.unit.sp
 
 @Sampled
 @Composable
 fun RelativePaddingFromSample() {
+    // We want to have 30.sp distance from the top of the layout box to the baseline of the
+    // first line of text.
+    val distanceToBaseline = 30.sp
+    // We convert the 30.sp value to dps, which is required for the relativePaddingFrom API.
+    val distanceToBaselineDp = with(DensityAmbient.current) { distanceToBaseline.toDp() }
+    // The result will be a layout with 30.sp distance from the top of the layout box to the
+    // baseline of the first line of text.
     Text(
         text = "This is an example.",
-        modifier = Modifier.relativePaddingFrom(FirstBaseline, before = 30.dp)
+        modifier = Modifier.relativePaddingFrom(FirstBaseline, before = distanceToBaselineDp)
     )
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
index d24efd2..da3734e 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
@@ -16,9 +16,6 @@
 
 package androidx.compose.foundation.layout
 
-import androidx.compose.foundation.layout.ColumnScope.alignWithSiblings
-import androidx.compose.foundation.layout.ColumnScope.weight
-import androidx.compose.foundation.layout.RowScope.alignWithSiblings
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
@@ -118,7 +115,7 @@
  */
 @LayoutScopeMarker
 @Immutable
-object ColumnScope {
+interface ColumnScope {
     /**
      * Align the element horizontally within the [Column]. This alignment will have priority over
      * the [Column]'s `horizontalAlignment` parameter.
@@ -195,4 +192,6 @@
     fun Modifier.alignWithSiblings(
         alignmentLineBlock: (Measured) -> Int
     ) = this.then(SiblingsAlignedModifier.WithAlignmentLineBlock(alignmentLineBlock))
+
+    companion object : ColumnScope
 }
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
index 736f0dda..a9a342c 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.foundation.layout
 
-import androidx.compose.foundation.layout.RowScope.weight
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
@@ -123,7 +122,7 @@
  */
 @LayoutScopeMarker
 @Immutable
-object RowScope {
+interface RowScope {
     /**
      * Align the element vertically within the [Row]. This alignment will have priority over the
      * [Row]'s `verticalAlignment` parameter.
@@ -199,4 +198,6 @@
     fun Modifier.alignWithSiblings(
         alignmentLineBlock: (Measured) -> Int
     ) = this.then(SiblingsAlignedModifier.WithAlignmentLineBlock(alignmentLineBlock))
+
+    companion object : RowScope
 }
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Stack.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Stack.kt
index f59f573..2e8a212 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Stack.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Stack.kt
@@ -51,7 +51,7 @@
     alignment: Alignment = Alignment.TopStart,
     children: @Composable StackScope.() -> Unit
 ) {
-    val stackChildren: @Composable () -> Unit = { StackScope().children() }
+    val stackChildren: @Composable () -> Unit = { StackScope.children() }
 
     Layout(stackChildren, modifier = modifier) { measurables, constraints ->
         val placeables = arrayOfNulls<Placeable>(measurables.size)
@@ -102,7 +102,7 @@
  */
 @LayoutScopeMarker
 @Immutable
-class StackScope {
+interface StackScope {
     /**
      * Pull the content element to a specific [Alignment] within the [Stack]. This alignment will
      * have priority over the [Stack]'s `alignment` parameter.
@@ -129,12 +129,12 @@
     @Stable
     fun Modifier.matchParentSize() = this.then(StretchAlignModifier)
 
-    internal companion object {
-        @Stable
-        val StretchAlignModifier: ParentDataModifier = StackChildData(Alignment.Center, true)
-    }
+    companion object : StackScope
 }
 
+@Stable
+private val StretchAlignModifier: ParentDataModifier = StackChildData(Alignment.Center, true)
+
 private val Measurable.stackChildData: StackChildData? get() = parentData as? StackChildData
 private val Measurable.stretch: Boolean get() = stackChildData?.stretch ?: false
 
diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
index bd0cd7e1..e8538a4 100644
--- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
+++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
@@ -37,7 +37,7 @@
     val surface = Surface.makeRasterN32Premul(width, height)
     val canvas = surface.canvas
     val component = object : Component() {}
-    val owners = DesktopOwners(component = component, redraw = {})
+    val owners = DesktopOwners(component = component, invalidate = {})
 
     fun setContent(content: @Composable () -> Unit): DesktopOwners {
         val owner = DesktopOwner(owners, density)
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index 86bc5ab..8bf4929 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -12,7 +12,7 @@
     method @androidx.compose.runtime.Composable public static void TopAppBar-ye6PvEY(androidx.compose.ui.Modifier modifier = Modifier, long backgroundColor = MaterialTheme.colors.primarySurface, long contentColor = contentColorFor(backgroundColor), float elevation = androidx.compose.material.AppBarKt.TopAppBarElevation, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
-  public final class BackdropConstants {
+  public final class BackdropScaffoldConstants {
     method public float getDefaultFrontLayerElevation();
     method public long getDefaultFrontLayerScrimColor();
     method public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
@@ -23,12 +23,12 @@
     property public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
     property public final float DefaultHeaderHeight;
     property public final float DefaultPeekHeight;
-    field public static final androidx.compose.material.BackdropConstants INSTANCE;
+    field public static final androidx.compose.material.BackdropScaffoldConstants INSTANCE;
   }
 
   public final class BackdropScaffoldKt {
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold-n7o2bDw(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BackdropScaffoldState backdropScaffoldState = rememberBackdropState(Concealed), boolean gesturesEnabled = true, float peekHeight = BackdropConstants.DefaultPeekHeight, float headerHeight = BackdropConstants.DefaultHeaderHeight, boolean persistentAppBar = true, boolean stickyFrontLayer = true, long backLayerBackgroundColor = MaterialTheme.colors.primary, long backLayerContentColor = contentColorFor(backLayerBackgroundColor), androidx.compose.ui.graphics.Shape frontLayerShape = BackdropConstants.DefaultFrontLayerShape, float frontLayerElevation = BackdropConstants.DefaultFrontLayerElevation, long frontLayerBackgroundColor = MaterialTheme.colors.surface, long frontLayerContentColor = contentColorFor(frontLayerBackgroundColor), long frontLayerScrimColor = BackdropConstants.DefaultFrontLayerScrimColor, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent);
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange = { return true }, androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold-n7o2bDw(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BackdropScaffoldState scaffoldState = rememberBackdropScaffoldState(Concealed), boolean gesturesEnabled = true, float peekHeight = BackdropScaffoldConstants.DefaultPeekHeight, float headerHeight = BackdropScaffoldConstants.DefaultHeaderHeight, boolean persistentAppBar = true, boolean stickyFrontLayer = true, long backLayerBackgroundColor = MaterialTheme.colors.primary, long backLayerContentColor = contentColorFor(backLayerBackgroundColor), androidx.compose.ui.graphics.Shape frontLayerShape = BackdropScaffoldConstants.DefaultFrontLayerShape, float frontLayerElevation = BackdropScaffoldConstants.DefaultFrontLayerElevation, long frontLayerBackgroundColor = MaterialTheme.colors.surface, long frontLayerContentColor = contentColorFor(frontLayerBackgroundColor), long frontLayerScrimColor = BackdropScaffoldConstants.DefaultFrontLayerScrimColor, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock = AnimationClockAmbient.current, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange = { return true }, androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
   }
 
   @androidx.compose.material.ExperimentalMaterialApi public final class BackdropScaffoldState extends androidx.compose.material.SwipeableState<androidx.compose.material.BackdropValue> {
@@ -87,6 +87,49 @@
 }), long selectedContentColor = contentColor(), long unselectedContentColor = EmphasisAmbient.current.medium.applyEmphasis(selectedContentColor));
   }
 
+  public final class BottomSheetScaffoldConstants {
+    method public float getDefaultSheetElevation();
+    method public float getDefaultSheetPeekHeight();
+    property public final float DefaultSheetElevation;
+    property public final float DefaultSheetPeekHeight;
+    field public static final androidx.compose.material.BottomSheetScaffoldConstants INSTANCE;
+  }
+
+  public final class BottomSheetScaffoldKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BottomSheetScaffold-0Ttp7_s(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BottomSheetScaffoldState scaffoldState = rememberBottomSheetScaffoldState(), kotlin.jvm.functions.Function0<kotlin.Unit>? topBar = null, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = null, androidx.compose.material.FabPosition floatingActionButtonPosition = androidx.compose.material.FabPosition.End, boolean sheetGesturesEnabled = true, androidx.compose.ui.graphics.Shape sheetShape = large, float sheetElevation = BottomSheetScaffoldConstants.DefaultSheetElevation, long sheetBackgroundColor = MaterialTheme.colors.surface, long sheetContentColor = contentColorFor(sheetBackgroundColor), float sheetPeekHeight = BottomSheetScaffoldConstants.DefaultSheetPeekHeight, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent = null, boolean drawerGesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long drawerScrimColor = DrawerConstants.defaultScrimColor, long backgroundColor = MaterialTheme.colors.background, long contentColor = contentColorFor(backgroundColor), kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> bodyContent);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetScaffoldState rememberBottomSheetScaffoldState(androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), androidx.compose.material.BottomSheetState bottomSheetState = rememberBottomSheetState(BottomSheetValue.Collapsed), androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetState rememberBottomSheetState(androidx.compose.material.BottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock = AnimationClockAmbient.current, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange = { return true });
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class BottomSheetScaffoldState {
+    ctor public BottomSheetScaffoldState(androidx.compose.material.DrawerState drawerState, androidx.compose.material.BottomSheetState bottomSheetState, androidx.compose.material.SnackbarHostState snackbarHostState);
+    method public androidx.compose.material.BottomSheetState getBottomSheetState();
+    method public androidx.compose.material.DrawerState getDrawerState();
+    method public androidx.compose.material.SnackbarHostState getSnackbarHostState();
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class BottomSheetState extends androidx.compose.material.SwipeableState<androidx.compose.material.BottomSheetValue> {
+    ctor public BottomSheetState(androidx.compose.material.BottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange);
+    method public void collapse(kotlin.jvm.functions.Function0<kotlin.Unit>? onCollapsed = null);
+    method public void expand(kotlin.jvm.functions.Function0<kotlin.Unit>? onExpanded = null);
+    method public boolean isCollapsed();
+    method public boolean isExpanded();
+    property public final boolean isCollapsed;
+    property public final boolean isExpanded;
+    field public static final androidx.compose.material.BottomSheetState.Companion Companion;
+  }
+
+  public static final class BottomSheetState.Companion {
+    method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.BottomSheetState,?> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange);
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public enum BottomSheetValue {
+    method public static androidx.compose.material.BottomSheetValue valueOf(String name) throws java.lang.IllegalArgumentException;
+    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;
+  }
+
   public final class ButtonConstants {
     method @androidx.compose.runtime.Composable public androidx.compose.animation.core.AnimatedValue<androidx.compose.ui.unit.Dp,androidx.compose.animation.core.AnimationVector1D> defaultAnimatedElevation-qDazgog(androidx.compose.foundation.InteractionState interactionState, boolean enabled, float defaultElevation = 2.dp, float pressedElevation = 8.dp, float disabledElevation = 0.dp);
     method @androidx.compose.runtime.Composable public long defaultButtonBackgroundColor-Q31_wr0(boolean enabled, long defaultColor = MaterialTheme.colors.primary, long disabledColor = defaultDisabledBackgroundColor);
@@ -210,14 +253,16 @@
 
   public final class DrawerConstants {
     method public float getDefaultElevation();
+    method public long getDefaultScrimColor();
     property public final float DefaultElevation;
+    property public final long defaultScrimColor;
     field public static final androidx.compose.material.DrawerConstants INSTANCE;
     field public static final float ScrimDefaultOpacity = 0.32f;
   }
 
   public final class DrawerKt {
-    method @androidx.compose.runtime.Composable public static void BottomDrawerLayout--6CoO6E(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BottomDrawerState drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = MaterialTheme.colors.onSurface.copy(DrawerConstants.ScrimDefaultOpacity), kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
-    method @androidx.compose.runtime.Composable public static void ModalDrawerLayout-TlzqArY(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = MaterialTheme.colors.onSurface.copy(DrawerConstants.ScrimDefaultOpacity), kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+    method @androidx.compose.runtime.Composable public static void BottomDrawerLayout--6CoO6E(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BottomDrawerState drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = DrawerConstants.defaultScrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+    method @androidx.compose.runtime.Composable public static void ModalDrawerLayout-TlzqArY(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = DrawerConstants.defaultScrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
     method @androidx.compose.runtime.Composable public static androidx.compose.material.BottomDrawerState rememberBottomDrawerState(androidx.compose.material.BottomDrawerValue initialValue, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomDrawerValue,java.lang.Boolean> confirmStateChange = { return true });
     method @androidx.compose.runtime.Composable public static androidx.compose.material.DrawerState rememberDrawerState(androidx.compose.material.DrawerValue initialValue, kotlin.jvm.functions.Function1<? super androidx.compose.material.DrawerValue,java.lang.Boolean> confirmStateChange = { return true });
   }
@@ -335,6 +380,40 @@
     method @androidx.compose.runtime.Composable public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.ui.Modifier modifier = Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
+  public final class ModalBottomSheetConstants {
+    method public float getDefaultElevation();
+    method public long getDefaultScrimColor();
+    property public final float DefaultElevation;
+    property public final long DefaultScrimColor;
+    field public static final androidx.compose.material.ModalBottomSheetConstants INSTANCE;
+  }
+
+  public final class ModalBottomSheetKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void ModalBottomSheetLayout-dpadJcU(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.ModalBottomSheetState sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden), androidx.compose.ui.graphics.Shape sheetShape = large, float sheetElevation = ModalBottomSheetConstants.DefaultElevation, long sheetBackgroundColor = MaterialTheme.colors.surface, long sheetContentColor = contentColorFor(sheetBackgroundColor), long scrimColor = ModalBottomSheetConstants.DefaultScrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.ModalBottomSheetState rememberModalBottomSheetState(androidx.compose.material.ModalBottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock = AnimationClockAmbient.current, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmStateChange = { return true });
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class ModalBottomSheetState extends androidx.compose.material.SwipeableState<androidx.compose.material.ModalBottomSheetValue> {
+    ctor public ModalBottomSheetState(androidx.compose.material.ModalBottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmStateChange);
+    method public void hide(kotlin.jvm.functions.Function0<kotlin.Unit>? onHidden = null);
+    method public boolean isVisible();
+    method public void show(kotlin.jvm.functions.Function0<kotlin.Unit>? onShown = null);
+    property public final boolean isVisible;
+    field public static final androidx.compose.material.ModalBottomSheetState.Companion Companion;
+  }
+
+  public static final class ModalBottomSheetState.Companion {
+    method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.ModalBottomSheetState,?> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmStateChange);
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public enum ModalBottomSheetValue {
+    method public static androidx.compose.material.ModalBottomSheetValue valueOf(String name) throws java.lang.IllegalArgumentException;
+    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;
+  }
+
   public final class OutlinedTextFieldKt {
     method @androidx.compose.runtime.Composable public static void OutlinedTextField--KhY4tc(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
     method @androidx.compose.runtime.Composable public static void OutlinedTextField-YV8zA4E(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
@@ -381,7 +460,7 @@
   }
 
   public final class ScaffoldKt {
-    method @androidx.compose.runtime.Composable public static void Scaffold-6SxdeRQ(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.ScaffoldState scaffoldState = rememberScaffoldState(), kotlin.jvm.functions.Function0<kotlin.Unit>? topBar = null, kotlin.jvm.functions.Function0<kotlin.Unit>? bottomBar = null, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = null, androidx.compose.material.FabPosition floatingActionButtonPosition = androidx.compose.material.FabPosition.End, boolean isFloatingActionButtonDocked = false, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent = null, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long drawerScrimColor = MaterialTheme.colors.onSurface.copy(DrawerConstants.ScrimDefaultOpacity), long backgroundColor = MaterialTheme.colors.background, long contentColor = contentColorFor(backgroundColor), kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> bodyContent);
+    method @androidx.compose.runtime.Composable public static void Scaffold-6SxdeRQ(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.ScaffoldState scaffoldState = rememberScaffoldState(), kotlin.jvm.functions.Function0<kotlin.Unit>? topBar = null, kotlin.jvm.functions.Function0<kotlin.Unit>? bottomBar = null, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = null, androidx.compose.material.FabPosition floatingActionButtonPosition = androidx.compose.material.FabPosition.End, boolean isFloatingActionButtonDocked = false, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent = null, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long drawerScrimColor = DrawerConstants.defaultScrimColor, long backgroundColor = MaterialTheme.colors.background, long contentColor = contentColorFor(backgroundColor), kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> bodyContent);
     method @androidx.compose.runtime.Composable public static androidx.compose.material.ScaffoldState rememberScaffoldState(androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState(), boolean isDrawerGesturesEnabled = true);
   }
 
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index 86bc5ab..8bf4929 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -12,7 +12,7 @@
     method @androidx.compose.runtime.Composable public static void TopAppBar-ye6PvEY(androidx.compose.ui.Modifier modifier = Modifier, long backgroundColor = MaterialTheme.colors.primarySurface, long contentColor = contentColorFor(backgroundColor), float elevation = androidx.compose.material.AppBarKt.TopAppBarElevation, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
-  public final class BackdropConstants {
+  public final class BackdropScaffoldConstants {
     method public float getDefaultFrontLayerElevation();
     method public long getDefaultFrontLayerScrimColor();
     method public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
@@ -23,12 +23,12 @@
     property public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
     property public final float DefaultHeaderHeight;
     property public final float DefaultPeekHeight;
-    field public static final androidx.compose.material.BackdropConstants INSTANCE;
+    field public static final androidx.compose.material.BackdropScaffoldConstants INSTANCE;
   }
 
   public final class BackdropScaffoldKt {
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold-n7o2bDw(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BackdropScaffoldState backdropScaffoldState = rememberBackdropState(Concealed), boolean gesturesEnabled = true, float peekHeight = BackdropConstants.DefaultPeekHeight, float headerHeight = BackdropConstants.DefaultHeaderHeight, boolean persistentAppBar = true, boolean stickyFrontLayer = true, long backLayerBackgroundColor = MaterialTheme.colors.primary, long backLayerContentColor = contentColorFor(backLayerBackgroundColor), androidx.compose.ui.graphics.Shape frontLayerShape = BackdropConstants.DefaultFrontLayerShape, float frontLayerElevation = BackdropConstants.DefaultFrontLayerElevation, long frontLayerBackgroundColor = MaterialTheme.colors.surface, long frontLayerContentColor = contentColorFor(frontLayerBackgroundColor), long frontLayerScrimColor = BackdropConstants.DefaultFrontLayerScrimColor, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent);
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange = { return true }, androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold-n7o2bDw(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BackdropScaffoldState scaffoldState = rememberBackdropScaffoldState(Concealed), boolean gesturesEnabled = true, float peekHeight = BackdropScaffoldConstants.DefaultPeekHeight, float headerHeight = BackdropScaffoldConstants.DefaultHeaderHeight, boolean persistentAppBar = true, boolean stickyFrontLayer = true, long backLayerBackgroundColor = MaterialTheme.colors.primary, long backLayerContentColor = contentColorFor(backLayerBackgroundColor), androidx.compose.ui.graphics.Shape frontLayerShape = BackdropScaffoldConstants.DefaultFrontLayerShape, float frontLayerElevation = BackdropScaffoldConstants.DefaultFrontLayerElevation, long frontLayerBackgroundColor = MaterialTheme.colors.surface, long frontLayerContentColor = contentColorFor(frontLayerBackgroundColor), long frontLayerScrimColor = BackdropScaffoldConstants.DefaultFrontLayerScrimColor, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock = AnimationClockAmbient.current, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange = { return true }, androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
   }
 
   @androidx.compose.material.ExperimentalMaterialApi public final class BackdropScaffoldState extends androidx.compose.material.SwipeableState<androidx.compose.material.BackdropValue> {
@@ -87,6 +87,49 @@
 }), long selectedContentColor = contentColor(), long unselectedContentColor = EmphasisAmbient.current.medium.applyEmphasis(selectedContentColor));
   }
 
+  public final class BottomSheetScaffoldConstants {
+    method public float getDefaultSheetElevation();
+    method public float getDefaultSheetPeekHeight();
+    property public final float DefaultSheetElevation;
+    property public final float DefaultSheetPeekHeight;
+    field public static final androidx.compose.material.BottomSheetScaffoldConstants INSTANCE;
+  }
+
+  public final class BottomSheetScaffoldKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BottomSheetScaffold-0Ttp7_s(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BottomSheetScaffoldState scaffoldState = rememberBottomSheetScaffoldState(), kotlin.jvm.functions.Function0<kotlin.Unit>? topBar = null, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = null, androidx.compose.material.FabPosition floatingActionButtonPosition = androidx.compose.material.FabPosition.End, boolean sheetGesturesEnabled = true, androidx.compose.ui.graphics.Shape sheetShape = large, float sheetElevation = BottomSheetScaffoldConstants.DefaultSheetElevation, long sheetBackgroundColor = MaterialTheme.colors.surface, long sheetContentColor = contentColorFor(sheetBackgroundColor), float sheetPeekHeight = BottomSheetScaffoldConstants.DefaultSheetPeekHeight, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent = null, boolean drawerGesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long drawerScrimColor = DrawerConstants.defaultScrimColor, long backgroundColor = MaterialTheme.colors.background, long contentColor = contentColorFor(backgroundColor), kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> bodyContent);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetScaffoldState rememberBottomSheetScaffoldState(androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), androidx.compose.material.BottomSheetState bottomSheetState = rememberBottomSheetState(BottomSheetValue.Collapsed), androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetState rememberBottomSheetState(androidx.compose.material.BottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock = AnimationClockAmbient.current, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange = { return true });
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class BottomSheetScaffoldState {
+    ctor public BottomSheetScaffoldState(androidx.compose.material.DrawerState drawerState, androidx.compose.material.BottomSheetState bottomSheetState, androidx.compose.material.SnackbarHostState snackbarHostState);
+    method public androidx.compose.material.BottomSheetState getBottomSheetState();
+    method public androidx.compose.material.DrawerState getDrawerState();
+    method public androidx.compose.material.SnackbarHostState getSnackbarHostState();
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class BottomSheetState extends androidx.compose.material.SwipeableState<androidx.compose.material.BottomSheetValue> {
+    ctor public BottomSheetState(androidx.compose.material.BottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange);
+    method public void collapse(kotlin.jvm.functions.Function0<kotlin.Unit>? onCollapsed = null);
+    method public void expand(kotlin.jvm.functions.Function0<kotlin.Unit>? onExpanded = null);
+    method public boolean isCollapsed();
+    method public boolean isExpanded();
+    property public final boolean isCollapsed;
+    property public final boolean isExpanded;
+    field public static final androidx.compose.material.BottomSheetState.Companion Companion;
+  }
+
+  public static final class BottomSheetState.Companion {
+    method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.BottomSheetState,?> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange);
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public enum BottomSheetValue {
+    method public static androidx.compose.material.BottomSheetValue valueOf(String name) throws java.lang.IllegalArgumentException;
+    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;
+  }
+
   public final class ButtonConstants {
     method @androidx.compose.runtime.Composable public androidx.compose.animation.core.AnimatedValue<androidx.compose.ui.unit.Dp,androidx.compose.animation.core.AnimationVector1D> defaultAnimatedElevation-qDazgog(androidx.compose.foundation.InteractionState interactionState, boolean enabled, float defaultElevation = 2.dp, float pressedElevation = 8.dp, float disabledElevation = 0.dp);
     method @androidx.compose.runtime.Composable public long defaultButtonBackgroundColor-Q31_wr0(boolean enabled, long defaultColor = MaterialTheme.colors.primary, long disabledColor = defaultDisabledBackgroundColor);
@@ -210,14 +253,16 @@
 
   public final class DrawerConstants {
     method public float getDefaultElevation();
+    method public long getDefaultScrimColor();
     property public final float DefaultElevation;
+    property public final long defaultScrimColor;
     field public static final androidx.compose.material.DrawerConstants INSTANCE;
     field public static final float ScrimDefaultOpacity = 0.32f;
   }
 
   public final class DrawerKt {
-    method @androidx.compose.runtime.Composable public static void BottomDrawerLayout--6CoO6E(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BottomDrawerState drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = MaterialTheme.colors.onSurface.copy(DrawerConstants.ScrimDefaultOpacity), kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
-    method @androidx.compose.runtime.Composable public static void ModalDrawerLayout-TlzqArY(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = MaterialTheme.colors.onSurface.copy(DrawerConstants.ScrimDefaultOpacity), kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+    method @androidx.compose.runtime.Composable public static void BottomDrawerLayout--6CoO6E(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BottomDrawerState drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = DrawerConstants.defaultScrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+    method @androidx.compose.runtime.Composable public static void ModalDrawerLayout-TlzqArY(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = DrawerConstants.defaultScrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
     method @androidx.compose.runtime.Composable public static androidx.compose.material.BottomDrawerState rememberBottomDrawerState(androidx.compose.material.BottomDrawerValue initialValue, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomDrawerValue,java.lang.Boolean> confirmStateChange = { return true });
     method @androidx.compose.runtime.Composable public static androidx.compose.material.DrawerState rememberDrawerState(androidx.compose.material.DrawerValue initialValue, kotlin.jvm.functions.Function1<? super androidx.compose.material.DrawerValue,java.lang.Boolean> confirmStateChange = { return true });
   }
@@ -335,6 +380,40 @@
     method @androidx.compose.runtime.Composable public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.ui.Modifier modifier = Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
+  public final class ModalBottomSheetConstants {
+    method public float getDefaultElevation();
+    method public long getDefaultScrimColor();
+    property public final float DefaultElevation;
+    property public final long DefaultScrimColor;
+    field public static final androidx.compose.material.ModalBottomSheetConstants INSTANCE;
+  }
+
+  public final class ModalBottomSheetKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void ModalBottomSheetLayout-dpadJcU(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.ModalBottomSheetState sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden), androidx.compose.ui.graphics.Shape sheetShape = large, float sheetElevation = ModalBottomSheetConstants.DefaultElevation, long sheetBackgroundColor = MaterialTheme.colors.surface, long sheetContentColor = contentColorFor(sheetBackgroundColor), long scrimColor = ModalBottomSheetConstants.DefaultScrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.ModalBottomSheetState rememberModalBottomSheetState(androidx.compose.material.ModalBottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock = AnimationClockAmbient.current, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmStateChange = { return true });
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class ModalBottomSheetState extends androidx.compose.material.SwipeableState<androidx.compose.material.ModalBottomSheetValue> {
+    ctor public ModalBottomSheetState(androidx.compose.material.ModalBottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmStateChange);
+    method public void hide(kotlin.jvm.functions.Function0<kotlin.Unit>? onHidden = null);
+    method public boolean isVisible();
+    method public void show(kotlin.jvm.functions.Function0<kotlin.Unit>? onShown = null);
+    property public final boolean isVisible;
+    field public static final androidx.compose.material.ModalBottomSheetState.Companion Companion;
+  }
+
+  public static final class ModalBottomSheetState.Companion {
+    method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.ModalBottomSheetState,?> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmStateChange);
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public enum ModalBottomSheetValue {
+    method public static androidx.compose.material.ModalBottomSheetValue valueOf(String name) throws java.lang.IllegalArgumentException;
+    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;
+  }
+
   public final class OutlinedTextFieldKt {
     method @androidx.compose.runtime.Composable public static void OutlinedTextField--KhY4tc(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
     method @androidx.compose.runtime.Composable public static void OutlinedTextField-YV8zA4E(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
@@ -381,7 +460,7 @@
   }
 
   public final class ScaffoldKt {
-    method @androidx.compose.runtime.Composable public static void Scaffold-6SxdeRQ(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.ScaffoldState scaffoldState = rememberScaffoldState(), kotlin.jvm.functions.Function0<kotlin.Unit>? topBar = null, kotlin.jvm.functions.Function0<kotlin.Unit>? bottomBar = null, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = null, androidx.compose.material.FabPosition floatingActionButtonPosition = androidx.compose.material.FabPosition.End, boolean isFloatingActionButtonDocked = false, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent = null, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long drawerScrimColor = MaterialTheme.colors.onSurface.copy(DrawerConstants.ScrimDefaultOpacity), long backgroundColor = MaterialTheme.colors.background, long contentColor = contentColorFor(backgroundColor), kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> bodyContent);
+    method @androidx.compose.runtime.Composable public static void Scaffold-6SxdeRQ(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.ScaffoldState scaffoldState = rememberScaffoldState(), kotlin.jvm.functions.Function0<kotlin.Unit>? topBar = null, kotlin.jvm.functions.Function0<kotlin.Unit>? bottomBar = null, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = null, androidx.compose.material.FabPosition floatingActionButtonPosition = androidx.compose.material.FabPosition.End, boolean isFloatingActionButtonDocked = false, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent = null, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long drawerScrimColor = DrawerConstants.defaultScrimColor, long backgroundColor = MaterialTheme.colors.background, long contentColor = contentColorFor(backgroundColor), kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> bodyContent);
     method @androidx.compose.runtime.Composable public static androidx.compose.material.ScaffoldState rememberScaffoldState(androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState(), boolean isDrawerGesturesEnabled = true);
   }
 
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index 86bc5ab..8bf4929 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -12,7 +12,7 @@
     method @androidx.compose.runtime.Composable public static void TopAppBar-ye6PvEY(androidx.compose.ui.Modifier modifier = Modifier, long backgroundColor = MaterialTheme.colors.primarySurface, long contentColor = contentColorFor(backgroundColor), float elevation = androidx.compose.material.AppBarKt.TopAppBarElevation, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
-  public final class BackdropConstants {
+  public final class BackdropScaffoldConstants {
     method public float getDefaultFrontLayerElevation();
     method public long getDefaultFrontLayerScrimColor();
     method public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
@@ -23,12 +23,12 @@
     property public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
     property public final float DefaultHeaderHeight;
     property public final float DefaultPeekHeight;
-    field public static final androidx.compose.material.BackdropConstants INSTANCE;
+    field public static final androidx.compose.material.BackdropScaffoldConstants INSTANCE;
   }
 
   public final class BackdropScaffoldKt {
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold-n7o2bDw(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BackdropScaffoldState backdropScaffoldState = rememberBackdropState(Concealed), boolean gesturesEnabled = true, float peekHeight = BackdropConstants.DefaultPeekHeight, float headerHeight = BackdropConstants.DefaultHeaderHeight, boolean persistentAppBar = true, boolean stickyFrontLayer = true, long backLayerBackgroundColor = MaterialTheme.colors.primary, long backLayerContentColor = contentColorFor(backLayerBackgroundColor), androidx.compose.ui.graphics.Shape frontLayerShape = BackdropConstants.DefaultFrontLayerShape, float frontLayerElevation = BackdropConstants.DefaultFrontLayerElevation, long frontLayerBackgroundColor = MaterialTheme.colors.surface, long frontLayerContentColor = contentColorFor(frontLayerBackgroundColor), long frontLayerScrimColor = BackdropConstants.DefaultFrontLayerScrimColor, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent);
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange = { return true }, androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold-n7o2bDw(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BackdropScaffoldState scaffoldState = rememberBackdropScaffoldState(Concealed), boolean gesturesEnabled = true, float peekHeight = BackdropScaffoldConstants.DefaultPeekHeight, float headerHeight = BackdropScaffoldConstants.DefaultHeaderHeight, boolean persistentAppBar = true, boolean stickyFrontLayer = true, long backLayerBackgroundColor = MaterialTheme.colors.primary, long backLayerContentColor = contentColorFor(backLayerBackgroundColor), androidx.compose.ui.graphics.Shape frontLayerShape = BackdropScaffoldConstants.DefaultFrontLayerShape, float frontLayerElevation = BackdropScaffoldConstants.DefaultFrontLayerElevation, long frontLayerBackgroundColor = MaterialTheme.colors.surface, long frontLayerContentColor = contentColorFor(frontLayerBackgroundColor), long frontLayerScrimColor = BackdropScaffoldConstants.DefaultFrontLayerScrimColor, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock = AnimationClockAmbient.current, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange = { return true }, androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
   }
 
   @androidx.compose.material.ExperimentalMaterialApi public final class BackdropScaffoldState extends androidx.compose.material.SwipeableState<androidx.compose.material.BackdropValue> {
@@ -87,6 +87,49 @@
 }), long selectedContentColor = contentColor(), long unselectedContentColor = EmphasisAmbient.current.medium.applyEmphasis(selectedContentColor));
   }
 
+  public final class BottomSheetScaffoldConstants {
+    method public float getDefaultSheetElevation();
+    method public float getDefaultSheetPeekHeight();
+    property public final float DefaultSheetElevation;
+    property public final float DefaultSheetPeekHeight;
+    field public static final androidx.compose.material.BottomSheetScaffoldConstants INSTANCE;
+  }
+
+  public final class BottomSheetScaffoldKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BottomSheetScaffold-0Ttp7_s(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BottomSheetScaffoldState scaffoldState = rememberBottomSheetScaffoldState(), kotlin.jvm.functions.Function0<kotlin.Unit>? topBar = null, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = null, androidx.compose.material.FabPosition floatingActionButtonPosition = androidx.compose.material.FabPosition.End, boolean sheetGesturesEnabled = true, androidx.compose.ui.graphics.Shape sheetShape = large, float sheetElevation = BottomSheetScaffoldConstants.DefaultSheetElevation, long sheetBackgroundColor = MaterialTheme.colors.surface, long sheetContentColor = contentColorFor(sheetBackgroundColor), float sheetPeekHeight = BottomSheetScaffoldConstants.DefaultSheetPeekHeight, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent = null, boolean drawerGesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long drawerScrimColor = DrawerConstants.defaultScrimColor, long backgroundColor = MaterialTheme.colors.background, long contentColor = contentColorFor(backgroundColor), kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> bodyContent);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetScaffoldState rememberBottomSheetScaffoldState(androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), androidx.compose.material.BottomSheetState bottomSheetState = rememberBottomSheetState(BottomSheetValue.Collapsed), androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetState rememberBottomSheetState(androidx.compose.material.BottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock = AnimationClockAmbient.current, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange = { return true });
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class BottomSheetScaffoldState {
+    ctor public BottomSheetScaffoldState(androidx.compose.material.DrawerState drawerState, androidx.compose.material.BottomSheetState bottomSheetState, androidx.compose.material.SnackbarHostState snackbarHostState);
+    method public androidx.compose.material.BottomSheetState getBottomSheetState();
+    method public androidx.compose.material.DrawerState getDrawerState();
+    method public androidx.compose.material.SnackbarHostState getSnackbarHostState();
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class BottomSheetState extends androidx.compose.material.SwipeableState<androidx.compose.material.BottomSheetValue> {
+    ctor public BottomSheetState(androidx.compose.material.BottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange);
+    method public void collapse(kotlin.jvm.functions.Function0<kotlin.Unit>? onCollapsed = null);
+    method public void expand(kotlin.jvm.functions.Function0<kotlin.Unit>? onExpanded = null);
+    method public boolean isCollapsed();
+    method public boolean isExpanded();
+    property public final boolean isCollapsed;
+    property public final boolean isExpanded;
+    field public static final androidx.compose.material.BottomSheetState.Companion Companion;
+  }
+
+  public static final class BottomSheetState.Companion {
+    method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.BottomSheetState,?> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange);
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public enum BottomSheetValue {
+    method public static androidx.compose.material.BottomSheetValue valueOf(String name) throws java.lang.IllegalArgumentException;
+    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;
+  }
+
   public final class ButtonConstants {
     method @androidx.compose.runtime.Composable public androidx.compose.animation.core.AnimatedValue<androidx.compose.ui.unit.Dp,androidx.compose.animation.core.AnimationVector1D> defaultAnimatedElevation-qDazgog(androidx.compose.foundation.InteractionState interactionState, boolean enabled, float defaultElevation = 2.dp, float pressedElevation = 8.dp, float disabledElevation = 0.dp);
     method @androidx.compose.runtime.Composable public long defaultButtonBackgroundColor-Q31_wr0(boolean enabled, long defaultColor = MaterialTheme.colors.primary, long disabledColor = defaultDisabledBackgroundColor);
@@ -210,14 +253,16 @@
 
   public final class DrawerConstants {
     method public float getDefaultElevation();
+    method public long getDefaultScrimColor();
     property public final float DefaultElevation;
+    property public final long defaultScrimColor;
     field public static final androidx.compose.material.DrawerConstants INSTANCE;
     field public static final float ScrimDefaultOpacity = 0.32f;
   }
 
   public final class DrawerKt {
-    method @androidx.compose.runtime.Composable public static void BottomDrawerLayout--6CoO6E(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BottomDrawerState drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = MaterialTheme.colors.onSurface.copy(DrawerConstants.ScrimDefaultOpacity), kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
-    method @androidx.compose.runtime.Composable public static void ModalDrawerLayout-TlzqArY(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = MaterialTheme.colors.onSurface.copy(DrawerConstants.ScrimDefaultOpacity), kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+    method @androidx.compose.runtime.Composable public static void BottomDrawerLayout--6CoO6E(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BottomDrawerState drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = DrawerConstants.defaultScrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+    method @androidx.compose.runtime.Composable public static void ModalDrawerLayout-TlzqArY(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), boolean gesturesEnabled = true, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long scrimColor = DrawerConstants.defaultScrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
     method @androidx.compose.runtime.Composable public static androidx.compose.material.BottomDrawerState rememberBottomDrawerState(androidx.compose.material.BottomDrawerValue initialValue, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomDrawerValue,java.lang.Boolean> confirmStateChange = { return true });
     method @androidx.compose.runtime.Composable public static androidx.compose.material.DrawerState rememberDrawerState(androidx.compose.material.DrawerValue initialValue, kotlin.jvm.functions.Function1<? super androidx.compose.material.DrawerValue,java.lang.Boolean> confirmStateChange = { return true });
   }
@@ -335,6 +380,40 @@
     method @androidx.compose.runtime.Composable public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.ui.Modifier modifier = Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
+  public final class ModalBottomSheetConstants {
+    method public float getDefaultElevation();
+    method public long getDefaultScrimColor();
+    property public final float DefaultElevation;
+    property public final long DefaultScrimColor;
+    field public static final androidx.compose.material.ModalBottomSheetConstants INSTANCE;
+  }
+
+  public final class ModalBottomSheetKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void ModalBottomSheetLayout-dpadJcU(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.ModalBottomSheetState sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden), androidx.compose.ui.graphics.Shape sheetShape = large, float sheetElevation = ModalBottomSheetConstants.DefaultElevation, long sheetBackgroundColor = MaterialTheme.colors.surface, long sheetContentColor = contentColorFor(sheetBackgroundColor), long scrimColor = ModalBottomSheetConstants.DefaultScrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.ModalBottomSheetState rememberModalBottomSheetState(androidx.compose.material.ModalBottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock = AnimationClockAmbient.current, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmStateChange = { return true });
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class ModalBottomSheetState extends androidx.compose.material.SwipeableState<androidx.compose.material.ModalBottomSheetValue> {
+    ctor public ModalBottomSheetState(androidx.compose.material.ModalBottomSheetValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmStateChange);
+    method public void hide(kotlin.jvm.functions.Function0<kotlin.Unit>? onHidden = null);
+    method public boolean isVisible();
+    method public void show(kotlin.jvm.functions.Function0<kotlin.Unit>? onShown = null);
+    property public final boolean isVisible;
+    field public static final androidx.compose.material.ModalBottomSheetState.Companion Companion;
+  }
+
+  public static final class ModalBottomSheetState.Companion {
+    method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.ModalBottomSheetState,?> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmStateChange);
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public enum ModalBottomSheetValue {
+    method public static androidx.compose.material.ModalBottomSheetValue valueOf(String name) throws java.lang.IllegalArgumentException;
+    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;
+  }
+
   public final class OutlinedTextFieldKt {
     method @androidx.compose.runtime.Composable public static void OutlinedTextField--KhY4tc(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
     method @androidx.compose.runtime.Composable public static void OutlinedTextField-YV8zA4E(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
@@ -381,7 +460,7 @@
   }
 
   public final class ScaffoldKt {
-    method @androidx.compose.runtime.Composable public static void Scaffold-6SxdeRQ(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.ScaffoldState scaffoldState = rememberScaffoldState(), kotlin.jvm.functions.Function0<kotlin.Unit>? topBar = null, kotlin.jvm.functions.Function0<kotlin.Unit>? bottomBar = null, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = null, androidx.compose.material.FabPosition floatingActionButtonPosition = androidx.compose.material.FabPosition.End, boolean isFloatingActionButtonDocked = false, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent = null, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long drawerScrimColor = MaterialTheme.colors.onSurface.copy(DrawerConstants.ScrimDefaultOpacity), long backgroundColor = MaterialTheme.colors.background, long contentColor = contentColorFor(backgroundColor), kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> bodyContent);
+    method @androidx.compose.runtime.Composable public static void Scaffold-6SxdeRQ(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.ScaffoldState scaffoldState = rememberScaffoldState(), kotlin.jvm.functions.Function0<kotlin.Unit>? topBar = null, kotlin.jvm.functions.Function0<kotlin.Unit>? bottomBar = null, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = null, androidx.compose.material.FabPosition floatingActionButtonPosition = androidx.compose.material.FabPosition.End, boolean isFloatingActionButtonDocked = false, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent = null, androidx.compose.ui.graphics.Shape drawerShape = large, float drawerElevation = DrawerConstants.DefaultElevation, long drawerBackgroundColor = MaterialTheme.colors.surface, long drawerContentColor = contentColorFor(drawerBackgroundColor), long drawerScrimColor = DrawerConstants.defaultScrimColor, long backgroundColor = MaterialTheme.colors.background, long contentColor = contentColorFor(backgroundColor), kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> bodyContent);
     method @androidx.compose.runtime.Composable public static androidx.compose.material.ScaffoldState rememberScaffoldState(androidx.compose.material.DrawerState drawerState = rememberDrawerState(DrawerValue.Closed), androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState(), boolean isDrawerGesturesEnabled = true);
   }
 
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 6bcaecd..6bf3ef2 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
@@ -20,11 +20,13 @@
 import androidx.compose.integration.demos.common.ComposableDemo
 import androidx.compose.integration.demos.common.DemoCategory
 import androidx.compose.material.samples.AlertDialogSample
-import androidx.compose.material.samples.BackdropSample
+import androidx.compose.material.samples.BackdropScaffoldSample
 import androidx.compose.material.samples.BottomDrawerSample
 import androidx.compose.material.samples.CustomAlertDialogSample
 import androidx.compose.material.samples.EmphasisSample
+import androidx.compose.material.samples.ModalBottomSheetSample
 import androidx.compose.material.samples.ModalDrawerSample
+import androidx.compose.material.samples.BottomSheetScaffoldSample
 import androidx.compose.material.samples.ScaffoldWithBottomBarAndCutout
 import androidx.compose.material.samples.ScaffoldWithCoroutinesSnackbar
 
@@ -34,8 +36,9 @@
         ComposableDemo("Custom buttons") { CustomAlertDialogSample() }
     )),
     ComposableDemo("App Bars") { AppBarDemo() },
-    ComposableDemo("Backdrop") { BackdropSample() },
+    ComposableDemo("Backdrop") { BackdropScaffoldSample() },
     ComposableDemo("Bottom Navigation") { BottomNavigationDemo() },
+    ComposableDemo("Bottom Sheet") { BottomSheetScaffoldSample() },
     ComposableDemo("Buttons & FABs") { ButtonDemo() },
     DemoCategory("Navigation drawer", listOf(
         ComposableDemo("Modal drawer") { ModalDrawerSample() },
@@ -55,6 +58,7 @@
             ActivityDemo("Dynamic Theme", DynamicThemeActivity::class)
         )
     ),
+    ComposableDemo("Modal bottom sheet") { ModalBottomSheetSample() },
     ComposableDemo("Progress Indicators") { ProgressIndicatorDemo() },
     ComposableDemo("Scaffold") { ScaffoldWithBottomBarAndCutout() },
     ComposableDemo("Selection Controls") { SelectionControlsDemo() },
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
index 6e4dc36..28f0b72 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
@@ -21,7 +21,7 @@
 import androidx.compose.foundation.ScrollableColumn
 import androidx.compose.foundation.Text
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope.align
+import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
@@ -233,7 +233,7 @@
 }
 
 @Composable
-private fun Title(title: String) {
+private fun ColumnScope.Title(title: String) {
     Text(
         text = title,
         style = MaterialTheme.typography.body1,
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
index 333cfc84..098fd3d 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
@@ -25,7 +25,7 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.lazy.LazyColumnFor
 import androidx.compose.material.BackdropScaffold
-import androidx.compose.material.BackdropValue.Concealed
+import androidx.compose.material.BackdropValue
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.IconButton
 import androidx.compose.material.ListItem
@@ -34,7 +34,7 @@
 import androidx.compose.material.icons.filled.Close
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.icons.filled.Menu
-import androidx.compose.material.rememberBackdropState
+import androidx.compose.material.rememberBackdropScaffoldState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -49,70 +49,22 @@
 @Sampled
 @Composable
 @OptIn(ExperimentalMaterialApi::class)
-fun BackdropSample() {
-    val selection = remember { mutableStateOf(1) }
-    val backdropScaffoldState = rememberBackdropState(Concealed)
-    BackdropScaffold(
-        backdropScaffoldState = backdropScaffoldState,
-        appBar = {
-            TopAppBar(
-                title = { Text("Backdrop") },
-                navigationIcon = {
-                    if (backdropScaffoldState.isConcealed) {
-                        IconButton(onClick = { backdropScaffoldState.reveal() }) {
-                            Icon(Icons.Default.Menu)
-                        }
-                    } else {
-                        IconButton(onClick = { backdropScaffoldState.conceal() }) {
-                            Icon(Icons.Default.Close)
-                        }
-                    }
-                },
-                elevation = 0.dp,
-                backgroundColor = Color.Transparent
-            )
-        },
-        backLayerContent = {
-            LazyColumnFor((1..5).toList()) {
-                ListItem(
-                    Modifier.clickable {
-                        selection.value = it
-                        backdropScaffoldState.conceal()
-                    },
-                    text = { Text("Select $it") }
-                )
-            }
-        },
-        frontLayerContent = {
-            Box(
-                Modifier.fillMaxSize(),
-                gravity = ContentGravity.Center
-            ) {
-                Text("Selection: ${selection.value}")
-            }
-        }
-    )
-}
-
-@Sampled
-@Composable
-@OptIn(ExperimentalMaterialApi::class)
-fun BackdropWithSnackbarSample() {
+fun BackdropScaffoldSample() {
     val scope = rememberCoroutineScope()
     val selection = remember { mutableStateOf(1) }
-    val backdropScaffoldState = rememberBackdropState(Concealed)
+    val scaffoldState = rememberBackdropScaffoldState(BackdropValue.Concealed)
     BackdropScaffold(
-        backdropScaffoldState = backdropScaffoldState,
+        scaffoldState = scaffoldState,
         appBar = {
             TopAppBar(
-                title = { Text("Backdrop") },
+                title = { Text("Backdrop scaffold") },
                 navigationIcon = {
-                    if (backdropScaffoldState.isConcealed) {
-                        IconButton(onClick = { backdropScaffoldState.reveal() }) {
+                    if (scaffoldState.isConcealed) {
+                        IconButton(onClick = { scaffoldState.reveal() }) {
                             Icon(Icons.Default.Menu)
                         }
                     } else {
-                        IconButton(onClick = { backdropScaffoldState.conceal() }) {
+                        IconButton(onClick = { scaffoldState.conceal() }) {
                             Icon(Icons.Default.Close)
                         }
                     }
@@ -122,7 +74,7 @@
                     IconButton(onClick = {
                         // show snackbar as a suspend function
                         scope.launch {
-                            backdropScaffoldState.snackbarHostState
+                            scaffoldState.snackbarHostState
                                 .showSnackbar("Snackbar #${++clickCount}")
                         }
                     }) {
@@ -138,7 +90,7 @@
                 ListItem(
                     Modifier.clickable {
                         selection.value = it
-                        backdropScaffoldState.conceal()
+                        scaffoldState.conceal()
                     },
                     text = { Text("Select $it") }
                 )
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt
new file mode 100644
index 0000000..8f5a7b8
--- /dev/null
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.Box
+import androidx.compose.foundation.ContentGravity
+import androidx.compose.foundation.Icon
+import androidx.compose.foundation.ScrollableColumn
+import androidx.compose.foundation.Text
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.preferredHeight
+import androidx.compose.material.BottomSheetScaffold
+import androidx.compose.material.Button
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.FabPosition
+import androidx.compose.material.FloatingActionButton
+import androidx.compose.material.IconButton
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.material.rememberBottomSheetScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
+
+private val colors = listOf(
+    Color(0xFFffd7d7.toInt()),
+    Color(0xFFffe9d6.toInt()),
+    Color(0xFFfffbd0.toInt()),
+    Color(0xFFe3ffd9.toInt()),
+    Color(0xFFd0fff8.toInt())
+)
+
+@Sampled
+@Composable
+@OptIn(ExperimentalMaterialApi::class)
+fun BottomSheetScaffoldSample() {
+    val scope = rememberCoroutineScope()
+    val scaffoldState = rememberBottomSheetScaffoldState()
+    BottomSheetScaffold(
+        sheetContent = {
+            Box(
+                Modifier.fillMaxWidth().preferredHeight(128.dp),
+                gravity = ContentGravity.Center
+            ) {
+                Text("Swipe up to expand sheet")
+            }
+            Column(
+                Modifier.fillMaxWidth().padding(64.dp),
+                horizontalAlignment = Alignment.CenterHorizontally
+            ) {
+                Text("Sheet content")
+                Spacer(Modifier.preferredHeight(20.dp))
+                Button(onClick = { scaffoldState.bottomSheetState.collapse() }) {
+                    Text("Click to collapse sheet")
+                }
+            }
+        },
+        scaffoldState = scaffoldState,
+        topBar = {
+            TopAppBar(
+                title = { Text("Bottom sheet scaffold") },
+                navigationIcon = {
+                    IconButton(onClick = { scaffoldState.drawerState.open() }) {
+                        Icon(Icons.Default.Menu)
+                    }
+                }
+            )
+        },
+        floatingActionButton = {
+            var clickCount by remember { mutableStateOf(0) }
+            FloatingActionButton(onClick = {
+                // show snackbar as a suspend function
+                scope.launch {
+                    scaffoldState.snackbarHostState.showSnackbar("Snackbar #${++clickCount}")
+                }
+            }) {
+                Icon(Icons.Default.Favorite)
+            }
+        },
+        floatingActionButtonPosition = FabPosition.End,
+        sheetPeekHeight = 128.dp,
+        drawerContent = {
+            Column(
+                Modifier.fillMaxWidth().padding(16.dp),
+                horizontalAlignment = Alignment.CenterHorizontally
+            ) {
+                Text("Drawer content")
+                Spacer(Modifier.preferredHeight(20.dp))
+                Button(onClick = { scaffoldState.drawerState.close() }) {
+                    Text("Click to close drawer")
+                }
+            }
+        }
+    ) { innerPadding ->
+        ScrollableColumn(contentPadding = innerPadding) {
+            repeat(100) {
+                Box(
+                    Modifier
+                        .fillMaxWidth()
+                        .preferredHeight(50.dp)
+                        .background(colors[it % colors.size])
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ModalBottomSheetSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ModalBottomSheetSamples.kt
new file mode 100644
index 0000000..144cf53
--- /dev/null
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ModalBottomSheetSamples.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.Icon
+import androidx.compose.foundation.Text
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.preferredHeight
+import androidx.compose.material.Button
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.ListItem
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Sampled
+@Composable
+@OptIn(ExperimentalMaterialApi::class)
+fun ModalBottomSheetSample() {
+    val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
+    ModalBottomSheetLayout(
+        sheetState = state,
+        sheetContent = {
+            for (i in 1..5) {
+                ListItem(
+                    text = { Text("Item $i") },
+                    icon = { Icon(Icons.Default.Favorite) }
+                )
+            }
+        }
+    ) {
+        Column(
+            modifier = Modifier.fillMaxSize().padding(16.dp),
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Text("Rest of the UI")
+            Spacer(Modifier.preferredHeight(20.dp))
+            Button(onClick = { state.show() }) {
+                Text("Click to show sheet")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
index 1f81634..48cfeb0 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
@@ -71,7 +71,7 @@
     fun backdropScaffold_testOffset_whenConcealed() {
         rule.setContent {
             BackdropScaffold(
-                backdropScaffoldState = rememberBackdropState(Concealed),
+                scaffoldState = rememberBackdropScaffoldState(Concealed),
                 peekHeight = peekHeight,
                 headerHeight = headerHeight,
                 appBar = { Box(Modifier.preferredHeight(peekHeight)) },
@@ -88,7 +88,7 @@
     fun backdropScaffold_testOffset_whenRevealed() {
         rule.setContent {
             BackdropScaffold(
-                backdropScaffoldState = rememberBackdropState(Revealed),
+                scaffoldState = rememberBackdropScaffoldState(Revealed),
                 peekHeight = peekHeight,
                 headerHeight = headerHeight,
                 appBar = { Box(Modifier.preferredHeight(peekHeight)) },
@@ -105,7 +105,7 @@
     fun backdropScaffold_testOffset_whenRevealed_backContentTooLarge() {
         rule.setContent {
             BackdropScaffold(
-                backdropScaffoldState = rememberBackdropState(Revealed),
+                scaffoldState = rememberBackdropScaffoldState(Revealed),
                 peekHeight = peekHeight,
                 headerHeight = headerHeight,
                 appBar = { Box(Modifier.preferredHeight(peekHeight)) },
@@ -122,7 +122,7 @@
     fun backdropScaffold_testOffset_whenRevealed_nonPersistentAppBar() {
         rule.setContent {
             BackdropScaffold(
-                backdropScaffoldState = rememberBackdropState(Revealed),
+                scaffoldState = rememberBackdropScaffoldState(Revealed),
                 peekHeight = peekHeight,
                 headerHeight = headerHeight,
                 persistentAppBar = false,
@@ -140,7 +140,7 @@
     fun backdropScaffold_testOffset_whenRevealed_nonStickyFrontLayer() {
         rule.setContent {
             BackdropScaffold(
-                backdropScaffoldState = rememberBackdropState(Revealed),
+                scaffoldState = rememberBackdropScaffoldState(Revealed),
                 peekHeight = peekHeight,
                 headerHeight = headerHeight,
                 stickyFrontLayer = false,
@@ -156,10 +156,10 @@
 
     @Test
     fun backdropScaffold_revealAndConceal_manually() {
-        val backdropState = BackdropScaffoldState(Concealed, clock = clock)
+        val scaffoldState = BackdropScaffoldState(Concealed, clock = clock)
         rule.setContent {
             BackdropScaffold(
-                backdropScaffoldState = backdropState,
+                scaffoldState = scaffoldState,
                 peekHeight = peekHeight,
                 headerHeight = headerHeight,
                 appBar = { Box(Modifier.preferredHeight(peekHeight)) },
@@ -172,7 +172,7 @@
             .assertTopPositionInRootIsEqualTo(peekHeight)
 
         rule.runOnIdle {
-            backdropState.reveal()
+            scaffoldState.reveal()
         }
 
         advanceClock()
@@ -181,7 +181,7 @@
             .assertTopPositionInRootIsEqualTo(peekHeight + contentHeight)
 
         rule.runOnIdle {
-            backdropState.conceal()
+            scaffoldState.conceal()
         }
 
         advanceClock()
@@ -192,10 +192,10 @@
 
     @Test
     fun backdropScaffold_revealBySwiping() {
-        val backdropState = BackdropScaffoldState(Concealed, clock)
+        val scaffoldState = BackdropScaffoldState(Concealed, clock)
         rule.setContent {
             BackdropScaffold(
-                backdropScaffoldState = backdropState,
+                scaffoldState = scaffoldState,
                 peekHeight = peekHeight,
                 headerHeight = headerHeight,
                 appBar = { Box(Modifier.preferredHeight(peekHeight)) },
@@ -205,7 +205,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(backdropState.value).isEqualTo(Concealed)
+            assertThat(scaffoldState.value).isEqualTo(Concealed)
         }
 
         rule.onNodeWithTag(frontLayer)
@@ -214,16 +214,16 @@
         advanceClock()
 
         rule.runOnIdle {
-            assertThat(backdropState.value).isEqualTo(Revealed)
+            assertThat(scaffoldState.value).isEqualTo(Revealed)
         }
     }
 
     @Test
     fun backdropScaffold_concealByTapingOnFrontLayer() {
-        val backdropState = BackdropScaffoldState(Revealed, clock)
+        val scaffoldState = BackdropScaffoldState(Revealed, clock)
         rule.setContent {
             BackdropScaffold(
-                backdropScaffoldState = backdropState,
+                scaffoldState = scaffoldState,
                 peekHeight = peekHeight,
                 headerHeight = headerHeight,
                 frontLayerScrimColor = Color.Red,
@@ -234,7 +234,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(backdropState.value).isEqualTo(Revealed)
+            assertThat(scaffoldState.value).isEqualTo(Revealed)
         }
 
         rule.onNodeWithTag(frontLayer)
@@ -243,17 +243,17 @@
         advanceClock()
 
         rule.runOnIdle {
-            assertThat(backdropState.value).isEqualTo(Concealed)
+            assertThat(scaffoldState.value).isEqualTo(Concealed)
         }
     }
 
     @Test
     fun backdropScaffold_scrimIsDisabledWhenTransparent() {
         var frontLayerClicks = 0
-        val backdropState = BackdropScaffoldState(Revealed, clock)
+        val scaffoldState = BackdropScaffoldState(Revealed, clock)
         rule.setContent {
             BackdropScaffold(
-                backdropScaffoldState = backdropState,
+                scaffoldState = scaffoldState,
                 peekHeight = peekHeight,
                 headerHeight = headerHeight,
                 frontLayerScrimColor = Color.Transparent,
@@ -269,7 +269,7 @@
 
         rule.runOnIdle {
             assertThat(frontLayerClicks).isEqualTo(0)
-            assertThat(backdropState.value).isEqualTo(Revealed)
+            assertThat(scaffoldState.value).isEqualTo(Revealed)
         }
 
         rule.onNodeWithTag(frontLayer)
@@ -279,7 +279,7 @@
 
         rule.runOnIdle {
             assertThat(frontLayerClicks).isEqualTo(1)
-            assertThat(backdropState.value).isEqualTo(Revealed)
+            assertThat(scaffoldState.value).isEqualTo(Revealed)
         }
     }
 }
\ No newline at end of file
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
new file mode 100644
index 0000000..2e559fa
--- /dev/null
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.animation.core.ManualAnimationClock
+import androidx.compose.foundation.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.preferredHeight
+import androidx.compose.runtime.emptyContent
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
+import androidx.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.ui.test.createComposeRule
+import androidx.ui.test.onNodeWithTag
+import androidx.ui.test.performGesture
+import androidx.ui.test.swipeDown
+import androidx.ui.test.swipeUp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@MediumTest
+@RunWith(JUnit4::class)
+@OptIn(ExperimentalMaterialApi::class)
+class ModalBottomSheetTest {
+
+    @get:Rule
+    val rule = createComposeRule(disableTransitions = true)
+
+    private val sheetHeight = 256.dp
+    private val sheetTag = "sheetContentTag"
+
+    private lateinit var clock: ManualAnimationClock
+
+    private fun advanceClock() {
+        clock.clockTimeMillis += 100000L
+    }
+
+    @Before
+    fun init() {
+        clock = ManualAnimationClock(initTimeMillis = 0L)
+    }
+
+    @Test
+    fun modalBottomSheet_testOffset_whenHidden() {
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden),
+                content = emptyContent(),
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .fillMaxWidth()
+                            .preferredHeight(sheetHeight)
+                            .testTag(sheetTag)
+                    )
+                }
+            )
+        }
+
+        val height = rule.rootHeight()
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(height)
+    }
+
+    @Test
+    fun modalBottomSheet_testOffset_whenExpanded() {
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Expanded),
+                content = emptyContent(),
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .fillMaxWidth()
+                            .preferredHeight(sheetHeight)
+                            .testTag(sheetTag)
+                    )
+                }
+            )
+        }
+
+        val height = rule.rootHeight()
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(height - sheetHeight)
+    }
+
+    @Test
+    fun modalBottomSheet_testOffset_tallBottomSheet_whenHidden() {
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden),
+                content = emptyContent(),
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .fillMaxSize()
+                            .testTag(sheetTag)
+                    )
+                }
+            )
+        }
+
+        val height = rule.rootHeight()
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(height)
+    }
+
+    @Test
+    fun modalBottomSheet_testOffset_tallBottomSheet_whenExpanded() {
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Expanded),
+                content = emptyContent(),
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .fillMaxSize()
+                            .testTag(sheetTag)
+                    )
+                }
+            )
+        }
+
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(0.dp)
+    }
+
+    @Test
+    fun modalBottomSheet_testOffset_tallBottomSheet_whenHalfExpanded() {
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.HalfExpanded),
+                content = emptyContent(),
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .fillMaxSize()
+                            .testTag(sheetTag)
+                    )
+                }
+            )
+        }
+
+        val height = rule.rootHeight()
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(height / 2)
+    }
+
+    @Test
+    fun modalBottomSheet_showAndHide_manually() {
+        val sheetState = ModalBottomSheetState(ModalBottomSheetValue.Hidden, clock)
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = sheetState,
+                content = emptyContent(),
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .fillMaxWidth()
+                            .preferredHeight(sheetHeight)
+                            .testTag(sheetTag)
+                    )
+                }
+            )
+        }
+
+        val height = rule.rootHeight()
+
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(height)
+
+        rule.runOnIdle {
+            sheetState.show()
+        }
+
+        advanceClock()
+
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(height - sheetHeight)
+
+        rule.runOnIdle {
+            sheetState.hide()
+        }
+
+        advanceClock()
+
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(height)
+    }
+
+    @Test
+    fun modalBottomSheet_showAndHide_manually_tallBottomSheet() {
+        val sheetState = ModalBottomSheetState(ModalBottomSheetValue.Hidden, clock)
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = sheetState,
+                content = emptyContent(),
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .fillMaxSize()
+                            .testTag(sheetTag)
+                    )
+                }
+            )
+        }
+
+        val height = rule.rootHeight()
+
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(height)
+
+        rule.runOnIdle {
+            sheetState.show()
+        }
+
+        advanceClock()
+
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(height / 2)
+
+        rule.runOnIdle {
+            sheetState.hide()
+        }
+
+        advanceClock()
+
+        rule.onNodeWithTag(sheetTag)
+            .assertTopPositionInRootIsEqualTo(height)
+    }
+
+    @Test
+    fun modalBottomSheet_hideBySwiping() {
+        val sheetState = ModalBottomSheetState(ModalBottomSheetValue.Expanded, clock)
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = sheetState,
+                content = emptyContent(),
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .fillMaxWidth()
+                            .preferredHeight(sheetHeight)
+                            .testTag(sheetTag)
+                    )
+                }
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(sheetState.value).isEqualTo(ModalBottomSheetValue.Expanded)
+        }
+
+        rule.onNodeWithTag(sheetTag)
+            .performGesture { swipeDown() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(sheetState.value).isEqualTo(ModalBottomSheetValue.Hidden)
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_hideBySwiping_tallBottomSheet() {
+        val sheetState = ModalBottomSheetState(ModalBottomSheetValue.Expanded, clock)
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = sheetState,
+                content = emptyContent(),
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .fillMaxSize()
+                            .testTag(sheetTag)
+                    )
+                }
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(sheetState.value).isEqualTo(ModalBottomSheetValue.Expanded)
+        }
+
+        rule.onNodeWithTag(sheetTag)
+            .performGesture { swipeDown() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(sheetState.value).isEqualTo(ModalBottomSheetValue.Hidden)
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_expandBySwiping() {
+        val sheetState = ModalBottomSheetState(ModalBottomSheetValue.HalfExpanded, clock)
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = sheetState,
+                content = emptyContent(),
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .fillMaxSize()
+                            .testTag(sheetTag)
+                    )
+                }
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(sheetState.value).isEqualTo(ModalBottomSheetValue.HalfExpanded)
+        }
+
+        rule.onNodeWithTag(sheetTag)
+            .performGesture { swipeUp() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(sheetState.value).isEqualTo(ModalBottomSheetValue.Expanded)
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
index e8deecf..9290b83 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
@@ -80,7 +80,7 @@
  * @param clock The animation clock that will be used to drive the animations.
  * @param animationSpec The default animation that will be used to animate to a new state.
  * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
- * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the backdrop.
+ * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the scaffold.
  */
 @ExperimentalMaterialApi
 class BackdropScaffoldState(
@@ -161,23 +161,25 @@
  * Create and [remember] a [BackdropScaffoldState] with the default animation clock.
  *
  * @param initialValue The initial value of the state.
+ * @param clock The animation clock that will be used to drive the animations.
  * @param animationSpec The default animation that will be used to animate to a new state.
  * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
- * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the backdrop.
+ * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the scaffold.
  */
 @Composable
 @ExperimentalMaterialApi
-fun rememberBackdropState(
+fun rememberBackdropScaffoldState(
     initialValue: BackdropValue,
+    clock: AnimationClockObservable = AnimationClockAmbient.current,
     animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
     confirmStateChange: (BackdropValue) -> Boolean = { true },
     snackbarHostState: SnackbarHostState = SnackbarHostState()
 ): BackdropScaffoldState {
-    val clock = AnimationClockAmbient.current.asDisposableClock()
+    val disposableClock = clock.asDisposableClock()
     return rememberSavedInstanceState(
-        clock,
+        disposableClock,
         saver = BackdropScaffoldState.Saver(
-            clock = clock,
+            clock = disposableClock,
             animationSpec = animationSpec,
             confirmStateChange = confirmStateChange,
             snackbarHostState = snackbarHostState
@@ -185,7 +187,7 @@
     ) {
         BackdropScaffoldState(
             initialValue = initialValue,
-            clock = clock,
+            clock = disposableClock,
             animationSpec = animationSpec,
             confirmStateChange = confirmStateChange,
             snackbarHostState = snackbarHostState
@@ -201,6 +203,8 @@
  * This component provides an API to put together several material components to construct your
  * screen. For a similar component which implements the basic material design layout strategy
  * with app bars, floating action buttons and navigation drawers, use the standard [Scaffold].
+ * For similar component that uses a bottom sheet as the centerpiece of the screen, use
+ * [BottomSheetScaffold].
  *
  * Either the back layer or front layer can be active at a time. When the front layer is active,
  * it sits at an offset below the top of the screen. This is the [peekHeight] and defaults to
@@ -210,21 +214,18 @@
  * To switch between the back layer and front layer, you can either swipe on the front layer if
  * [gesturesEnabled] is set to `true` or use any of the methods in [BackdropScaffoldState].
  *
- * The backdrop also contains an app bar, which by default is placed above the back layer's
+ * The scaffold also contains an app bar, which by default is placed above the back layer's
  * content. If [persistentAppBar] is set to `false`, then the backdrop will not show the app bar
  * when the back layer is revealed; instead it will switch between the app bar and the provided
  * content with an animation. For best results, the [peekHeight] should match the app bar height.
+ * To show a snackbar, use the method `showSnackbar` of [BackdropScaffoldState.snackbarHostState].
  *
- * A simple example of a backdrop looks like this:
+ * A simple example of a backdrop scaffold looks like this:
  *
- * @sample androidx.compose.material.samples.BackdropSample
+ * @sample androidx.compose.material.samples.BackdropScaffoldSample
  *
- * To show a snackbar, use [BackdropScaffoldState.snackbarHostState]:
- *
- * @sample androidx.compose.material.samples.BackdropWithSnackbarSample
- *
- * @param modifier Optional [Modifier] for the entire backdrop.
- * @param backdropScaffoldState The state of the backdrop.
+ * @param modifier Optional [Modifier] for the root of the scaffold.
+ * @param scaffoldState The state of the scaffold.
  * @param gesturesEnabled Whether or not the backdrop can be interacted with by gestures.
  * @param peekHeight The height of the visible part of the back layer when it is concealed.
  * @param headerHeight The minimum height of the front layer when it is inactive.
@@ -245,7 +246,7 @@
  * @param frontLayerScrimColor The color of the scrim applied to the front layer when the back
  * layer is revealed. If you set this to `Color.Transparent`, then a scrim will not be applied
  * and interaction with the front layer will not be blocked when the back layer is revealed.
- * @param snackbarHost The component hosting the snackbars shown inside the backdrop.
+ * @param snackbarHost The component hosting the snackbars shown inside the scaffold.
  * @param appBar App bar for the back layer. Make sure that the [peekHeight] is equal to the
  * height of the app bar, so that the app bar is fully visible. Consider using [TopAppBar] but
  * set the elevation to 0dp and background color to transparent as a surface is already provided.
@@ -256,19 +257,19 @@
 @ExperimentalMaterialApi
 fun BackdropScaffold(
     modifier: Modifier = Modifier,
-    backdropScaffoldState: BackdropScaffoldState = rememberBackdropState(Concealed),
+    scaffoldState: BackdropScaffoldState = rememberBackdropScaffoldState(Concealed),
     gesturesEnabled: Boolean = true,
-    peekHeight: Dp = BackdropConstants.DefaultPeekHeight,
-    headerHeight: Dp = BackdropConstants.DefaultHeaderHeight,
+    peekHeight: Dp = BackdropScaffoldConstants.DefaultPeekHeight,
+    headerHeight: Dp = BackdropScaffoldConstants.DefaultHeaderHeight,
     persistentAppBar: Boolean = true,
     stickyFrontLayer: Boolean = true,
     backLayerBackgroundColor: Color = MaterialTheme.colors.primary,
     backLayerContentColor: Color = contentColorFor(backLayerBackgroundColor),
-    frontLayerShape: Shape = BackdropConstants.DefaultFrontLayerShape,
-    frontLayerElevation: Dp = BackdropConstants.DefaultFrontLayerElevation,
+    frontLayerShape: Shape = BackdropScaffoldConstants.DefaultFrontLayerShape,
+    frontLayerElevation: Dp = BackdropScaffoldConstants.DefaultFrontLayerElevation,
     frontLayerBackgroundColor: Color = MaterialTheme.colors.surface,
     frontLayerContentColor: Color = contentColorFor(frontLayerBackgroundColor),
-    frontLayerScrimColor: Color = BackdropConstants.DefaultFrontLayerScrimColor,
+    frontLayerScrimColor: Color = BackdropScaffoldConstants.DefaultFrontLayerScrimColor,
     snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
     appBar: @Composable () -> Unit,
     backLayerContent: @Composable () -> Unit,
@@ -284,7 +285,7 @@
                 backLayerContent()
             }
         } else {
-            BackLayerTransition(backdropScaffoldState.targetValue, appBar, backLayerContent)
+            BackLayerTransition(scaffoldState.targetValue, appBar, backLayerContent)
         }
     }
     val calculateBackLayerConstraints: (Constraints) -> Constraints = {
@@ -296,7 +297,7 @@
         color = backLayerBackgroundColor,
         contentColor = backLayerContentColor
     ) {
-        WithBackLayerHeight(
+        BackdropStack(
             modifier.fillMaxSize(),
             backLayer,
             calculateBackLayerConstraints
@@ -308,7 +309,7 @@
             }
 
             val swipeable = Modifier.swipeable(
-                state = backdropScaffoldState,
+                state = scaffoldState,
                 anchors = mapOf(
                     peekHeightPx to Concealed,
                     revealedHeight to Revealed
@@ -320,7 +321,7 @@
 
             // Front layer
             Surface(
-                Modifier.offsetPx(y = backdropScaffoldState.offset).then(swipeable),
+                Modifier.offsetPx(y = scaffoldState.offset).then(swipeable),
                 shape = frontLayerShape,
                 elevation = frontLayerElevation,
                 color = frontLayerBackgroundColor,
@@ -331,8 +332,8 @@
 
                     Scrim(
                         color = frontLayerScrimColor,
-                        onDismiss = { backdropScaffoldState.conceal() },
-                        visible = backdropScaffoldState.targetValue == Revealed
+                        onDismiss = { scaffoldState.conceal() },
+                        visible = scaffoldState.targetValue == Revealed
                     )
                 }
             }
@@ -341,10 +342,10 @@
             Box(
                 Modifier.zIndex(Float.POSITIVE_INFINITY),
                 gravity = ContentGravity.BottomCenter,
-                paddingBottom = if (backdropScaffoldState.isRevealed &&
+                paddingBottom = if (scaffoldState.isRevealed &&
                     revealedHeight == fullHeight - headerHeightPx) headerHeight else 0.dp
             ) {
-                snackbarHost(backdropScaffoldState.snackbarHostState)
+                snackbarHost(scaffoldState.snackbarHostState)
             }
         }
     }
@@ -409,12 +410,9 @@
     }
 }
 
-/**
- * A composable that defines its own content according to the height of one of its children.
- */
 @Composable
 @OptIn(ExperimentalSubcomposeLayoutApi::class)
-private fun WithBackLayerHeight(
+private fun BackdropStack(
     modifier: Modifier,
     backLayer: @Composable () -> Unit,
     calculateBackLayerConstraints: (Constraints) -> Constraints,
@@ -451,7 +449,7 @@
 /**
  * Contains useful constants for [BackdropScaffold].
  */
-object BackdropConstants {
+object BackdropScaffoldConstants {
 
     /**
      * The default peek height of the back layer.
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
new file mode 100644
index 0000000..0a6fe8f
--- /dev/null
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.asDisposableClock
+import androidx.compose.animation.core.AnimationClockObservable
+import androidx.compose.animation.core.AnimationEndReason
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Stack
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.savedinstancestate.Saver
+import androidx.compose.runtime.savedinstancestate.rememberSavedInstanceState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Layout
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.WithConstraints
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.onPositioned
+import androidx.compose.ui.platform.AnimationClockAmbient
+import androidx.compose.ui.platform.DensityAmbient
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+import kotlin.math.roundToInt
+
+/**
+ * Possible values of [BottomSheetState].
+ */
+@ExperimentalMaterialApi
+enum class BottomSheetValue {
+    /**
+     * The bottom sheet is visible, but only showing its peek height.
+     */
+    Collapsed,
+
+    /**
+     * The bottom sheet is visible at its maximum height.
+     */
+    Expanded
+}
+
+/**
+ * State of the persistent bottom sheet in [BottomSheetScaffold].
+ *
+ * @param initialValue The initial value of the state.
+ * @param clock The animation clock that will be used to drive the animations.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@ExperimentalMaterialApi
+class BottomSheetState(
+    initialValue: BottomSheetValue,
+    clock: AnimationClockObservable,
+    animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+    confirmStateChange: (BottomSheetValue) -> Boolean = { true }
+) : SwipeableState<BottomSheetValue>(
+    initialValue = initialValue,
+    clock = clock,
+    animationSpec = animationSpec,
+    confirmStateChange = confirmStateChange
+) {
+    /**
+     * Whether the bottom sheet is expanded.
+     */
+    val isExpanded: Boolean
+        get() = value == BottomSheetValue.Expanded
+
+    /**
+     * Whether the bottom sheet is collapsed.
+     */
+    val isCollapsed: Boolean
+        get() = value == BottomSheetValue.Collapsed
+
+    /**
+     * Expand the bottom sheet, with an animation.
+     *
+     * @param onExpanded Optional callback invoked when the bottom sheet has been expanded.
+     */
+    fun expand(onExpanded: (() -> Unit)? = null) {
+        animateTo(BottomSheetValue.Expanded, onEnd = { endReason, _ ->
+            if (endReason == AnimationEndReason.TargetReached) {
+                onExpanded?.invoke()
+            }
+        })
+    }
+
+    /**
+     * Collapse the bottom sheet, with an animation.
+     *
+     * @param onCollapsed Optional callback invoked when the bottom sheet has been collapsed.
+     */
+    fun collapse(onCollapsed: (() -> Unit)? = null) {
+        animateTo(BottomSheetValue.Collapsed, onEnd = { endReason, _ ->
+            if (endReason == AnimationEndReason.TargetReached) {
+                onCollapsed?.invoke()
+            }
+        })
+    }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [BottomSheetState].
+         */
+        fun Saver(
+            clock: AnimationClockObservable,
+            animationSpec: AnimationSpec<Float>,
+            confirmStateChange: (BottomSheetValue) -> Boolean
+        ): Saver<BottomSheetState, *> = Saver(
+            save = { it.value },
+            restore = {
+                BottomSheetState(
+                    initialValue = it,
+                    clock = clock,
+                    animationSpec = animationSpec,
+                    confirmStateChange = confirmStateChange
+                )
+            }
+        )
+    }
+}
+
+/**
+ * Create a [BottomSheetState] and [remember] it against the [clock]. If a clock is not
+ * specified, the default animation clock will be used, as provided by [AnimationClockAmbient].
+ *
+ * @param initialValue The initial value of the state.
+ * @param clock The animation clock that will be used to drive the animations.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+@ExperimentalMaterialApi
+fun rememberBottomSheetState(
+    initialValue: BottomSheetValue,
+    clock: AnimationClockObservable = AnimationClockAmbient.current,
+    animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+    confirmStateChange: (BottomSheetValue) -> Boolean = { true }
+): BottomSheetState {
+    val disposableClock = clock.asDisposableClock()
+    return rememberSavedInstanceState(
+        disposableClock,
+        saver = BottomSheetState.Saver(
+            clock = disposableClock,
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    ) {
+        BottomSheetState(
+            initialValue = initialValue,
+            clock = disposableClock,
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * State of the [BottomSheetScaffold] composable.
+ *
+ * @param drawerState The state of the navigation drawer.
+ * @param bottomSheetState The state of the persistent bottom sheet.
+ * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the scaffold.
+ */
+@ExperimentalMaterialApi
+class BottomSheetScaffoldState(
+    val drawerState: DrawerState,
+    val bottomSheetState: BottomSheetState,
+    val snackbarHostState: SnackbarHostState
+)
+
+/**
+ * Create and [remember] a [BottomSheetScaffoldState].
+ *
+ * @param drawerState The state of the navigation drawer.
+ * @param bottomSheetState The state of the persistent bottom sheet.
+ * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the scaffold.
+ */
+@Composable
+@ExperimentalMaterialApi
+fun rememberBottomSheetScaffoldState(
+    drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
+    bottomSheetState: BottomSheetState = rememberBottomSheetState(BottomSheetValue.Collapsed),
+    snackbarHostState: SnackbarHostState = SnackbarHostState()
+): BottomSheetScaffoldState {
+    return remember(drawerState, bottomSheetState, snackbarHostState) {
+        BottomSheetScaffoldState(
+            drawerState = drawerState,
+            bottomSheetState = bottomSheetState,
+            snackbarHostState = snackbarHostState
+        )
+    }
+}
+
+/**
+ * Standard bottom sheets co-exist with the screen’s main UI region and allow for simultaneously
+ * viewing and interacting with both regions. They are commonly used to keep a feature or
+ * secondary content visible on screen when content in main UI region is frequently scrolled or
+ * panned.
+ *
+ * This component provides an API to put together several material components to construct your
+ * screen. For a similar component which implements the basic material design layout strategy
+ * with app bars, floating action buttons and navigation drawers, use the standard [Scaffold].
+ * For similar component that uses a backdrop as the centerpiece of the screen, use
+ * [BackdropScaffold].
+ *
+ * A simple example of a bottom sheet scaffold looks like this:
+ *
+ * @sample androidx.compose.material.samples.BottomSheetScaffoldSample
+ *
+ * @param sheetContent The content of the bottom sheet.
+ * @param modifier An optional [Modifier] for the root of the scaffold.
+ * @param scaffoldState The state of the scaffold.
+ * @param topBar An optional top app bar.
+ * @param snackbarHost The composable hosting the snackbars shown inside the scaffold.
+ * @param floatingActionButton An optional floating action button.
+ * @param floatingActionButtonPosition The position of the floating action button.
+ * @param sheetGesturesEnabled Whether the bottom sheet can be interacted with by gestures.
+ * @param sheetShape The shape of the bottom sheet.
+ * @param sheetElevation The elevation of the bottom sheet.
+ * @param sheetBackgroundColor The background color of the bottom sheet.
+ * @param sheetContentColor The preferred content color provided by the bottom sheet to its
+ * children. Defaults to the matching `onFoo` color for [sheetBackgroundColor], or if that is
+ * not a color from the theme, this will keep the same content color set above the bottom sheet.
+ * @param sheetPeekHeight The height of the bottom sheet when it is collapsed.
+ * @param drawerContent The content of the drawer sheet.
+ * @param drawerGesturesEnabled Whether the drawer sheet can be interacted with by gestures.
+ * @param drawerShape The shape of the drawer sheet.
+ * @param drawerElevation The elevation of the drawer sheet.
+ * @param drawerBackgroundColor The background color of the drawer sheet.
+ * @param drawerContentColor The preferred content color provided by the drawer sheet to its
+ * children. Defaults to the matching `onFoo` color for [drawerBackgroundColor], or if that is
+ * not a color from the theme, this will keep the same content color set above the drawer sheet.
+ * @param drawerScrimColor The color of the scrim that is applied when the drawer is open.
+ * @param bodyContent The main content of the screen. You should use the provided [PaddingValues]
+ * to properly offset the content, so that it is not obstructed by the bottom sheet when collapsed.
+ */
+@Composable
+@ExperimentalMaterialApi
+@OptIn(ExperimentalAnimationApi::class)
+fun BottomSheetScaffold(
+    sheetContent: @Composable ColumnScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(),
+    topBar: (@Composable () -> Unit)? = null,
+    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
+    floatingActionButton: (@Composable () -> Unit)? = null,
+    floatingActionButtonPosition: FabPosition = FabPosition.End,
+    sheetGesturesEnabled: Boolean = true,
+    sheetShape: Shape = MaterialTheme.shapes.large,
+    sheetElevation: Dp = BottomSheetScaffoldConstants.DefaultSheetElevation,
+    sheetBackgroundColor: Color = MaterialTheme.colors.surface,
+    sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
+    sheetPeekHeight: Dp = BottomSheetScaffoldConstants.DefaultSheetPeekHeight,
+    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
+    drawerGesturesEnabled: Boolean = true,
+    drawerShape: Shape = MaterialTheme.shapes.large,
+    drawerElevation: Dp = DrawerConstants.DefaultElevation,
+    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
+    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
+    drawerScrimColor: Color = DrawerConstants.defaultScrimColor,
+    backgroundColor: Color = MaterialTheme.colors.background,
+    contentColor: Color = contentColorFor(backgroundColor),
+    bodyContent: @Composable (PaddingValues) -> Unit
+) {
+    WithConstraints(modifier) {
+        val fullHeight = constraints.maxHeight.toFloat()
+        val peekHeightPx = with(DensityAmbient.current) { sheetPeekHeight.toPx() }
+        var bottomSheetHeight by remember { mutableStateOf(fullHeight) }
+
+        val swipeable = Modifier.swipeable(
+            state = scaffoldState.bottomSheetState,
+            anchors = mapOf(
+                fullHeight - peekHeightPx to BottomSheetValue.Collapsed,
+                fullHeight - bottomSheetHeight to BottomSheetValue.Expanded
+            ),
+            thresholds = { _, _ -> FixedThreshold(56.dp) },
+            orientation = Orientation.Vertical,
+            enabled = sheetGesturesEnabled,
+            resistance = null
+        )
+
+        val child = @Composable {
+            BottomSheetScaffoldStack(
+                body = {
+                    Surface(
+                        color = backgroundColor,
+                        contentColor = contentColor
+                    ) {
+                        Column(Modifier.fillMaxSize()) {
+                            topBar?.invoke()
+                            bodyContent(PaddingValues(bottom = sheetPeekHeight))
+                        }
+                    }
+                },
+                bottomSheet = {
+                    Surface(
+                        swipeable
+                            .fillMaxWidth()
+                            .heightIn(min = sheetPeekHeight)
+                            .onPositioned { bottomSheetHeight = it.size.height.toFloat() },
+                        shape = sheetShape,
+                        elevation = sheetElevation,
+                        color = sheetBackgroundColor,
+                        contentColor = sheetContentColor,
+                        content = { Column(children = sheetContent) }
+                    )
+                },
+                floatingActionButton = {
+                    Stack(Modifier.zIndex(FabZIndex)) {
+                        floatingActionButton?.invoke()
+                    }
+                },
+                snackbarHost = {
+                    Stack(Modifier.zIndex(SnackbarZIndex)) {
+                        snackbarHost(scaffoldState.snackbarHostState)
+                    }
+                },
+                bottomSheetOffset = scaffoldState.bottomSheetState.offset,
+                floatingActionButtonPosition = floatingActionButtonPosition
+            )
+        }
+        if (drawerContent == null) {
+            child()
+        } else {
+            ModalDrawerLayout(
+                drawerContent = drawerContent,
+                drawerState = scaffoldState.drawerState,
+                gesturesEnabled = drawerGesturesEnabled,
+                drawerShape = drawerShape,
+                drawerElevation = drawerElevation,
+                drawerBackgroundColor = drawerBackgroundColor,
+                drawerContentColor = drawerContentColor,
+                scrimColor = drawerScrimColor,
+                bodyContent = child
+            )
+        }
+    }
+}
+
+@Composable
+private fun BottomSheetScaffoldStack(
+    body: @Composable () -> Unit,
+    bottomSheet: @Composable () -> Unit,
+    floatingActionButton: @Composable () -> Unit,
+    snackbarHost: @Composable () -> Unit,
+    bottomSheetOffset: State<Float>,
+    floatingActionButtonPosition: FabPosition
+) {
+    Layout(children = {
+        body()
+        bottomSheet()
+        floatingActionButton()
+        snackbarHost()
+    }) { measurables, constraints ->
+        val placeable = measurables.first().measure(constraints)
+
+        layout(placeable.width, placeable.height) {
+            placeable.placeRelative(0, 0)
+
+            val (sheetPlaceable, fabPlaceable, snackbarPlaceable) =
+                measurables.drop(1).map {
+                    it.measure(constraints.copy(minWidth = 0, minHeight = 0))
+                }
+
+            val sheetOffsetY = bottomSheetOffset.value.roundToInt()
+
+            sheetPlaceable.placeRelative(0, sheetOffsetY)
+
+            val fabOffsetX = when (floatingActionButtonPosition) {
+                FabPosition.Center -> (placeable.width - fabPlaceable.width) / 2
+                FabPosition.End -> placeable.width - fabPlaceable.width - FabEndSpacing.toIntPx()
+            }
+            val fabOffsetY = sheetOffsetY - fabPlaceable.height / 2
+
+            fabPlaceable.placeRelative(fabOffsetX, fabOffsetY)
+
+            val snackbarOffsetX = (placeable.width - snackbarPlaceable.width) / 2
+            val snackbarOffsetY = placeable.height - snackbarPlaceable.height
+
+            snackbarPlaceable.placeRelative(snackbarOffsetX, snackbarOffsetY)
+        }
+    }
+}
+
+private val FabEndSpacing = 16.dp
+private val FabZIndex = 8f
+private val SnackbarZIndex = Float.POSITIVE_INFINITY
+
+/**
+ * Contains useful constants for [BottomSheetScaffold].
+ */
+object BottomSheetScaffoldConstants {
+
+    /**
+     * The default elevation used by [BottomSheetScaffold].
+     */
+    val DefaultSheetElevation = 8.dp
+
+    /**
+     * The default peek height used by [BottomSheetScaffold].
+     */
+    val DefaultSheetPeekHeight = 56.dp
+}
\ No newline at end of file
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
index 4c9f769..b30d451 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
@@ -326,8 +326,7 @@
     drawerElevation: Dp = DrawerConstants.DefaultElevation,
     drawerBackgroundColor: Color = MaterialTheme.colors.surface,
     drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
-    scrimColor: Color = MaterialTheme.colors.onSurface
-        .copy(alpha = DrawerConstants.ScrimDefaultOpacity),
+    scrimColor: Color = DrawerConstants.defaultScrimColor,
     bodyContent: @Composable () -> Unit
 ) {
     WithConstraints(modifier.fillMaxSize()) {
@@ -422,8 +421,7 @@
     drawerElevation: Dp = DrawerConstants.DefaultElevation,
     drawerBackgroundColor: Color = MaterialTheme.colors.surface,
     drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
-    scrimColor: Color = MaterialTheme.colors.onSurface
-        .copy(alpha = DrawerConstants.ScrimDefaultOpacity),
+    scrimColor: Color = DrawerConstants.defaultScrimColor,
     bodyContent: @Composable () -> Unit
 ) {
     WithConstraints(modifier.fillMaxSize()) {
@@ -507,6 +505,10 @@
      */
     val DefaultElevation = 16.dp
 
+    @Composable
+    val defaultScrimColor: Color
+        get() = MaterialTheme.colors.onSurface.copy(alpha = ScrimDefaultOpacity)
+
     /**
      * Default alpha for scrim color
      */
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
new file mode 100644
index 0000000..ee22a16
--- /dev/null
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.animation.animate
+import androidx.compose.animation.asDisposableClock
+import androidx.compose.animation.core.AnimationClockObservable
+import androidx.compose.animation.core.AnimationEndReason
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.TweenSpec
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Stack
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offsetPx
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.savedinstancestate.Saver
+import androidx.compose.runtime.savedinstancestate.rememberSavedInstanceState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.gesture.tapGestureFilter
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.layout.ExperimentalSubcomposeLayoutApi
+import androidx.compose.ui.layout.SubcomposeLayout
+import androidx.compose.ui.platform.AnimationClockAmbient
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import kotlin.math.max
+
+/**
+ * Possible values of [ModalBottomSheetState].
+ */
+@ExperimentalMaterialApi
+enum class ModalBottomSheetValue {
+    /**
+     * The bottom sheet is not visible.
+     */
+    Hidden,
+
+    /**
+     * The bottom sheet is visible at full height.
+     */
+    Expanded,
+
+    /**
+     * The bottom sheet is partially visible at 50% of the screen height. This state is only
+     * enabled if the height of the bottom sheet is more than 50% of the screen height.
+     */
+    HalfExpanded
+}
+
+/**
+ * State of the [ModalBottomSheetLayout] composable.
+ *
+ * @param initialValue The initial value of the state.
+ * @param clock The animation clock that will be used to drive the animations.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@ExperimentalMaterialApi
+class ModalBottomSheetState(
+    initialValue: ModalBottomSheetValue,
+    clock: AnimationClockObservable,
+    animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+    confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+) : SwipeableState<ModalBottomSheetValue>(
+    initialValue = initialValue,
+    clock = clock,
+    animationSpec = animationSpec,
+    confirmStateChange = confirmStateChange
+) {
+    /**
+     * Whether the bottom sheet is visible.
+     */
+    val isVisible: Boolean
+        get() = value != ModalBottomSheetValue.Hidden
+
+    private val isHalfExpandedEnabled: Boolean
+        get() = anchors.values.contains(ModalBottomSheetValue.HalfExpanded)
+
+    /**
+     * Show the bottom sheet, with an animation.
+     *
+     * @param onShown Optional callback invoked when the bottom sheet has been shown.
+     */
+    fun show(onShown: (() -> Unit)? = null) {
+        val targetValue =
+            if (isHalfExpandedEnabled) ModalBottomSheetValue.HalfExpanded
+            else ModalBottomSheetValue.Expanded
+        animateTo(targetValue = targetValue, onEnd = { endReason, _ ->
+            if (endReason == AnimationEndReason.TargetReached) {
+                onShown?.invoke()
+            }
+        })
+    }
+
+    /**
+     * Hide the bottom sheet, with an animation.
+     *
+     * @param onHidden Optional callback invoked when the bottom sheet has been hidden.
+     */
+    fun hide(onHidden: (() -> Unit)? = null) {
+        animateTo(targetValue = ModalBottomSheetValue.Hidden, onEnd = { endReason, _ ->
+            if (endReason == AnimationEndReason.TargetReached) {
+                onHidden?.invoke()
+            }
+        })
+    }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [ModalBottomSheetState].
+         */
+        fun Saver(
+            clock: AnimationClockObservable,
+            animationSpec: AnimationSpec<Float>,
+            confirmStateChange: (ModalBottomSheetValue) -> Boolean
+        ): Saver<ModalBottomSheetState, *> = Saver(
+            save = { it.value },
+            restore = {
+                ModalBottomSheetState(
+                    initialValue = it,
+                    clock = clock,
+                    animationSpec = animationSpec,
+                    confirmStateChange = confirmStateChange
+                )
+            }
+        )
+    }
+}
+
+/**
+ * Create a [ModalBottomSheetState] and [remember] it against the [clock]. If a clock is not
+ * specified, the default animation clock will be used, as provided by [AnimationClockAmbient].
+ *
+ * @param initialValue The initial value of the state.
+ * @param clock The animation clock that will be used to drive the animations.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+@ExperimentalMaterialApi
+fun rememberModalBottomSheetState(
+    initialValue: ModalBottomSheetValue,
+    clock: AnimationClockObservable = AnimationClockAmbient.current,
+    animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+    confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+): ModalBottomSheetState {
+    val disposableClock = clock.asDisposableClock()
+    return rememberSavedInstanceState(
+        disposableClock,
+        saver = ModalBottomSheetState.Saver(
+            clock = disposableClock,
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    ) {
+        ModalBottomSheetState(
+            initialValue = initialValue,
+            clock = disposableClock,
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * Modal bottom sheets present a set of choices while blocking interaction with the rest of the
+ * screen. They are an alternative to inline menus and simple dialogs on mobile, providing
+ * additional room for content, iconography, and actions.
+ *
+ * A simple example of a modal bottom sheet looks like this:
+ *
+ * @sample androidx.compose.material.samples.ModalBottomSheetSample
+ *
+ * @param sheetContent The content of the bottom sheet.
+ * @param modifier Optional [Modifier] for the entire component.
+ * @param sheetState The state of the bottom sheet.
+ * @param sheetShape The shape of the bottom sheet.
+ * @param sheetElevation The elevation of the bottom sheet.
+ * @param sheetBackgroundColor The background color of the bottom sheet.
+ * @param sheetContentColor The preferred content color provided by the bottom sheet to its
+ * children. Defaults to the matching `onFoo` color for [sheetBackgroundColor], or if that is not
+ * a color from the theme, this will keep the same content color set above the bottom sheet.
+ * @param scrimColor The color of the scrim that is applied to the rest of the screen when the
+ * bottom sheet is visible. If you set this to `Color.Transparent`, then a scrim will no longer be
+ * applied and the bottom sheet will not block interaction with the rest of the screen when visible.
+ * @param content The content of rest of the screen.
+ */
+@Composable
+@ExperimentalMaterialApi
+fun ModalBottomSheetLayout(
+    sheetContent: @Composable ColumnScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    sheetState: ModalBottomSheetState =
+        rememberModalBottomSheetState(ModalBottomSheetValue.Hidden),
+    sheetShape: Shape = MaterialTheme.shapes.large,
+    sheetElevation: Dp = ModalBottomSheetConstants.DefaultElevation,
+    sheetBackgroundColor: Color = MaterialTheme.colors.surface,
+    sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
+    scrimColor: Color = ModalBottomSheetConstants.DefaultScrimColor,
+    content: @Composable () -> Unit
+) = BottomSheetStack(
+    modifier = modifier,
+    sheetContent = {
+        Surface(
+            Modifier
+                .fillMaxWidth()
+                .offsetPx(y = sheetState.offset),
+            shape = sheetShape,
+            elevation = sheetElevation,
+            color = sheetBackgroundColor,
+            contentColor = sheetContentColor
+        ) {
+            Column(children = sheetContent)
+        }
+    },
+    content = { constraints, sheetHeight ->
+        val fullHeight = constraints.maxHeight.toFloat()
+        val anchors = if (sheetHeight < fullHeight / 2) {
+            mapOf(
+                fullHeight to ModalBottomSheetValue.Hidden,
+                fullHeight - sheetHeight to ModalBottomSheetValue.Expanded
+            )
+        } else {
+            mapOf(
+                fullHeight to ModalBottomSheetValue.Hidden,
+                fullHeight / 2 to ModalBottomSheetValue.HalfExpanded,
+                max(0f, fullHeight - sheetHeight) to ModalBottomSheetValue.Expanded
+            )
+        }
+        val swipeable = Modifier.swipeable(
+            state = sheetState,
+            anchors = anchors,
+            thresholds = { _, _ -> FixedThreshold(56.dp) },
+            orientation = Orientation.Vertical,
+            enabled = sheetState.value != ModalBottomSheetValue.Hidden,
+            resistance = null
+        )
+
+        Stack(Modifier.fillMaxSize().then(swipeable)) {
+            content()
+
+            Scrim(
+                color = scrimColor,
+                onDismiss = { sheetState.hide() },
+                visible = sheetState.targetValue != ModalBottomSheetValue.Hidden
+            )
+        }
+    }
+)
+
+@Composable
+private fun Scrim(
+    color: Color,
+    onDismiss: () -> Unit,
+    visible: Boolean
+) {
+    if (color != Color.Transparent) {
+        val alpha = animate(target = if (visible) 1f else 0f, animSpec = TweenSpec())
+        val dismissModifier = if (visible) Modifier.tapGestureFilter { onDismiss() } else Modifier
+
+        Canvas(
+            Modifier
+                .fillMaxSize()
+                .then(dismissModifier)
+        ) {
+            drawRect(color = color, alpha = alpha)
+        }
+    }
+}
+
+@Composable
+@OptIn(ExperimentalSubcomposeLayoutApi::class)
+private fun BottomSheetStack(
+    modifier: Modifier,
+    sheetContent: @Composable () -> Unit,
+    content: @Composable (constraints: Constraints, sheetHeight: Float) -> Unit
+) {
+    SubcomposeLayout<BottomSheetStackSlot>(modifier) { constraints ->
+        val sheetPlaceable =
+            subcompose(BottomSheetStackSlot.SheetContent, sheetContent)
+                .first().measure(constraints.copy(minWidth = 0, minHeight = 0))
+
+        val sheetHeight = sheetPlaceable.height.toFloat()
+
+        val placeable =
+            subcompose(BottomSheetStackSlot.Content) { content(constraints, sheetHeight) }
+                .first().measure(constraints)
+
+        layout(placeable.width, placeable.height) {
+            placeable.placeRelative(0, 0)
+            sheetPlaceable.placeRelative(0, 0)
+        }
+    }
+}
+
+private enum class BottomSheetStackSlot { SheetContent, Content }
+
+/**
+ * Contains useful constants for [ModalBottomSheetLayout].
+ */
+object ModalBottomSheetConstants {
+
+    /**
+     * The default elevation used by [ModalBottomSheetLayout].
+     */
+    val DefaultElevation = 16.dp
+
+    /**
+     * The default scrim color used by [ModalBottomSheetLayout].
+     */
+    @Composable
+    val DefaultScrimColor: Color
+        get() = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
+}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
index edac09c..3ff001d 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
@@ -127,8 +127,9 @@
  * screen, by ensuring proper layout strategy for them and collecting necessary data so these
  * components will work together correctly.
  *
- * For a similar API which uses a backdrop as the centerpiece of the screen, use the experimental
- * [BackdropScaffold] component.
+ * For similar components that implement different layout structures, see [BackdropScaffold],
+ * which uses a backdrop as the centerpiece of the screen, and [BottomSheetScaffold], which uses
+ * a persistent bottom sheet as the centerpiece of the screen.
  *
  * Simple example of a Scaffold with [TopAppBar], [FloatingActionButton] and drawer:
  *
@@ -193,8 +194,7 @@
     drawerElevation: Dp = DrawerConstants.DefaultElevation,
     drawerBackgroundColor: Color = MaterialTheme.colors.surface,
     drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
-    drawerScrimColor: Color = MaterialTheme.colors.onSurface
-        .copy(alpha = DrawerConstants.ScrimDefaultOpacity),
+    drawerScrimColor: Color = DrawerConstants.defaultScrimColor,
     backgroundColor: Color = MaterialTheme.colors.background,
     contentColor: Color = contentColorFor(backgroundColor),
     bodyContent: @Composable (PaddingValues) -> Unit
diff --git a/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/ActualDesktop.kt b/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/ActualDesktop.kt
index 2411c19..55c2720 100644
--- a/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/ActualDesktop.kt
+++ b/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/ActualDesktop.kt
@@ -16,6 +16,18 @@
 
 package androidx.compose.runtime.dispatch
 
+import kotlinx.coroutines.yield
+
+// TODO(demin): how to implement this clock in case we have multiple windows on different
+//  monitors? This clock is using by Recomposer to sync composition with frame rendering.
+//  Also this clock is available to use in coroutines in client code.
 actual val DefaultMonotonicFrameClock: MonotonicFrameClock by lazy {
-    DesktopUiDispatcher.Dispatcher.frameClock
-}
+    object : MonotonicFrameClock {
+        override suspend fun <R> withFrameNanos(
+            onFrame: (Long) -> R
+        ): R {
+            yield()
+            return onFrame(System.nanoTime())
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt b/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt
deleted file mode 100644
index e1922fd..0000000
--- a/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.runtime.dispatch
-
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Runnable
-import kotlinx.coroutines.suspendCancellableCoroutine
-import java.awt.event.ActionListener
-import javax.swing.SwingUtilities.invokeLater
-import javax.swing.Timer
-import kotlin.coroutines.CoroutineContext
-
-private typealias Action = (Long) -> Unit
-private typealias Queue = ArrayList<Action>
-
-/**
- * Ticks scheduler for Desktop. It tries to mimic Android's Choreographer.
- *
- * There are some plans to make possible redrawing based on composition snapshots,
- * so maybe some requirements for dispatcher will be mitigated in the future.
- **/
-class DesktopUiDispatcher : CoroutineDispatcher() {
-    private val lock = Any()
-    @PublishedApi internal val callbackLock = Any()
-    private var callbacks = Queue()
-
-    @Volatile
-    private var scheduled = false
-
-    private fun scheduleIfNeeded() {
-        synchronized(lock) {
-            if (!scheduled && hasPendingChanges()) {
-                invokeLater { tick() }
-                scheduled = true
-            }
-        }
-    }
-
-    val frameClock: MonotonicFrameClock = object :
-        MonotonicFrameClock {
-        override suspend fun <R> withFrameNanos(
-            onFrame: (Long) -> R
-        ): R {
-            return suspendCancellableCoroutine { co ->
-                val callback = { now: Long ->
-                    val res = runCatching {
-                        onFrame(now)
-                    }
-                    co.resumeWith(res)
-                }
-                scheduleCallback(callback)
-                co.invokeOnCancellation {
-                    removeCallback(callback)
-                }
-            }
-        }
-    }
-
-    fun hasPendingChanges() = callbacks.isNotEmpty()
-
-    fun runAllCallbacks() {
-        val now = System.nanoTime()
-        runCallbacks(now, callbacks)
-        scheduleIfNeeded()
-    }
-
-    private fun tick() {
-        scheduled = false
-        runAllCallbacks()
-        scheduleIfNeeded()
-    }
-
-    fun scheduleCallback(action: Action) {
-        synchronized(lock) {
-            callbacks.add(action)
-            scheduleIfNeeded()
-        }
-    }
-
-    fun removeCallback(action: (Long) -> Unit) {
-        synchronized(lock) {
-            callbacks.remove(action)
-        }
-    }
-
-    fun scheduleCallbackWithDelay(delay: Int, action: Action) {
-        Timer(delay,
-            ActionListener {
-                scheduleCallback { action(System.nanoTime()) }
-            }).apply {
-            isRepeats = false
-            start()
-        }
-    }
-
-    /**
-     * Prevent execution of callbacks while we execute [block] on other thread.
-     */
-    inline fun lockCallbacks(block: () -> Unit) = synchronized(callbackLock, block)
-
-    private fun runCallbacks(now: Long, callbacks: Queue) {
-        synchronized(lock) {
-            callbacks.toList().also { callbacks.clear() }
-        }.forEach {
-            synchronized(callbackLock) {
-                it(now)
-            }
-        }
-    }
-
-    override fun dispatch(context: CoroutineContext, block: Runnable) {
-        scheduleCallback { block.run() }
-    }
-
-    companion object {
-        val Dispatcher: DesktopUiDispatcher by lazy { DesktopUiDispatcher() }
-        val Main: CoroutineContext by lazy {
-            Dispatcher + Dispatcher.frameClock
-        }
-    }
-}
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index 180c8a2..46b9aae 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -56,6 +56,9 @@
             dependsOn jvmMain
         }
         desktopMain {
+            dependencies {
+                api(KOTLIN_COROUTINES_SWING)
+            }
             dependsOn jvmMain
         }
 
diff --git a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.kt b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.kt
index c610e73..efcc40f 100644
--- a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.kt
+++ b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.kt
@@ -16,7 +16,9 @@
 
 package androidx.compose.runtime
 
-import androidx.compose.runtime.dispatch.DesktopUiDispatcher
+import androidx.compose.runtime.dispatch.DefaultMonotonicFrameClock
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.swing.Swing
 import javax.swing.SwingUtilities
 import kotlin.coroutines.CoroutineContext
 
@@ -29,7 +31,7 @@
     }
 
     override fun mainThreadCompositionContext(): CoroutineContext {
-        return DesktopUiDispatcher.Main
+        return Dispatchers.Swing + DefaultMonotonicFrameClock
     }
 }
 
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/Rects.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/Rects.kt
index c322ca1..010c9da 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/Rects.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/Rects.kt
@@ -24,7 +24,7 @@
         top,
         right,
         bottom
-    )
+    )!!
 }
 
 fun org.jetbrains.skija.Rect.toComposeRect() =
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidAmbients.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidAmbients.kt
index ad2cecf..39abd69 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidAmbients.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidAmbients.kt
@@ -19,6 +19,8 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.view.View
+import androidx.compose.animation.core.InternalAnimationApi
+import androidx.compose.animation.core.rootAnimationClockFactory
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.Providers
@@ -64,9 +66,11 @@
 val ViewModelStoreOwnerAmbient = staticAmbientOf<ViewModelStoreOwner>()
 
 @Composable
+@OptIn(InternalAnimationApi::class)
 internal fun ProvideAndroidAmbients(owner: AndroidOwner, content: @Composable () -> Unit) {
     val view = owner.view
     val context = view.context
+    val rootAnimationClock = remember { rootAnimationClockFactory() }
 
     var configuration by remember {
         mutableStateOf(
@@ -100,6 +104,7 @@
     ) {
         ProvideCommonAmbients(
             owner = owner,
+            animationClock = rootAnimationClock,
             uriHandler = uriHandler,
             content = content
         )
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Ambients.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Ambients.kt
index a8eca12..32b538a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Ambients.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Ambients.kt
@@ -17,11 +17,8 @@
 package androidx.compose.ui.platform
 
 import androidx.compose.animation.core.AnimationClockObservable
-import androidx.compose.animation.core.InternalAnimationApi
-import androidx.compose.animation.core.rootAnimationClockFactory
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Providers
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.staticAmbientOf
 import androidx.compose.ui.autofill.Autofill
 import androidx.compose.ui.autofill.AutofillTree
@@ -98,20 +95,17 @@
 val LayoutDirectionAmbient = staticAmbientOf<LayoutDirection>()
 
 @Composable
-@OptIn(InternalAnimationApi::class)
 internal fun ProvideCommonAmbients(
     owner: Owner,
+    animationClock: AnimationClockObservable,
     uriHandler: UriHandler,
     content: @Composable () -> Unit
 ) {
-    val rootAnimationClock = remember { rootAnimationClockFactory() }
-
     Providers(
-        AnimationClockAmbient provides rootAnimationClock,
+        AnimationClockAmbient provides animationClock,
         AutofillAmbient provides owner.autofill,
         AutofillTreeAmbient provides owner.autofillTree,
         ClipboardManagerAmbient provides owner.clipboardManager,
-        @Suppress("DEPRECATION")
         DensityAmbient provides owner.density,
         FontLoaderAmbient provides owner.fontLoader,
         HapticFeedBackAmbient provides owner.hapticFeedBack,
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopAnimationClock.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopAnimationClock.kt
new file mode 100644
index 0000000..f790d2c
--- /dev/null
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopAnimationClock.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.platform
+
+import androidx.compose.animation.core.AnimationClockObservable
+import androidx.compose.animation.core.AnimationClockObserver
+import androidx.compose.animation.core.ManualAnimationClock
+
+internal class DesktopAnimationClock(
+    private val invalidate: () -> Unit
+) : AnimationClockObservable {
+    private val manual = ManualAnimationClock(0, dispatchOnSubscribe = false)
+
+    fun onFrame(nanoTime: Long) {
+        manual.clockTimeMillis = nanoTime / 1_000_000L
+        if (manual.hasObservers) {
+            invalidate()
+        }
+    }
+
+    override fun subscribe(observer: AnimationClockObserver) {
+        manual.subscribe(observer)
+        invalidate()
+    }
+
+    override fun unsubscribe(observer: AnimationClockObserver) {
+        manual.unsubscribe(observer)
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
index 6c0e93a..a948e9c 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
@@ -15,8 +15,6 @@
  */
 package androidx.compose.ui.platform
 
-import androidx.compose.runtime.Recomposer
-import androidx.compose.runtime.dispatch.DesktopUiDispatcher
 import androidx.compose.runtime.staticAmbientOf
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.mouse.MouseScrollEvent
@@ -36,16 +34,14 @@
 @OptIn(InternalCoreApi::class)
 class DesktopOwners(
     component: Component,
-    private val redraw: () -> Unit
+    val invalidate: () -> Unit
 ) {
     val list = LinkedHashSet<DesktopOwner>()
 
-    // Optimization: we don't need more than one redrawing per tick
-    private var redrawingScheduled = false
-
     private var pointerId = 0L
     private var isMousePressed = false
 
+    internal val animationClock = DesktopAnimationClock(invalidate)
     internal val platformInputService: DesktopPlatformInput = DesktopPlatformInput(component)
 
     fun register(desktopOwner: DesktopOwner) {
@@ -58,7 +54,8 @@
         invalidate()
     }
 
-    fun onRender(canvas: Canvas, width: Int, height: Int) {
+    fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
+        animationClock.onFrame(nanoTime)
         for (owner in list) {
             owner.setSize(width, height)
             owner.draw(canvas)
@@ -121,18 +118,4 @@
             )
         )
     }
-
-    fun invalidate() {
-        if (!redrawingScheduled) {
-            DesktopUiDispatcher.Dispatcher.scheduleCallback {
-                redrawingScheduled = false
-                if (Recomposer.current().hasPendingChanges()) {
-                    invalidate()
-                } else {
-                    redraw()
-                }
-            }
-            redrawingScheduled = true
-        }
-    }
 }
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
index 233b986..3d98db3 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
@@ -47,6 +47,7 @@
     ) {
         ProvideCommonAmbients(
             owner = owner,
+            animationClock = owner.container.animationClock,
             uriHandler = DesktopUriHandler(),
             content = content
         )
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
index b571ab0..37a2e36 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
@@ -21,12 +21,10 @@
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
-import com.nhaarman.mockitokotlin2.doAnswer
-import com.nhaarman.mockitokotlin2.doReturn
-import com.nhaarman.mockitokotlin2.mock
 import org.jetbrains.skiko.Library
 import org.junit.Assert.assertEquals
 import org.junit.Test
+import java.awt.Component
 
 @OptIn(ExperimentalLayoutNodeApi::class, ExperimentalComposeApi::class)
 class DesktopOwnerTest {
@@ -36,10 +34,12 @@
 
         var invalidateCount = 0
 
-        val owners = mock<DesktopOwners> {
-            on { platformInputService } doReturn mock()
-            on { invalidate() }.doAnswer { invalidateCount++; Unit }
-        }
+        val owners = DesktopOwners(
+            component = object : Component() {},
+            invalidate = {
+                invalidateCount++
+            }
+        )
         val owner = DesktopOwner(owners)
         val node = LayoutNode()
         val state = mutableStateOf(2)
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
index bd0cd7e1..e8538a4 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
@@ -37,7 +37,7 @@
     val surface = Surface.makeRasterN32Premul(width, height)
     val canvas = surface.canvas
     val component = object : Component() {}
-    val owners = DesktopOwners(component = component, redraw = {})
+    val owners = DesktopOwners(component = component, invalidate = {})
 
     fun setContent(content: @Composable () -> Unit): DesktopOwners {
         val owner = DesktopOwner(owners, density)
diff --git a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
index 980aba5..45d819e 100644
--- a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
+++ b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
@@ -249,6 +249,9 @@
      * <p>
      * If one of the tables in the Observer does not exist in the database, this method throws an
      * {@link IllegalArgumentException}.
+     * <p>
+     * This method should be called on a background/worker thread as it performs database
+     * operations.
      *
      * @param observer The observer which listens the database for changes.
      */
@@ -322,6 +325,9 @@
 
     /**
      * Removes the observer from the observers list.
+     * <p>
+     * This method should be called on a background/worker thread as it performs database
+     * operations.
      *
      * @param observer The observer to remove.
      */
diff --git a/settings.gradle b/settings.gradle
index ff95a5c..052dbb5 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -336,6 +336,7 @@
 includeProject(":viewpager2:integration-tests:testapp", "viewpager2/integration-tests/testapp")
 includeProject(":wear:wear", "wear/wear")
 includeProject(":wear:wear-complications-data", "wear/wear-complications-data")
+includeProject(":wear:wear-complications-data-samples", "wear/wear-complications-data/samples")
 includeProject(":wear:wear-complications-rendering", "wear/wear-complications-rendering")
 includeProject(":wear:wear-input", "wear/wear-input")
 includeProject(":wear:wear-input-testing", "wear/wear-input-testing")
diff --git a/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/experimental/widget/FloatingToolbar.java b/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/experimental/widget/FloatingToolbar.java
deleted file mode 100644
index fa8f88a..0000000
--- a/textclassifier/integration-tests/testapp/src/main/java/androidx/textclassifier/integration/testapp/experimental/widget/FloatingToolbar.java
+++ /dev/null
@@ -1,558 +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.textclassifier.integration.testapp.experimental.widget;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Build;
-import android.text.TextUtils;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.view.menu.MenuItemImpl;
-import androidx.core.internal.view.SupportMenu;
-import androidx.core.internal.view.SupportMenuItem;
-import androidx.core.util.Preconditions;
-import androidx.core.view.MenuItemCompat;
-import androidx.core.view.ViewCompat;
-import androidx.textclassifier.integration.testapp.R;
-import androidx.textclassifier.widget.IFloatingToolbar;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * An experimental implementation of floating toolbar that supports slice.
- */
-@RequiresApi(api = Build.VERSION_CODES.M)
-public final class FloatingToolbar implements IFloatingToolbar {
-
-    private static final SupportMenuItem.OnMenuItemClickListener NO_OP_MENUITEM_CLICK_LISTENER =
-            new SupportMenuItem.OnMenuItemClickListener() {
-                @Override
-                public boolean onMenuItemClick(android.view.MenuItem item) {
-                    return false;
-                }
-            };
-
-    @VisibleForTesting
-    static final Object FLOATING_TOOLBAR_TAG = "floating_toolbar";
-    @VisibleForTesting
-    static final Object MAIN_PANEL_TAG = "main_panel";
-
-    private final FloatingToolbarPopup mPopup;
-    private final Rect mContentRect = new Rect();
-
-    private SupportMenu mMenu;
-    private List<SupportMenuItem> mShowingMenuItems = new ArrayList<>();
-    private SupportMenuItem.OnMenuItemClickListener mMenuItemClickListener =
-            NO_OP_MENUITEM_CLICK_LISTENER;
-
-    /* Item click listeners */
-    private SupportMenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
-
-    private final Comparator<SupportMenuItem> mMenuItemComparator =
-            new Comparator<SupportMenuItem>() {
-                @Override
-                public int compare(SupportMenuItem menuItem1, SupportMenuItem menuItem2) {
-                    int customOrder1 = getCustomOrder(menuItem1);
-                    int customOrder2 = getCustomOrder(menuItem2);
-                    if (customOrder1 == customOrder2) {
-                        return compareOrder(menuItem1, menuItem2);
-                    }
-                    return Integer.compare(customOrder1, customOrder2);
-                }
-
-                private int getCustomOrder(SupportMenuItem menuItem) {
-                    if (menuItem.getItemId() == MENU_ID_SMART_ACTION) {
-                        return 0;
-                    }
-                    if (requiresActionButton(menuItem)) {
-                        return 1;
-                    }
-                    if (requiresOverflow(menuItem)) {
-                        return 3;
-                    }
-                    return 2;
-                }
-
-                private int compareOrder(SupportMenuItem menuItem1, SupportMenuItem menuItem2) {
-                    return menuItem1.getOrder() - menuItem2.getOrder();
-                }
-            };
-
-    public FloatingToolbar(View view) {
-        mPopup = new FloatingToolbarPopup(view.getRootView());
-    }
-
-    @Override
-    public void setMenu(@NonNull SupportMenu menu) {
-        mMenu = Preconditions.checkNotNull(menu);
-    }
-
-    @Nullable
-    @Override
-    public SupportMenu getMenu() {
-        return mMenu;
-    }
-
-    @Override
-    public void setSuggestedWidth(int suggestedWidth) {}
-
-    @Override
-    public void show() {
-        doShow();
-    }
-
-    @Override
-    public void setContentRect(@NonNull Rect rect) {
-
-    }
-
-    private void doShow() {
-        List<SupportMenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
-        Collections.sort(menuItems, mMenuItemComparator);
-        if (!isCurrentlyShowing(menuItems)) {
-            mPopup.layoutMenuItems(menuItems, mOnMenuItemClickListener);
-        }
-        mPopup.show(mContentRect);
-    }
-
-    /**
-     * Returns true if this floating toolbar is currently showing the specified menu items.
-     */
-    private boolean isCurrentlyShowing(List<SupportMenuItem> menuItems) {
-        if (mShowingMenuItems == null || menuItems.size() != mShowingMenuItems.size()) {
-            return false;
-        }
-
-        final int size = menuItems.size();
-        for (int i = 0; i < size; i++) {
-            final SupportMenuItem menuItem = menuItems.get(i);
-            final SupportMenuItem showingItem = mShowingMenuItems.get(i);
-            if (menuItem.getItemId() != showingItem.getItemId()
-                    || !TextUtils.equals(menuItem.getTitle(), showingItem.getTitle())
-                    || !Objects.equals(menuItem.getIcon(), showingItem.getIcon())
-                    || menuItem.getGroupId() != showingItem.getGroupId()) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    @Override
-    public void updateLayout() {}
-
-    @Override
-    public void dismiss() {
-        mPopup.dismiss();
-    }
-
-    @Override
-    public void hide() {}
-
-    @Override
-    public boolean isShowing() {
-        return false;
-    }
-
-    @Override
-    public boolean isHidden() {
-        return false;
-    }
-
-    @Override
-    public void setOnDismissListener(@Nullable PopupWindow.OnDismissListener onDismiss) {}
-
-    @Override
-    public void setDismissOnMenuItemClick(boolean dismiss) {}
-
-    @Override
-    public void setOnMenuItemClickListener(
-            @Nullable SupportMenuItem.OnMenuItemClickListener menuItemClickListener) {
-        mMenuItemClickListener = mMenuItemClickListener == null
-                ? NO_OP_MENUITEM_CLICK_LISTENER : menuItemClickListener;
-    }
-
-    /**
-     * Returns the visible and enabled menu items in the specified menu.
-     * This method is recursive.
-     */
-    private static List<SupportMenuItem> getVisibleAndEnabledMenuItems(SupportMenu menu) {
-        List<SupportMenuItem> menuItems = new ArrayList<>();
-        for (int i = 0; (menu != null) && (i < menu.size()); i++) {
-            SupportMenuItem menuItem = (SupportMenuItem) menu.getItem(i);
-            if (menuItem.isVisible() && menuItem.isEnabled()) {
-                SupportMenu subMenu = (SupportMenu) menuItem.getSubMenu();
-                if (subMenu != null) {
-                    menuItems.addAll(getVisibleAndEnabledMenuItems(subMenu));
-                } else {
-                    menuItems.add(menuItem);
-                }
-            }
-        }
-        return menuItems;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static boolean requiresOverflow(SupportMenuItem menuItem) {
-        if (menuItem instanceof MenuItemImpl) {
-            final MenuItemImpl impl = (MenuItemImpl) menuItem;
-            return !impl.requiresActionButton() && !impl.requestsActionButton();
-        }
-        return false;
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static boolean requiresActionButton(SupportMenuItem menuItem) {
-        return menuItem instanceof MenuItemImpl
-                && menuItem.requiresActionButton();
-    }
-
-    private static final class FloatingToolbarPopup {
-
-        final View mHost;  // Host for the popup window.
-        final Context mContext;
-        final PopupWindow mPopupWindow;
-
-        /* View components */
-        private final ViewGroup mContentContainer;  // holds all contents.
-        private final MainPanel mMainPanel;  // holds menu items that are initially displayed.
-
-        /* Item click listeners */
-        private SupportMenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
-        private final View.OnClickListener mMenuItemButtonOnClickListener =
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        if (v.getTag() instanceof SupportMenuItem) {
-                            if (mOnMenuItemClickListener != null) {
-                                mOnMenuItemClickListener.onMenuItemClick(
-                                        (SupportMenuItem) v.getTag());
-                            }
-                        }
-                    }
-                };
-
-        FloatingToolbarPopup(View host) {
-            mHost = Preconditions.checkNotNull(host);
-            mContext = host.getContext();
-            mPopupWindow = createPopupWindow(mContext);
-            mContentContainer = createContentContainer(
-                    mContext, (ViewGroup) mPopupWindow.getContentView());
-            mMainPanel = new MainPanel(
-                    mContentContainer.findViewById(R.id.mainPanel),
-                    mContext,
-                    mMenuItemButtonOnClickListener);
-        }
-
-        /**
-         * Shows this popup at the specified coordinates.
-         * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
-         */
-        public void show(Rect contentRectOnScreen) {
-            Preconditions.checkNotNull(contentRectOnScreen);
-            mPopupWindow.showAtLocation(mHost, Gravity.NO_GRAVITY, 0, 0);
-        }
-
-        /**
-         * Lays out buttons for the specified menu items.
-         * Requires a subsequent call to {@link #show()} to show the items.
-         */
-        public void layoutMenuItems(
-                List<SupportMenuItem> menuItems,
-                SupportMenuItem.OnMenuItemClickListener menuItemClickListener) {
-            mOnMenuItemClickListener = menuItemClickListener;
-            menuItems = mMainPanel.layoutMenuItems(menuItems);
-            if (!menuItems.isEmpty()) {
-                // Add remaining items to the overflow.
-            }
-        }
-
-        public void dismiss() {
-
-        }
-
-        /**
-         * Clears out the panels and their container. Resets their calculated sizes.
-         */
-        @SuppressWarnings("unchecked")
-        void clearPanels() {
-            mMainPanel.clear();
-            mContentContainer.removeAllViews();
-        }
-
-        static ViewGroup createContentContainer(Context context, ViewGroup parent) {
-            ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context)
-                    .inflate(R.layout.floating_popup_container, parent);
-            contentContainer.setTag(FLOATING_TOOLBAR_TAG);
-            contentContainer.setClipToOutline(true);
-            return contentContainer;
-        }
-
-        static PopupWindow createPopupWindow(Context context) {
-            ViewGroup popupContentHolder = new LinearLayout(context);
-            popupContentHolder.setSoundEffectsEnabled(false);
-            PopupWindow popupWindow = new PopupWindow(popupContentHolder);
-            popupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
-            popupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
-            popupWindow.setClippingEnabled(false);
-            popupWindow.setOutsideTouchable(true);
-            popupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
-            popupWindow.setAnimationStyle(0);
-            // Set the next line to Color.TRANSPARENT for a translucent popup.
-            int color = Color.argb(50, 50, 0, 0);
-            popupWindow.setBackgroundDrawable(new ColorDrawable(color));
-            return popupWindow;
-        }
-
-        /**
-         * This class is responsible for layout of the main panel.
-         */
-        private static final class MainPanel {
-
-            private final ViewGroup mWidget;
-            private final Context mContext;
-            private final int mIconTextSpacing;
-            private final int mOverflowButtonWidth;
-            private final int mToolbarWidth;
-
-            private int mAvailableWidth;
-
-            /* Item click listeners */
-            private final View.OnClickListener mMenuItemButtonOnClickListener;
-
-            MainPanel(ViewGroup widget,
-                    Context context,
-                    View.OnClickListener menuItemButtonOnClickListener) {
-                mWidget = Preconditions.checkNotNull(widget);
-                mWidget.setTag(MAIN_PANEL_TAG);
-                mContext = Preconditions.checkNotNull(context);
-                mMenuItemButtonOnClickListener =
-                        Preconditions.checkNotNull(menuItemButtonOnClickListener);
-                mIconTextSpacing = mContext.getResources()
-                        .getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing);
-                mOverflowButtonWidth = mContext.getResources()
-                        .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_button_width);
-                mToolbarWidth = mContext.getResources()
-                        .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width);
-                mAvailableWidth = mToolbarWidth;
-            }
-
-            /**
-             * Fits as many menu items in the main panel and returns a list of the menu items that
-             * were not fit in.
-             *
-             * @return The menu items that are not included in this main panel.
-             */
-            List<SupportMenuItem> layoutMenuItems(List<SupportMenuItem> menuItems) {
-                Preconditions.checkNotNull(menuItems);
-                final List<SupportMenuItem> mainPanelMenuItems = new ArrayList<>();
-                // add the overflow menu items to the end of the items list.
-                final List<SupportMenuItem> overflowMenuItems = new ArrayList<>();
-                for (SupportMenuItem menuItem : menuItems) {
-                    if (menuItem.getItemId() != MENU_ID_SMART_ACTION
-                            && requiresOverflow(menuItem)) {
-                        overflowMenuItems.add(menuItem);
-                    } else {
-                        mainPanelMenuItems.add(menuItem);
-                    }
-                }
-                mainPanelMenuItems.addAll(overflowMenuItems);
-
-                clear();
-                mWidget.setPaddingRelative(0, 0, 0, 0);
-                int index;
-                boolean isFirst = true;
-                boolean isLast = menuItems.size() == 1;
-                int itemCount = mainPanelMenuItems.size();
-                for (index = 0; index < itemCount; index++) {
-                    isLast = index == itemCount - 1;
-                    boolean added = addItem(
-                            mainPanelMenuItems.get(index),
-                            isFirst,
-                            isLast);
-                    if (!added) {
-                        break;
-                    }
-                    isFirst = false;
-                }
-                if (!isLast) {
-                    // Reserve space for overflowButton.
-                    mWidget.setPaddingRelative(0, 0, mOverflowButtonWidth, 0);
-                }
-                return mainPanelMenuItems.subList(index, itemCount);
-            }
-
-            void clear() {
-                mWidget.removeAllViews();
-                mAvailableWidth = mToolbarWidth;
-            }
-
-            /**
-             * Returns true if the given menu item is successfully added to the main panel ,
-             * otherwise, returns false.
-             */
-            private boolean addItem(final SupportMenuItem item, boolean isFirst, boolean isLast) {
-                // if this is the first item, regardless of requiresOverflow(), it should be
-                // displayed on the main panel. Otherwise all items including this one will be
-                // overflow items, and should be displayed in overflow panel.
-                if (!isFirst && requiresOverflow(item)) {
-                    return false;
-                }
-
-                final boolean showIcon = isFirst
-                        && item.getItemId() == MENU_ID_SMART_ACTION;
-                final View menuItemButton = PanelUtils.createMenuItemButton(
-                        mContext, item, mIconTextSpacing, showIcon);
-                if (!showIcon) {
-                    ((LinearLayout) menuItemButton).setGravity(Gravity.CENTER);
-                }
-
-                // Adding additional start padding for the first button to even out button
-                // spacing.
-                if (isFirst) {
-                    menuItemButton.setPaddingRelative(
-                            (int) (1.5 * menuItemButton.getPaddingStart()),
-                            menuItemButton.getPaddingTop(),
-                            menuItemButton.getPaddingEnd(),
-                            menuItemButton.getPaddingBottom());
-                }
-
-                // Adding additional end padding for the last button to even out button spacing.
-                if (isLast) {
-                    menuItemButton.setPaddingRelative(
-                            menuItemButton.getPaddingStart(),
-                            menuItemButton.getPaddingTop(),
-                            (int) (1.5 * menuItemButton.getPaddingEnd()),
-                            menuItemButton.getPaddingBottom());
-                }
-
-                menuItemButton.measure(
-                        View.MeasureSpec.UNSPECIFIED,
-                        View.MeasureSpec.UNSPECIFIED);
-                final int menuItemButtonWidth = Math.min(
-                        menuItemButton.getMeasuredWidth(), mToolbarWidth);
-
-                // Check if we can fit an item while reserving space for the overflowButton.
-                final boolean canFitWithOverflow =
-                        menuItemButtonWidth <= mAvailableWidth - mOverflowButtonWidth;
-                final boolean canFitNoOverflow =
-                        isLast && menuItemButtonWidth <= mAvailableWidth;
-                if (canFitWithOverflow || canFitNoOverflow) {
-                    PanelUtils.setButtonTagAndClickListener(
-                            menuItemButton, item, mMenuItemButtonOnClickListener);
-                    // Set tooltips for main panel items, but not overflow items (b/35726766).
-                    CharSequence tooltip = item.getTooltipText() == null
-                            ? item.getTitle()
-                            : item.getTooltipText();
-                    ViewCompat.setTooltipText(menuItemButton, tooltip);
-                    mWidget.addView(menuItemButton);
-                    final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
-                    params.width = menuItemButtonWidth;
-                    menuItemButton.setLayoutParams(params);
-                    mAvailableWidth -= menuItemButtonWidth;
-                    return true;
-                }
-                return false;
-            }
-        }
-
-        /**
-         * A helper class that contains the helper methods which are shared by different panels.
-         */
-        private static final class PanelUtils{
-
-            /**
-             * Creates and returns a menu button for the specified menu item.
-             */
-            static View createMenuItemButton(
-                    Context context,
-                    SupportMenuItem menuItem,
-                    int iconTextSpacing,
-                    boolean showIcon) {
-                final View menuItemButton = LayoutInflater.from(context)
-                        .inflate(R.layout.floating_popup_menu_button, null);
-                if (menuItem != null) {
-                    updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon);
-                }
-                return menuItemButton;
-            }
-
-            /**
-             * Updates the specified menu item button with the specified menu item data.
-             */
-            static void updateMenuItemButton(
-                    View menuItemButton,
-                    SupportMenuItem menuItem,
-                    int iconTextSpacing,
-                    boolean showIcon) {
-                final TextView buttonText = menuItemButton.findViewById(
-                        androidx.textclassifier.R.id.floating_toolbar_menu_item_text);
-                buttonText.setEllipsize(null);
-                if (TextUtils.isEmpty(menuItem.getTitle())) {
-                    buttonText.setVisibility(View.GONE);
-                } else {
-                    buttonText.setVisibility(View.VISIBLE);
-                    buttonText.setText(menuItem.getTitle());
-                }
-                final ImageView buttonIcon = menuItemButton.findViewById(
-                        androidx.textclassifier.R.id.floating_toolbar_menu_item_image);
-                if (menuItem.getIcon() == null || !showIcon) {
-                    buttonIcon.setVisibility(View.GONE);
-                    buttonText.setPaddingRelative(0, 0, 0, 0);
-                } else {
-                    buttonIcon.setVisibility(View.VISIBLE);
-                    buttonIcon.setImageDrawable(menuItem.getIcon());
-                    buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0);
-                }
-                final CharSequence contentDescription =
-                        MenuItemCompat.getContentDescription(menuItem);
-                if (TextUtils.isEmpty(contentDescription)) {
-                    menuItemButton.setContentDescription(menuItem.getTitle());
-                } else {
-                    menuItemButton.setContentDescription(contentDescription);
-                }
-            }
-
-            static void setButtonTagAndClickListener(
-                    View menuItemButton,
-                    SupportMenuItem menuItem,
-                    View.OnClickListener menuItemButtonOnClickListener) {
-                menuItemButton.setTag(menuItem);
-                menuItemButton.setOnClickListener(menuItemButtonOnClickListener);
-            }
-        }
-    }
-}
diff --git a/textclassifier/integration-tests/testapp/src/main/res/layout/floating_popup_container.xml b/textclassifier/integration-tests/testapp/src/main/res/layout/floating_popup_container.xml
deleted file mode 100644
index a5e375f..0000000
--- a/textclassifier/integration-tests/testapp/src/main/res/layout/floating_popup_container.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* 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.
-*/
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="350dp"
-    android:layout_height="200dp"
-    android:padding="0dp"
-    android:background="@android:color/background_light"
-    android:layout_marginLeft="16dp"
-    android:layout_marginRight="16dp"
-    android:layout_marginTop="12dp"
-    android:layout_marginBottom="12dp"
-    android:elevation="2dp"
-    android:focusable="true"
-    android:focusableInTouchMode="true">
-
-    <LinearLayout
-        android:id="@+id/slicePanel"
-        android:layout_width="350dp"
-        android:layout_height="200dp"
-        android:layout_alignParentTop="true"
-        android:layout_alignParentEnd="true"
-        android:paddingBottom="48dp"
-        android:background="@android:color/holo_blue_dark"
-        android:orientation="horizontal">
-    </LinearLayout>
-
-    <ListView
-        android:id="@+id/overflowPanel"
-        android:layout_width="200dp"
-        android:layout_height="200dp"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentEnd="true"
-        android:background="@android:color/black"/>
-
-    <LinearLayout
-        android:id="@+id/mainPanel"
-        android:layout_width="350dp"
-        android:layout_height="48dp"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentEnd="true"
-        android:background="@android:color/holo_red_dark"
-        android:orientation="horizontal" />
-
-    <ImageButton
-        android:id="@+id/overflowButton"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentEnd="true"
-        android:background="@android:color/holo_green_light"/>
-</RelativeLayout>
diff --git a/textclassifier/integration-tests/testapp/src/main/res/layout/floating_popup_menu_button.xml b/textclassifier/integration-tests/testapp/src/main/res/layout/floating_popup_menu_button.xml
deleted file mode 100644
index da17c0e..0000000
--- a/textclassifier/integration-tests/testapp/src/main/res/layout/floating_popup_menu_button.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright 2019 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:minWidth="@dimen/floating_toolbar_menu_button_minimum_width"
-    android:minHeight="@dimen/floating_toolbar_height"
-    android:paddingStart="@dimen/floating_toolbar_menu_button_side_padding"
-    android:paddingEnd="@dimen/floating_toolbar_menu_button_side_padding"
-    android:paddingTop="0dp"
-    android:paddingBottom="0dp"
-    android:background="?attr/selectableItemBackground">
-    <ImageView
-        android:id="@+id/floating_toolbar_menu_item_image"
-        android:layout_width="@dimen/floating_toolbar_menu_image_width"
-        android:layout_height="@dimen/floating_toolbar_height"
-        android:paddingTop="@dimen/floating_toolbar_menu_image_button_vertical_padding"
-        android:paddingStart="0dp"
-        android:paddingBottom="@dimen/floating_toolbar_menu_image_button_vertical_padding"
-        android:paddingEnd="0dp"
-        android:layout_margin="0dp"
-        android:scaleType="centerInside"
-        android:background="@null"
-        android:focusable="false"
-        android:focusableInTouchMode="false"
-        android:importantForAccessibility="no" />
-    <!-- TODO: need to change the text color to be "?attr/floatingToolbarForegroundColor"-->
-    <TextView
-        android:id="@+id/floating_toolbar_menu_item_text"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/floating_toolbar_height"
-        android:textAppearance="?android:attr/textAppearanceListItemSmall"
-        android:padding="0dp"
-        android:paddingStart="@dimen/floating_toolbar_menu_button_side_padding"
-        android:paddingLeft="@dimen/floating_toolbar_menu_button_side_padding"
-        android:layout_margin="0dp"
-        android:gravity="center"
-        android:singleLine="true"
-        android:ellipsize="end"
-        android:fontFamily="sans-serif-medium"
-        android:textSize="@dimen/floating_toolbar_text_size"
-        android:textAllCaps="@bool/floating_toolbar_all_caps"
-        android:background="@null"
-        android:focusable="false"
-        android:focusableInTouchMode="false"
-        android:importantForAccessibility="no"
-        android:textColor="@color/colorPrimaryDark" />
-</LinearLayout>
diff --git a/textclassifier/integration-tests/testapp/src/main/res/values/dimens.xml b/textclassifier/integration-tests/testapp/src/main/res/values/dimens.xml
index 25ed43d..4af50b5 100644
--- a/textclassifier/integration-tests/testapp/src/main/res/values/dimens.xml
+++ b/textclassifier/integration-tests/testapp/src/main/res/values/dimens.xml
@@ -16,14 +16,4 @@
   -->
 
 <resources>
-    <!-- Floating toolbar dimensions -->
-    <dimen name="floating_toolbar_preferred_width">350dp</dimen>
-    <dimen name="floating_toolbar_overflow_button_width">48dp</dimen>
-    <dimen name="floating_toolbar_icon_text_spacing">8dp</dimen>
-    <dimen name="floating_toolbar_menu_button_minimum_width">48dp</dimen>
-    <dimen name="floating_toolbar_height">48dp</dimen>
-    <dimen name="floating_toolbar_menu_button_side_padding">16dp</dimen>
-    <dimen name="floating_toolbar_menu_image_width">24dp</dimen>
-    <dimen name="floating_toolbar_menu_image_button_vertical_padding">12dp</dimen>
-    <dimen name="floating_toolbar_text_size">14sp</dimen>
 </resources>
diff --git a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopComposeTestRule.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopComposeTestRule.kt
index cea37ec..04cba4d 100644
--- a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopComposeTestRule.kt
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopComposeTestRule.kt
@@ -21,7 +21,6 @@
 import androidx.compose.runtime.EmbeddingContextFactory
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.Recomposer
-import androidx.compose.runtime.dispatch.DesktopUiDispatcher
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.platform.DesktopOwner
 import androidx.compose.ui.platform.DesktopOwners
@@ -88,13 +87,11 @@
 
     @OptIn(ExperimentalComposeApi::class)
     private fun isIdle() =
-        !DesktopUiDispatcher.Dispatcher.hasPendingChanges() &&
                 !Snapshot.current.hasPendingChanges() &&
                 !Recomposer.current().hasPendingChanges()
 
     override fun waitForIdle() {
         while (!isIdle()) {
-            DesktopUiDispatcher.Dispatcher.runAllCallbacks()
             runExecutionQueue()
             Thread.sleep(10)
         }
@@ -114,7 +111,7 @@
         val surface = Surface.makeRasterN32Premul(displaySize.width, displaySize.height)
         val canvas = surface.canvas
         val component = object : Component() {}
-        val owners = DesktopOwners(component = component, redraw = {}).also {
+        val owners = DesktopOwners(component = component, invalidate = {}).also {
             owners = it
         }
         val owner = DesktopOwner(owners)
diff --git a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/TestSkiaWindow.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/TestSkiaWindow.kt
index 6e1680b..8c3cac7 100644
--- a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/TestSkiaWindow.kt
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/TestSkiaWindow.kt
@@ -49,7 +49,7 @@
 
     fun setContent(content: @Composable () -> Unit) {
         val component = object : Component() {}
-        val owners = DesktopOwners(component = component, redraw = {})
+        val owners = DesktopOwners(component = component, invalidate = {})
         val owner = DesktopOwner(owners)
         owner.setContent(content)
         owner.setSize(width, height)
diff --git a/wear/wear-complications-data/samples/build.gradle b/wear/wear-complications-data/samples/build.gradle
new file mode 100644
index 0000000..1f38a2e
--- /dev/null
+++ b/wear/wear-complications-data/samples/build.gradle
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("kotlin-android")
+}
+
+dependencies {
+    implementation("androidx.concurrent:concurrent-futures:1.0.0")
+    implementation("androidx.core:core:1.1.0")
+    api(project(":wear:wear-complications-data"))
+    api(GUAVA_ANDROID)
+    api(KOTLIN_STDLIB)
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 28
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-complications-data/samples/src/main/AndroidManifest.xml b/wear/wear-complications-data/samples/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6ec6811
--- /dev/null
+++ b/wear/wear-complications-data/samples/src/main/AndroidManifest.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="androidx.wear.complications.samples">
+    <application
+        android:label="@string/app_name"
+        android:icon="@drawable/circle"
+        android:theme="@style/Theme.AppCompat.Light.NoActionBar">
+        <service
+            android:name=".AsynchronousProviderService"
+            android:label="@string/asynchronous_provider_name"
+            android:exported="true"
+            android:icon="@drawable/circle"
+            android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
+            <meta-data
+                android:name="android.support.wearable.complications.SUPPORTED_TYPES"
+                android:value="SHORT_TEXT,LONG_TEXT"/>
+            <meta-data
+                android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
+                android:value="0"/>
+            <intent-filter>
+                <action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST"/>
+            </intent-filter>
+        </service>
+
+        <service
+            android:name=".BackgroundProviderService"
+            android:label="@string/background_provider_name"
+            android:exported="true"
+            android:icon="@drawable/circle"
+            android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
+            <meta-data
+                android:name="android.support.wearable.complications.SUPPORTED_TYPES"
+                android:value="SHORT_TEXT,LONG_TEXT"/>
+            <meta-data
+                android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
+                android:value="0"/>
+            <intent-filter>
+                <action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST"/>
+            </intent-filter>
+        </service>
+
+        <service
+            android:name=".SynchronousProviderService"
+            android:label="@string/synchronous_provider_name"
+            android:exported="true"
+            android:icon="@drawable/circle"
+            android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
+            <meta-data
+                android:name="android.support.wearable.complications.SUPPORTED_TYPES"
+                android:value="SHORT_TEXT,LONG_TEXT"/>
+            <meta-data
+                android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
+                android:value="10000"/>
+            <intent-filter>
+                <action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/wear/wear-complications-data/samples/src/main/java/androidx/wear/complications/samples/AsynchronousProviderService.kt b/wear/wear-complications-data/samples/src/main/java/androidx/wear/complications/samples/AsynchronousProviderService.kt
new file mode 100644
index 0000000..3dcfb50
--- /dev/null
+++ b/wear/wear-complications-data/samples/src/main/java/androidx/wear/complications/samples/AsynchronousProviderService.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.wear.complications.samples
+
+import android.support.wearable.complications.ComplicationData
+import android.support.wearable.complications.ComplicationText
+import androidx.wear.complications.ComplicationProviderService
+import java.util.concurrent.Executors
+
+/** A minimal complication provider which reports the ID of the complication asynchronously. */
+class AsynchronousProviderService : ComplicationProviderService() {
+    val executor = Executors.newFixedThreadPool(5)
+
+    override fun onComplicationUpdate(
+        complicationId: Int,
+        type: Int,
+        provideMockData: Boolean,
+        callback: ComplicationUpdateCallback
+    ) {
+        executor.execute {
+            callback.onUpdateComplication(
+                when (type) {
+                    ComplicationData.TYPE_SHORT_TEXT ->
+                        ComplicationData.Builder(type)
+                            .setShortText(ComplicationText.plainText("# $complicationId"))
+                            .build()
+
+                    ComplicationData.TYPE_LONG_TEXT ->
+                        ComplicationData.Builder(type)
+                            .setLongText(ComplicationText.plainText("hello $complicationId"))
+                            .build()
+
+                    else -> null
+                }
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-complications-data/samples/src/main/java/androidx/wear/complications/samples/BackgroundProviderService.kt b/wear/wear-complications-data/samples/src/main/java/androidx/wear/complications/samples/BackgroundProviderService.kt
new file mode 100644
index 0000000..afe8eab
--- /dev/null
+++ b/wear/wear-complications-data/samples/src/main/java/androidx/wear/complications/samples/BackgroundProviderService.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.complications.samples
+
+import android.content.ComponentName
+import android.os.Handler
+import android.os.Looper
+import android.support.wearable.complications.ComplicationData
+import android.support.wearable.complications.ComplicationText
+import androidx.wear.complications.ComplicationProviderService
+import androidx.wear.complications.ProviderUpdateRequester
+
+const val UPDATE_CADEANCE_MS = 10000L
+
+var counter = 0
+var updateRequester: ProviderUpdateRequester? = null
+
+/** Example where we push updates to a counter every 10 seconds. */
+class BackgroundProviderService : ComplicationProviderService() {
+
+    private val handler = Handler(Looper.getMainLooper())
+
+    override fun onCreate() {
+        if (updateRequester == null) {
+            updateRequester = ProviderUpdateRequester(
+                this,
+                ComponentName(this, BackgroundProviderService::class.java)
+            )
+        }
+    }
+
+    override fun onComplicationActivated(complicationId: Int, type: Int) {
+        // Start requesting background updates.
+        backgroundUpdate()
+    }
+
+    private fun backgroundUpdate() {
+        counter++
+        updateRequester?.requestUpdateAll()
+        handler.postDelayed(this::backgroundUpdate, UPDATE_CADEANCE_MS)
+    }
+
+    override fun onComplicationUpdate(
+        complicationId: Int,
+        type: Int,
+        provideMockData: Boolean,
+        callback: ComplicationUpdateCallback
+    ) {
+        callback.onUpdateComplication(
+            when (type) {
+                ComplicationData.TYPE_SHORT_TEXT ->
+                    ComplicationData.Builder(type)
+                        .setShortText(ComplicationText.plainText("# $counter"))
+                        .build()
+
+                ComplicationData.TYPE_LONG_TEXT ->
+                    ComplicationData.Builder(type)
+                        .setLongText(ComplicationText.plainText("Count $counter"))
+                        .build()
+
+                else -> null
+            }
+        )
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-complications-data/samples/src/main/java/androidx/wear/complications/samples/SynchronousProviderService.kt b/wear/wear-complications-data/samples/src/main/java/androidx/wear/complications/samples/SynchronousProviderService.kt
new file mode 100644
index 0000000..bba72ec
--- /dev/null
+++ b/wear/wear-complications-data/samples/src/main/java/androidx/wear/complications/samples/SynchronousProviderService.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.wear.complications.samples
+
+import android.support.wearable.complications.ComplicationData
+import android.support.wearable.complications.ComplicationText
+import androidx.wear.complications.ComplicationProviderService
+
+/** A minimal complication provider which reports the ID of the complication immediately. */
+class SynchronousProviderService : ComplicationProviderService() {
+
+    override fun onComplicationUpdate(
+        complicationId: Int,
+        type: Int,
+        provideMockData: Boolean,
+        callback: ComplicationUpdateCallback
+    ) {
+        callback.onUpdateComplication(
+            when (type) {
+                ComplicationData.TYPE_SHORT_TEXT ->
+                    ComplicationData.Builder(type)
+                        .setShortText(ComplicationText.plainText("# $complicationId"))
+                        .build()
+
+                ComplicationData.TYPE_LONG_TEXT ->
+                    ComplicationData.Builder(type)
+                        .setLongText(ComplicationText.plainText("hello $complicationId"))
+                        .build()
+
+                else -> null
+            }
+        )
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-complications-data/samples/src/main/res/drawable/circle.xml b/wear/wear-complications-data/samples/src/main/res/drawable/circle.xml
new file mode 100644
index 0000000..d5dc5fa
--- /dev/null
+++ b/wear/wear-complications-data/samples/src/main/res/drawable/circle.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid />
+</shape>
\ No newline at end of file
diff --git a/wear/wear-complications-data/samples/src/main/res/values/strings.xml b/wear/wear-complications-data/samples/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b9290d8
--- /dev/null
+++ b/wear/wear-complications-data/samples/src/main/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <string name="app_name" translatable="false">Example Complication Provider</string>
+    <string name="asynchronous_provider_name" translatable="false">Asynchronous</string>
+    <string name="background_provider_name" translatable="false">Background</string>
+    <string name="synchronous_provider_name" translatable="false">Synchronous</string>
+</resources>
diff --git a/wear/wear-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java b/wear/wear-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
index 886ef20..68c933b 100644
--- a/wear/wear-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
+++ b/wear/wear-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
@@ -31,7 +31,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.wear.complications.ComplicationHelperActivity;
-import androidx.wear.complications.ComplicationManager;
 import androidx.wear.complications.ComplicationProviderService;
 
 import java.lang.annotation.Retention;
@@ -41,10 +40,10 @@
  * Container for complication data of all types.
  *
  * <p>A {@link ComplicationProviderService} should create instances of this class using {@link
- * ComplicationData.Builder} and send them to the complication system by calling {@link
- * ComplicationManager#updateComplicationData}. Depending on the type of complication data, some
- * fields will be required and some will be optional - see the documentation for each type, and for
- * the builder's set methods, for details.
+ * ComplicationData.Builder} and send them to the complication system in response to {@link
+ * ComplicationProviderService#onComplicationUpdate}. Depending on the type of complication data,
+ * some fields will be required and some will be optional - see the documentation for each type, and
+ * for the builder's set methods, for details.
  *
  * <p>A watch face will receive instances of this class as long as providers are configured.
  *
@@ -821,14 +820,17 @@
     }
 
     /**
-     * Returns the start time for this complication data, this may be 0.
+     * Returns the start time for this complication data (i.e. the first time at which it should
+     * be considered active and displayed), this may be 0. See also {@link #isActiveAt(long)}.
      */
     public long getStartDateTimeMillis() {
         return mFields.getLong(FIELD_START_TIME, 0);
     }
 
     /**
-     * Returns the end time for this complication data, this may be {@link Long#MAX_VALUE}.
+     * Returns the end time for this complication data (i.e. the last time at which it should be
+     * considered active and displayed), this may be {@link Long#MAX_VALUE}. See also {@link
+     * #isActiveAt(long)}.
      */
     public long getEndDateTimeMillis() {
         return mFields.getLong(FIELD_END_TIME, Long.MAX_VALUE);
@@ -992,7 +994,9 @@
 
         /**
          * Sets the <i>value</i> field. This is required for the {@link #TYPE_RANGED_VALUE} type,
-         * and is not valid for any other type.
+         * and is not valid for any other type. A {@link #TYPE_RANGED_VALUE} complication
+         * visually presents a single value, which is usually a percentage. E.g. you
+         * have completed 70% of today's target of 10000 steps.
          *
          * <p>Returns this Builder to allow chaining.
          *
@@ -1006,7 +1010,9 @@
 
         /**
          * Sets the <i>min value</i> field. This is required for the {@link #TYPE_RANGED_VALUE}
-         * type, and is not valid for any other type.
+         * type, and is not valid for any other type. A {@link #TYPE_RANGED_VALUE} complication
+         * visually presents a single value, which is usually a percentage. E.g. you have
+         * completed 70% of today's target of 10000 steps.
          *
          * <p>Returns this Builder to allow chaining.
          *
@@ -1020,7 +1026,9 @@
 
         /**
          * Sets the <i>max value</i> field. This is required for the {@link #TYPE_RANGED_VALUE}
-         * type, and is not valid for any other type.
+         * type, and is not valid for any other type.A {@link #TYPE_RANGED_VALUE} complication
+         * visually presents a single value, which is usually a percentage. E.g. you have
+         * completed 70% of today's target of 10000 steps.
          *
          * <p>Returns this Builder to allow chaining.
          *
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationManager.java b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationManager.java
deleted file mode 100644
index c0ecee3..0000000
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationManager.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.complications;
-
-import android.os.RemoteException;
-import android.support.wearable.complications.ComplicationData;
-import android.support.wearable.complications.IComplicationManager;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-/**
- * Allows providers to interact with the complications system.
- *
- * <p>Providers will receive an instance of this class in calls to {@link
- * ComplicationProviderService#onComplicationActivated onComplicationActivated} and {@link
- * ComplicationProviderService#onComplicationUpdate onComplicationUpdate}.
- */
-public class ComplicationManager {
-
-    private static final String TAG = "ComplicationManager";
-
-    @NonNull private final IComplicationManager mService;
-
-    /** @hide */
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public ComplicationManager(@NonNull IComplicationManager service) {
-        mService = service;
-    }
-
-    /**
-     * Updates complication data for the given {@code complicationId}. The type of data provided
-     * must either match the configured type for the given complication, or should be of {@link
-     * ComplicationData#TYPE_NO_DATA TYPE_NO_DATA}.
-     *
-     * <p>If no update is required (meaning that the complication will continue to display the
-     * previous value, which may be TYPE_NO_DATA if no data has ever been sent), then null may be
-     * passed as the data ({@link #noUpdateRequired} may be called to have the same effect).
-     *
-     * <p>A provider service should always call either this method or {@link #noUpdateRequired} in
-     * response to an update request from the system.
-     *
-     * <p>This method is often called in response to {@link
-     * ComplicationProviderService#onComplicationUpdate} but it can also be called when you have new
-     * data to push.
-     *
-     * <p> If {@link ComplicationProviderService#inRetailMode} is true then representative mock data
-     * should be returned rather than the real data.
-     *
-     * @param complicationId The ID of the complication to update
-     * @param data The {@link ComplicationData} to send to the watch face
-     * @throws RemoteException If it failed to send complication data
-     */
-    public void updateComplicationData(int complicationId, @NonNull ComplicationData data)
-            throws RemoteException {
-        if (data.getType() == ComplicationData.TYPE_NOT_CONFIGURED
-                || data.getType() == ComplicationData.TYPE_EMPTY) {
-            throw new IllegalArgumentException(
-                    "Cannot send data of TYPE_NOT_CONFIGURED or "
-                            + "TYPE_EMPTY. Use TYPE_NO_DATA instead.");
-        }
-        mService.updateComplicationData(complicationId, data);
-    }
-
-    /**
-     * Informs the system that no update is required after an update request. This means that the
-     * complication will continue to display the previous value, which may be TYPE_NO_DATA if no
-     * data has ever been sent.
-     *
-     * <p>A provider service should always call either this method or {@link
-     * #updateComplicationData} in response to an update request from the system.
-     *
-     * @param complicationId The ID of the complication
-     * @throws RemoteException If it failed to send complication data
-     */
-    public void noUpdateRequired(int complicationId) throws RemoteException {
-        mService.updateComplicationData(complicationId, null);
-    }
-}
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationProviderService.java b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationProviderService.java
index b2e0ab4..e738810 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationProviderService.java
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationProviderService.java
@@ -16,6 +16,8 @@
 
 package androidx.wear.complications;
 
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Service;
@@ -25,6 +27,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.support.wearable.complications.ComplicationData;
 import android.support.wearable.complications.ComplicationProviderInfo;
 import android.support.wearable.complications.IComplicationManager;
@@ -33,6 +36,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.annotation.UiThread;
+import androidx.annotation.VisibleForTesting;
 
 /**
  * Class for providers of complication data.
@@ -265,6 +270,36 @@
     private IComplicationProviderWrapper mWrapper;
     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
 
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    interface RetailModeProvider {
+        /**
+         * Returns true if the device is currently running in retail mode (e.g. the watch is being
+         * demonstrated in a store, or the watch face is being configured by the system UI).
+         */
+        boolean inRetailMode();
+    }
+
+    private RetailModeProvider mRetailModeProvider = new RetailModeProvider() {
+        /**
+         * Returns true if the device is currently running in retail mode (e.g. the watch is being
+         * demonstrated in a store, or the watch face is being configured by the system UI).
+         */
+        @Override
+        public boolean inRetailMode() {
+            ComponentName component = new ComponentName(RETAIL_PACKAGE, RETAIL_CLASS);
+            return (getPackageManager().getComponentEnabledSetting(component)
+                    == PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        }
+    };
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @VisibleForTesting
+    public void setRetailModeProvider(@NonNull RetailModeProvider retailModeProvider) {
+        mRetailModeProvider = retailModeProvider;
+    }
+
     @SuppressLint("SyntheticAccessor")
     @Override
     @Nullable
@@ -284,35 +319,53 @@
      * <p>This occurs when the watch face calls setActiveComplications, or when this provider is
      * chosen for a complication which is already active.
      *
-     * <p>Once this has been called, complication data may be sent for the given {@code
-     * complicationId}, until {@link #onComplicationDeactivated} is called for that id.
-     *
      * <p>This will usually be followed by a call to {@link #onComplicationUpdate}.
      *
      * <p>This will be called on the main thread.
      */
-    public void onComplicationActivated(
-            int complicationId, int type, @NonNull ComplicationManager manager) {
+    @UiThread
+    public void onComplicationActivated(int complicationId, int type) {
     }
 
     /**
      * Called when a complication data update is requested for the given complication id.
      *
-     * <p>In response to this request, {@link ComplicationManager#updateComplicationData} should be
-     * called on the provided {@link ComplicationManager} instance with the data to be displayed.
-     * Or, if no update is needed, {@link ComplicationManager#noUpdateRequired} may be called
-     * instead. One of these methods must be called so that the system knows when the provider has
-     * finished responding to the request.
+     * <p>In response to this request the result callback should be called with the data to be
+     * displayed. If the request can not be fulfilled or no update is needed then null should be
+     * passed to the callback.
      *
-     * <p>This call does not need to happen from within this method, but it should be made
-     * reasonably soon after the call to this method occurred. If a call does not occur within
-     * around 20 seconds (exact timeout length subject to change), then the system will unbind from
-     * this service which may cause your eventual update to not be received.
+     * <p>If provideMockData is true then representative mock data should be returned rather than
+     * the real data. E.g. A date could always be August 1st.
      *
-     * <p>This will be called on the main thread.
+     * <p>The callback doesn't have be called within onComplicationUpdate but it should be called
+     * soon after. If this does not occur within around 20 seconds (exact timeout length subject
+     * to change), then the system will unbind from this service which may cause your eventual
+     * update to not be received.
+     *
+     * @param complicationId  The id of the requested complication. Note this ID is distinct from
+     *                        ids
+     *                        used by the watch face itself.
+     * @param type            The type of complication data requested.
+     * @param provideMockData Whether or not mock or real data should be returned.
+     * @param resultCallback  The callback to pass the result to the system.
      */
+    @UiThread
     public abstract void onComplicationUpdate(
-            int complicationId, int type, @NonNull ComplicationManager manager);
+            int complicationId,
+            @ComplicationData.ComplicationType int type,
+            boolean provideMockData,
+            @NonNull ComplicationUpdateCallback resultCallback);
+
+    /** Callback for {@link #onComplicationUpdate}. */
+    public interface ComplicationUpdateCallback {
+        /**
+         * Sends the complicationData to the system. If null is passed then any
+         * previous complication data will not be overwritten. Can be called on any thread. Should
+         * only be called once.
+         */
+        void onUpdateComplication(@Nullable ComplicationData complicationData)
+                throws RemoteException;
+    }
 
     /**
      * Called when a complication is deactivated.
@@ -321,34 +374,37 @@
      * setActiveComplications and does not include the given complication (usually because the watch
      * face has stopped displaying it).
      *
-     * <p>Once this has been called, no complication data should be sent for the given {@code
-     * complicationId}, until {@link #onComplicationActivated} is called again for that id.
-     *
      * <p>This will be called on the main thread.
      */
+    @UiThread
     public void onComplicationDeactivated(int complicationId) {
     }
 
-    /**
-     * Returns true if the device is currently running in retail mode (e.g. the watch is being
-     * demonstrated in a store, or the watch face is being configured by the system UI). If it's in
-     * retail mode then representative mock data should be returned via
-     * {@link ComplicationManager#updateComplicationData}.
-     */
-    protected boolean inRetailMode() {
-        ComponentName component = new ComponentName(RETAIL_PACKAGE, RETAIL_CLASS);
-        return (getPackageManager().getComponentEnabledSetting(component)
-                == PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
-    }
-
     private class IComplicationProviderWrapper extends IComplicationProvider.Stub {
         @SuppressLint("SyntheticAccessor")
         @Override
         public void onUpdate(final int complicationId, final int type, IBinder manager) {
-            final ComplicationManager complicationManager =
-                    new ComplicationManager(IComplicationManager.Stub.asInterface(manager));
+            final IComplicationManager iComplicationManager =
+                    IComplicationManager.Stub.asInterface(manager);
+
             mMainThreadHandler.post(
-                    () -> onComplicationUpdate(complicationId, type, complicationManager));
+                    () -> onComplicationUpdate(complicationId, type,
+                            mRetailModeProvider.inRetailMode(),
+                            complicationData -> {
+                                // This can be run on an arbitrary thread, but that's OK.
+                                int dataType =
+                                        complicationData != null ? complicationData.getType() :
+                                                ComplicationData.TYPE_NO_DATA;
+                                if (dataType == ComplicationData.TYPE_NOT_CONFIGURED
+                                        || dataType == ComplicationData.TYPE_EMPTY) {
+                                    throw new IllegalArgumentException(
+                                            "Cannot send data of TYPE_NOT_CONFIGURED or "
+                                                    + "TYPE_EMPTY. Use TYPE_NO_DATA instead.");
+                                }
+
+                                iComplicationManager.updateComplicationData(
+                                        complicationId, complicationData);
+                            }));
         }
 
         @Override
@@ -363,11 +419,9 @@
         @SuppressLint("SyntheticAccessor")
         public void onComplicationActivated(
                 final int complicationId, final int type, IBinder manager) {
-            final ComplicationManager complicationManager =
-                    new ComplicationManager(IComplicationManager.Stub.asInterface(manager));
             mMainThreadHandler.post(
                     () -> ComplicationProviderService.this.onComplicationActivated(
-                            complicationId, type, complicationManager));
+                            complicationId, type));
         }
     }
 }
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderUpdateRequester.java b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderUpdateRequester.java
index 203c1e5..31303bd 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderUpdateRequester.java
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderUpdateRequester.java
@@ -34,7 +34,9 @@
     /** The package of the service that accepts provider requests. */
     private static final String UPDATE_REQUEST_RECEIVER_PACKAGE = "com.google.android.wearable.app";
 
-    private static final String ACTION_REQUEST_UPDATE =
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final String ACTION_REQUEST_UPDATE =
             "android.support.wearable.complications.ACTION_REQUEST_UPDATE";
 
     /** @hide */
diff --git a/wear/wear-complications-data/src/test/java/androidx/wear/complications/ComplicationManagerTest.java b/wear/wear-complications-data/src/test/java/androidx/wear/complications/ComplicationManagerTest.java
deleted file mode 100644
index e5c7d4f..0000000
--- a/wear/wear-complications-data/src/test/java/androidx/wear/complications/ComplicationManagerTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.complications;
-
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-
-import android.os.RemoteException;
-import android.support.wearable.complications.ComplicationData;
-import android.support.wearable.complications.ComplicationText;
-import android.support.wearable.complications.IComplicationManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-/** Tests for {@link ComplicationManager}. */
-@RunWith(ComplicationsTestRunner.class)
-@DoNotInstrument
-public class ComplicationManagerTest {
-
-    @Mock private IComplicationManager mRemoteManager;
-    private ComplicationManager mManagerUnderTest;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mManagerUnderTest = new ComplicationManager(mRemoteManager);
-    }
-
-    @Test
-    public void testUpdateComplicationData() throws RemoteException {
-        // GIVEN a complication id and a ComplicationData object...
-        int id = 5;
-        ComplicationData data =
-                new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
-                        .setShortText(ComplicationText.plainText("hello"))
-                        .build();
-
-        // WHEN updateComplicationManager is called with that id and data...
-        mManagerUnderTest.updateComplicationData(id, data);
-        try {
-            // THEN updateComplicationData is called on the wrapped IComplicationManager instance
-            // for the same id and with the expected data.
-            verify(mRemoteManager).updateComplicationData(eq(id), eq(data));
-        } catch (RemoteException e) {
-            fail("RemoteException");
-        }
-    }
-}
diff --git a/wear/wear-complications-data/src/test/java/androidx/wear/complications/ComplicationProviderServiceTest.java b/wear/wear-complications-data/src/test/java/androidx/wear/complications/ComplicationProviderServiceTest.java
new file mode 100644
index 0000000..8667c45
--- /dev/null
+++ b/wear/wear-complications-data/src/test/java/androidx/wear/complications/ComplicationProviderServiceTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.wear.complications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.os.RemoteException;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.support.wearable.complications.IComplicationManager;
+import android.support.wearable.complications.IComplicationProvider;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadows.ShadowLooper;
+
+/** Tests for {@link ComplicationProviderService}. */
+@RunWith(ComplicationsTestRunner.class)
+@DoNotInstrument
+public class ComplicationProviderServiceTest {
+
+    private static final String TAG = "ComplicationProviderServiceTest";
+
+    @Mock
+    private IComplicationManager mRemoteManager;
+    private IComplicationManager.Stub mLocalManager = new IComplicationManager.Stub() {
+        @Override
+        public void updateComplicationData(int complicationId, ComplicationData data)
+                throws RemoteException {
+            mRemoteManager.updateComplicationData(complicationId, data);
+        }
+    };
+
+    private IComplicationProvider.Stub mComplicationProvider;
+
+    private ComplicationProviderService mTestService = new ComplicationProviderService() {
+        private CharSequence mText = "Hello";
+
+        @Override
+        public void onComplicationUpdate(int complicationId, int type,
+                boolean provideMockData, ComplicationUpdateCallback callback) {
+            try {
+                callback.onUpdateComplication(
+                        new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
+                                .setLongText(ComplicationText.plainText("hello " + complicationId))
+                                .build());
+            } catch (RemoteException e) {
+                Log.e(TAG, "onComplicationUpdate failed with error: ", e);
+            }
+        }
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mComplicationProvider =
+                (IComplicationProvider.Stub) mTestService.onBind(
+                        new Intent(ComplicationProviderService.ACTION_COMPLICATION_UPDATE_REQUEST));
+        mTestService.setRetailModeProvider(() -> false);
+    }
+
+    @Test
+    public void testOnComplicationUpdate() throws Exception {
+        int id = 123;
+        mComplicationProvider.onUpdate(id, ComplicationData.TYPE_LONG_TEXT, mLocalManager);
+        ShadowLooper.runUiThreadTasks();
+        ArgumentCaptor<ComplicationData> data = ArgumentCaptor.forClass(ComplicationData.class);
+        verify(mRemoteManager).updateComplicationData(eq(id), data.capture());
+        assertThat(data.getValue().getLongText().getTextAt(null, 0)).isEqualTo(
+                "hello " + id
+        );
+    }
+}
diff --git a/wear/wear-complications-rendering/src/test/java/androidx/wear/complications/rendering/ComplicationDrawableTest.java b/wear/wear-complications-rendering/src/test/java/androidx/wear/complications/rendering/ComplicationDrawableTest.java
index 0da01ad..8e63b02 100644
--- a/wear/wear-complications-rendering/src/test/java/androidx/wear/complications/rendering/ComplicationDrawableTest.java
+++ b/wear/wear-complications-rendering/src/test/java/androidx/wear/complications/rendering/ComplicationDrawableTest.java
@@ -58,7 +58,7 @@
 import androidx.wear.watchface.WatchFaceService;
 import androidx.wear.watchface.WatchFaceType;
 import androidx.wear.watchface.WatchState;
-import androidx.wear.watchface.style.UserStyleManager;
+import androidx.wear.watchface.style.UserStyleRepository;
 
 import org.jetbrains.annotations.NotNull;
 import org.junit.Before;
@@ -702,13 +702,13 @@
                 @NotNull SurfaceHolder surfaceHolder,
                 @NotNull WatchFaceHost watchFaceHost,
                 @NotNull WatchState watchState) {
-            UserStyleManager styleManager = new UserStyleManager(new ArrayList<>());
+            UserStyleRepository userStyleRepository = new UserStyleRepository(new ArrayList<>());
             return new WatchFace.Builder(
                     WatchFaceType.ANALOG,
                     100,
-                    styleManager,
+                    userStyleRepository,
                     new ComplicationsHolder(new ArrayList<>()),
-                    new Renderer(surfaceHolder, styleManager, watchState) {
+                    new Renderer(surfaceHolder, userStyleRepository, watchState) {
                         @NotNull
                         @Override
                         public Bitmap takeScreenshot$wear_watchface_debug(
diff --git a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleCategory.kt b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleCategory.kt
index 1ac9f9f..3515775 100644
--- a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleCategory.kt
+++ b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleCategory.kt
@@ -90,7 +90,7 @@
         bundle.getString(KEY_DISPLAY_NAME)!!,
         bundle.getString(KEY_DESCRIPTION)!!,
         bundle.getParcelable(KEY_ICON),
-        UserStyleManager.readOptionsListFromBundle(bundle),
+        UserStyleRepository.readOptionsListFromBundle(bundle),
         Option.createFromBundle(bundle.getBundle(KEY_DEFAULT_OPTION)!!)
     )
 
@@ -105,7 +105,7 @@
             KEY_DEFAULT_OPTION,
             Bundle().apply { defaultOption.writeToBundle(this) }
         )
-        UserStyleManager.writeOptionListToBundle(options, bundle)
+        UserStyleRepository.writeOptionListToBundle(options, bundle)
     }
 
     /**
diff --git a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleManager.kt b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleRepository.kt
similarity index 99%
rename from wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleManager.kt
rename to wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleRepository.kt
index 7fdf822..3d3372b 100644
--- a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleManager.kt
+++ b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleRepository.kt
@@ -24,7 +24,7 @@
  * In memory storage for user style choices which allows listeners to be registered to observe
  * style changes.
  */
-class UserStyleManager(
+class UserStyleRepository(
     /**
      * The style categories (i.e the style schema) associated with this watch face, that the user
      * can configure. May be empty. The first entry in each Option list is that category's default
diff --git a/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/UserStyleManagerTest.kt b/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/UserStyleRepositoryTest.kt
similarity index 82%
rename from wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/UserStyleManagerTest.kt
rename to wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/UserStyleRepositoryTest.kt
index e588a15..63672f9 100644
--- a/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/UserStyleManagerTest.kt
+++ b/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/UserStyleRepositoryTest.kt
@@ -36,7 +36,7 @@
 }
 
 @RunWith(UserStyleManagerTestRunner::class)
-class UserStyleManagerTest {
+class UserStyleRepositoryTest {
     private val redStyleOption =
         ListUserStyleCategory.ListOption("red_style", "Red", icon = null)
 
@@ -76,11 +76,12 @@
         watchHandStyleList
     )
 
-    private val mockListener1 = Mockito.mock(UserStyleManager.UserStyleListener::class.java)
-    private val mockListener2 = Mockito.mock(UserStyleManager.UserStyleListener::class.java)
-    private val mockListener3 = Mockito.mock(UserStyleManager.UserStyleListener::class.java)
+    private val mockListener1 = Mockito.mock(UserStyleRepository.UserStyleListener::class.java)
+    private val mockListener2 = Mockito.mock(UserStyleRepository.UserStyleListener::class.java)
+    private val mockListener3 = Mockito.mock(UserStyleRepository.UserStyleListener::class.java)
 
-    private val styleManager = UserStyleManager(listOf(colorStyleCategory, watchHandStyleCategory))
+    private val userStyleRepository =
+        UserStyleRepository(listOf(colorStyleCategory, watchHandStyleCategory))
 
     private val icon1 = Icon.createWithContentUri("icon1")
     private val icon2 = Icon.createWithContentUri("icon2")
@@ -93,19 +94,19 @@
 
     @Test
     fun addUserStyleListener_firesImmediately() {
-        styleManager.addUserStyleListener(mockListener1)
-        Mockito.verify(mockListener1).onUserStyleChanged(styleManager.userStyle)
+        userStyleRepository.addUserStyleListener(mockListener1)
+        Mockito.verify(mockListener1).onUserStyleChanged(userStyleRepository.userStyle)
     }
 
     @Test
     fun assigning_userStyle_firesListeners() {
-        styleManager.addUserStyleListener(mockListener1)
-        styleManager.addUserStyleListener(mockListener2)
-        styleManager.addUserStyleListener(mockListener3)
+        userStyleRepository.addUserStyleListener(mockListener1)
+        userStyleRepository.addUserStyleListener(mockListener2)
+        userStyleRepository.addUserStyleListener(mockListener3)
 
-        Mockito.verify(mockListener1).onUserStyleChanged(styleManager.userStyle)
-        Mockito.verify(mockListener2).onUserStyleChanged(styleManager.userStyle)
-        Mockito.verify(mockListener3).onUserStyleChanged(styleManager.userStyle)
+        Mockito.verify(mockListener1).onUserStyleChanged(userStyleRepository.userStyle)
+        Mockito.verify(mockListener2).onUserStyleChanged(userStyleRepository.userStyle)
+        Mockito.verify(mockListener3).onUserStyleChanged(userStyleRepository.userStyle)
 
         val newStyle: Map<UserStyleCategory, UserStyleCategory.Option> = mapOf(
             colorStyleCategory to greenStyleOption,
@@ -116,11 +117,11 @@
         Mockito.reset(mockListener2)
         Mockito.reset(mockListener3)
 
-        styleManager.userStyle = newStyle
+        userStyleRepository.userStyle = newStyle
 
-        Mockito.verify(mockListener1).onUserStyleChanged(styleManager.userStyle)
-        Mockito.verify(mockListener2).onUserStyleChanged(styleManager.userStyle)
-        Mockito.verify(mockListener3).onUserStyleChanged(styleManager.userStyle)
+        Mockito.verify(mockListener1).onUserStyleChanged(userStyleRepository.userStyle)
+        Mockito.verify(mockListener2).onUserStyleChanged(userStyleRepository.userStyle)
+        Mockito.verify(mockListener3).onUserStyleChanged(userStyleRepository.userStyle)
     }
 
     @Test
@@ -130,10 +131,10 @@
             watchHandStyleCategory to gothicStyleOption
         )
 
-        styleManager.userStyle = newStyle
+        userStyleRepository.userStyle = newStyle
 
-        assertThat(styleManager.userStyle[colorStyleCategory]).isEqualTo(greenStyleOption)
-        assertThat(styleManager.userStyle[watchHandStyleCategory])
+        assertThat(userStyleRepository.userStyle[colorStyleCategory]).isEqualTo(greenStyleOption)
+        assertThat(userStyleRepository.userStyle[watchHandStyleCategory])
             .isEqualTo(gothicStyleOption)
     }
 
@@ -172,9 +173,9 @@
     @Test
     fun bundleAndUnbundleOptionList() {
         val bundle = Bundle()
-        UserStyleManager.writeOptionListToBundle(listOf(option1, option2, option3), bundle)
+        UserStyleRepository.writeOptionListToBundle(listOf(option1, option2, option3), bundle)
 
-        val unbundled = UserStyleManager.readOptionsListFromBundle(bundle)
+        val unbundled = UserStyleRepository.readOptionsListFromBundle(bundle)
         val optionArray = unbundled.filterIsInstance<ListUserStyleCategory.ListOption>()
             .toTypedArray()
 
@@ -201,11 +202,11 @@
             "id2", "displayName2", "description2", categoryIcon2, listOf(option3, option4)
         )
 
-        val bundles = UserStyleManager.userStyleCategoriesToBundles(
+        val bundles = UserStyleRepository.userStyleCategoriesToBundles(
             listOf(styleCategory1, styleCategory2)
         )
 
-        val unbundled = UserStyleManager.bundlesToUserStyleCategoryList(bundles)
+        val unbundled = UserStyleRepository.bundlesToUserStyleCategoryList(bundles)
 
         assert(unbundled[0] is ListUserStyleCategory)
         assertThat(unbundled[0].id).isEqualTo("id1")
@@ -255,9 +256,9 @@
             styleCategory1 as UserStyleCategory to option2 as UserStyleCategory.Option,
             styleCategory2 as UserStyleCategory to option3 as UserStyleCategory.Option
         )
-        val bundle = UserStyleManager.styleMapToBundle(styleMap)
+        val bundle = UserStyleRepository.styleMapToBundle(styleMap)
 
-        val unbundled = UserStyleManager.bundleToStyleMap(bundle, schema)
+        val unbundled = UserStyleRepository.bundleToStyleMap(bundle, schema)
         assertThat(unbundled.size).isEqualTo(2)
         assertThat(unbundled[styleCategory1]!!.id).isEqualTo(option2.id)
         assertThat(unbundled[styleCategory2]!!.id).isEqualTo(option3.id)
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasWatchFaceService.kt
index 4ac16c6..37b30c4 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasWatchFaceService.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasWatchFaceService.kt
@@ -43,7 +43,7 @@
 import androidx.wear.watchface.style.DoubleRangeUserStyleCategory
 import androidx.wear.watchface.style.ListUserStyleCategory
 import androidx.wear.watchface.style.UserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 import kotlin.math.cos
 import kotlin.math.sin
 
@@ -108,7 +108,7 @@
                 1.0,
                 0.75
             )
-        val styleManager = UserStyleManager(
+        val userStyleRepository = UserStyleRepository(
             listOf(colorStyleCategory, drawHourPipsStyleCategory, watchHandLengthStyleCategory)
         )
         val complicationSlots = ComplicationsHolder(
@@ -147,7 +147,7 @@
             surfaceHolder,
             this,
             watchFaceStyle,
-            styleManager,
+            userStyleRepository,
             watchState,
             colorStyleCategory,
             drawHourPipsStyleCategory,
@@ -159,7 +159,7 @@
             WatchFaceType.ANALOG,
             /** mInteractiveUpdateRateMillis */
             16,
-            styleManager,
+            userStyleRepository,
             complicationSlots,
             renderer,
             watchFaceHost,
@@ -175,13 +175,13 @@
     surfaceHolder: SurfaceHolder,
     private val context: Context,
     private var watchFaceColorStyle: WatchFaceColorStyle,
-    userStyleManager: UserStyleManager,
+    userStyleRepository: UserStyleRepository,
     private val watchState: WatchState,
     private val colorStyleCategory: ListUserStyleCategory,
     private val drawPipsStyleCategory: BooleanUserStyleCategory,
     private val watchHandLengthStyleCategoryDouble: DoubleRangeUserStyleCategory,
     private val complicationsHolder: ComplicationsHolder
-) : CanvasRenderer(surfaceHolder, userStyleManager, watchState, CanvasType.HARDWARE) {
+) : CanvasRenderer(surfaceHolder, userStyleRepository, watchState, CanvasType.HARDWARE) {
 
     private val clockHandPaint = Paint().apply {
         isAntiAlias = true
@@ -225,8 +225,8 @@
     private var watchHandScale = 1.0f
 
     init {
-        userStyleManager.addUserStyleListener(
-            object : UserStyleManager.UserStyleListener {
+        userStyleRepository.addUserStyleListener(
+            object : UserStyleRepository.UserStyleListener {
                 @SuppressLint("SyntheticAccessor")
                 override fun onUserStyleChanged(
                     userStyle: Map<UserStyleCategory, UserStyleCategory.Option>
@@ -234,8 +234,9 @@
                     watchFaceColorStyle =
                         WatchFaceColorStyle.create(context, userStyle[colorStyleCategory]!!.id)
 
-                    // Apply the userStyle to the complications. ComplicationDrawables for each of the
-                    // styles are defined in XML so we need to replace the complication's drawables.
+                    // Apply the userStyle to the complications. ComplicationDrawables for each of
+                    // the styles are defined in XML so we need to replace the complication's
+                    // drawables.
                     for ((_, complication) in complicationsHolder.complications) {
                         complication.renderer =
                             watchFaceColorStyle.getComplicationDrawableRenderer(context, watchState)
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
index 499c6de..e658b9d 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
@@ -34,7 +34,7 @@
 import androidx.wear.watchface.WatchFaceType
 import androidx.wear.watchface.WatchState
 import androidx.wear.watchface.style.ListUserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 import java.nio.ByteBuffer
 import java.nio.ByteOrder
 import java.nio.FloatBuffer
@@ -85,18 +85,18 @@
                 )
             )
         )
-        val styleManager = UserStyleManager(listOf(colorStyleCategory))
+        val userStyleRepository = UserStyleRepository(listOf(colorStyleCategory))
         val complicationSlots = ComplicationsHolder(emptyList())
         val renderer = ExampleOpenGLRenderer(
             surfaceHolder,
-            styleManager,
+            userStyleRepository,
             watchState,
             colorStyleCategory
         )
         return WatchFace.Builder(
             WatchFaceType.ANALOG,
             FRAME_PERIOD_MS,
-            styleManager,
+            userStyleRepository,
             complicationSlots,
             renderer,
             watchFaceHost,
@@ -107,10 +107,10 @@
 
 class ExampleOpenGLRenderer(
     surfaceHolder: SurfaceHolder,
-    private val userStyleManager: UserStyleManager,
+    private val userStyleRepository: UserStyleRepository,
     watchState: WatchState,
     private val colorStyleCategory: ListUserStyleCategory
-) : Gles2Renderer(surfaceHolder, userStyleManager, watchState) {
+) : Gles2Renderer(surfaceHolder, userStyleRepository, watchState) {
 
     /** Projection transformation matrix. Converts from 3D to 2D.  */
     private val projectionMatrix = FloatArray(16)
@@ -445,7 +445,7 @@
             GLES20.glClearColor(0f, 0f, 0f, 1f)
             ambientVpMatrix
         } else {
-            when (userStyleManager.userStyle[colorStyleCategory]!!.id) {
+            when (userStyleRepository.userStyle[colorStyleCategory]!!.id) {
                 "red_style" -> GLES20.glClearColor(0.5f, 0.2f, 0.2f, 1f)
                 "green_style" -> GLES20.glClearColor(0.2f, 0.5f, 0.2f, 1f)
             }
@@ -491,7 +491,7 @@
                 modelMatrices[secIndex],
                 0
             )
-            secondHandTriangleMap[userStyleManager.userStyle[colorStyleCategory]!!.id]
+            secondHandTriangleMap[userStyleRepository.userStyle[colorStyleCategory]!!.id]
                 ?.draw(mvpMatrix)
         }
 
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/KDocExampleWatchFace.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/KDocExampleWatchFace.kt
index 813b5b4..df05369 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/KDocExampleWatchFace.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/KDocExampleWatchFace.kt
@@ -39,7 +39,7 @@
 import androidx.wear.watchface.WatchState
 import androidx.wear.watchface.style.ListUserStyleCategory
 import androidx.wear.watchface.style.UserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 
 @Sampled
 fun kDocCreateExampleWatchFaceService(): WatchFaceService {
@@ -50,7 +50,7 @@
             watchFaceHost: WatchFaceHost,
             watchState: WatchState
         ): WatchFace {
-            val styleManager = UserStyleManager(
+            val userStyleRepository = UserStyleRepository(
                 listOf(
                     ListUserStyleCategory(
                         "color_style_category",
@@ -137,19 +137,20 @@
 
             val renderer = object : CanvasRenderer(
                 surfaceHolder,
-                styleManager,
+                userStyleRepository,
                 watchState,
                 CanvasType.HARDWARE
             ) {
                 init {
-                    styleManager.addUserStyleListener(object : UserStyleManager.UserStyleListener {
-                        override fun onUserStyleChanged(
-                            userStyle: Map<UserStyleCategory, UserStyleCategory.Option>
-                        ) {
-                            // `userStyle` will contain two userStyle categories with options from
-                            // the lists above. ...
-                        }
-                    })
+                    userStyleRepository.addUserStyleListener(
+                        object : UserStyleRepository.UserStyleListener {
+                            override fun onUserStyleChanged(
+                                userStyle: Map<UserStyleCategory, UserStyleCategory.Option>
+                            ) {
+                                // `userStyle` will contain two userStyle categories with options
+                                // from the lists above. ...
+                            }
+                        })
                 }
 
                 override fun render(
@@ -164,7 +165,7 @@
             return WatchFace.Builder(
                 WatchFaceType.ANALOG,
                 /* interactiveUpdateRateMillis */ 16,
-                styleManager,
+                userStyleRepository,
                 complicationSlots,
                 renderer,
                 watchFaceHost,
diff --git a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasWatchFaceService.kt b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasWatchFaceService.kt
index 3ec41bd..2887919 100644
--- a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasWatchFaceService.kt
+++ b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasWatchFaceService.kt
@@ -39,7 +39,7 @@
 import androidx.wear.watchface.style.BooleanUserStyleCategory
 import androidx.wear.watchface.style.DoubleRangeUserStyleCategory
 import androidx.wear.watchface.style.ListUserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 
 /** A simple canvas test watch face for integration tests. */
 internal class TestCanvasWatchFaceService(
@@ -97,7 +97,7 @@
                 1.0,
                 1.0
             )
-        val styleManager = UserStyleManager(
+        val userStyleRepository = UserStyleRepository(
             listOf(colorStyleCategory, drawHourPipsStyleCategory, watchHandLengthStyleCategory)
         )
         val complicationSlots = ComplicationsHolder(
@@ -136,7 +136,7 @@
             surfaceHolder,
             this,
             watchFaceStyle,
-            styleManager,
+            userStyleRepository,
             watchState,
             colorStyleCategory,
             drawHourPipsStyleCategory,
@@ -147,7 +147,7 @@
         return WatchFace.Builder(
             WatchFaceType.ANALOG,
             16,
-            styleManager,
+            userStyleRepository,
             complicationSlots,
             renderer,
             // We want full control over when frames are produced.
diff --git a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/TestGles2WatchFaceService.kt b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/TestGles2WatchFaceService.kt
index c00c0e6..b6945db 100644
--- a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/TestGles2WatchFaceService.kt
+++ b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/TestGles2WatchFaceService.kt
@@ -30,7 +30,7 @@
 import androidx.wear.watchface.samples.ExampleOpenGLRenderer
 import androidx.wear.watchface.samples.R
 import androidx.wear.watchface.style.ListUserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 
 /** A simple OpenGL test watch face for integration tests. */
 internal class TestGles2WatchFaceService(
@@ -69,11 +69,11 @@
                 )
             )
         )
-        val styleManager = UserStyleManager(listOf(colorStyleCategory))
+        val userStyleRepository = UserStyleRepository(listOf(colorStyleCategory))
         val complicationSlots = ComplicationsHolder(emptyList())
         val renderer = ExampleOpenGLRenderer(
             surfaceHolder,
-            styleManager,
+            userStyleRepository,
             watchState,
             colorStyleCategory
         )
@@ -81,7 +81,7 @@
         return WatchFace.Builder(
             WatchFaceType.ANALOG,
             16,
-            styleManager,
+            userStyleRepository,
             complicationSlots,
             renderer,
             // We want full control over when frames are produced.
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/CanvasRenderer.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/CanvasRenderer.kt
index caf9661..be7f6ab 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/CanvasRenderer.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/CanvasRenderer.kt
@@ -24,7 +24,7 @@
 import android.view.SurfaceHolder
 import androidx.annotation.IntDef
 import androidx.annotation.UiThread
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 
 /** @hide */
 @IntDef(
@@ -51,15 +51,15 @@
     /** The {@link SurfaceHolder} that {@link onDraw} will draw into. */
     surfaceHolder: SurfaceHolder,
 
-    /** The associated {@link UserStyleManager}. */
-    userStyleManager: UserStyleManager,
+    /** The associated {@link UserStyleRepository}. */
+    userStyleRepository: UserStyleRepository,
 
     /** The associated {@link WatchState}. */
     watchState: WatchState,
 
     /** The type of canvas to use. */
     @CanvasType private val canvasType: Int
-) : Renderer(surfaceHolder, userStyleManager, watchState) {
+) : Renderer(surfaceHolder, userStyleRepository, watchState) {
 
     internal override fun renderInternal(
         calendar: Calendar
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
index 872747c..33f83bd 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
@@ -308,6 +308,7 @@
     private var _enabled = true
 
     var enabled: Boolean
+        @JvmName("isEnabled")
         @UiThread
         get() = _enabled
         @UiThread
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Gles2Renderer.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Gles2Renderer.kt
index 1f47904..aa543ba 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Gles2Renderer.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Gles2Renderer.kt
@@ -28,7 +28,7 @@
 import android.view.SurfaceHolder
 import androidx.annotation.CallSuper
 import androidx.annotation.UiThread
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 
 import java.nio.ByteBuffer
 
@@ -56,12 +56,12 @@
     /** The {@link SurfaceHolder} that {@link onDraw} will draw into. */
     surfaceHolder: SurfaceHolder,
 
-    /** The associated {@link UserStyleManager}. */
-    userStyleManager: UserStyleManager,
+    /** The associated {@link UserStyleRepository}. */
+    userStyleRepository: UserStyleRepository,
 
     /** The associated {@link WatchState}. */
     watchState: WatchState
-) : Renderer(surfaceHolder, userStyleManager, watchState) {
+) : Renderer(surfaceHolder, userStyleRepository, watchState) {
     private companion object {
         private const val TAG = "Gles2WatchFace"
     }
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt
index 8266444..f7f6461 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt
@@ -22,15 +22,15 @@
 import android.view.SurfaceHolder
 import androidx.annotation.CallSuper
 import androidx.annotation.UiThread
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 
 /** The base class for {@link CanvasRenderer} and {@link Gles2Renderer}. */
 abstract class Renderer(
     /** The {@link SurfaceHolder} that {@link onDraw} will draw into. */
     _surfaceHolder: SurfaceHolder,
 
-    /** The associated {@link UserStyleManager}. */
-    internal val userStyleManager: UserStyleManager,
+    /** The associated {@link UserStyleRepository}. */
+    internal val userStyleRepository: UserStyleRepository,
 
     /** The associated {@link WatchState}. */
     internal val watchState: WatchState
@@ -70,7 +70,7 @@
 
     /**
      * Renders the watch face into the {@link #surfaceHolder} using the current {@link #drawMode}
-     * with the user style specified by the {@link #userStyleManager}.
+     * with the user style specified by the {@link #userStyleRepository}.
      *
      * @param calendar The Calendar to use when rendering the watch face
      * @return A {@link Bitmap} containing a screenshot of the watch face
@@ -80,7 +80,7 @@
 
     /**
      * Renders the watch face into a Bitmap with the user style specified by the
-     * {@link #userStyleManager}.
+     * {@link #userStyleRepository}.
      *
      * @param calendar The Calendar to use when rendering the watch face
      * @param drawMode The {@link DrawMode} to use when rendering the watch face
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index abff28e..c5707bf 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -41,7 +41,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.wear.complications.SystemProviders
 import androidx.wear.watchface.style.UserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 import androidx.wear.watchface.ui.WatchFaceConfigActivity
 import androidx.wear.watchface.ui.WatchFaceConfigDelegate
 import java.io.FileNotFoundException
@@ -112,7 +112,7 @@
 class WatchFace private constructor(
     @WatchFaceType private val watchFaceType: Int,
     private var interactiveUpdateRateMillis: Long,
-    internal val userStyleManager: UserStyleManager,
+    internal val userStyleRepository: UserStyleRepository,
     internal var complicationsHolder: ComplicationsHolder,
     internal val renderer: Renderer,
     private val watchFaceHostApi: WatchFaceHostApi,
@@ -149,8 +149,8 @@
          */
         private var interactiveUpdateRateMillis: Long,
 
-        /** The {@UserStyleManager} for this WatchFace. */
-        internal val userStyleManager: UserStyleManager,
+        /** The {@UserStyleRepository} for this WatchFace. */
+        internal val userStyleRepository: UserStyleRepository,
 
         /** The {@link ComplicationsHolder} for this WatchFace. */
         internal var complicationsHolder: ComplicationsHolder,
@@ -273,7 +273,7 @@
             return WatchFace(
                 watchFaceType,
                 interactiveUpdateRateMillis,
-                userStyleManager,
+                userStyleRepository,
                 complicationsHolder,
                 renderer,
                 watchFaceHost.api!!,
@@ -415,33 +415,34 @@
         // If the system has a stored user style then Home/SysUI is in charge of style
         // persistence, otherwise we need to do our own.
         val storedUserStyle =
-            watchFaceHostApi.getStoredUserStyle(userStyleManager.userStyleCategories)
+            watchFaceHostApi.getStoredUserStyle(userStyleRepository.userStyleCategories)
         if (storedUserStyle != null) {
-            userStyleManager.userStyle = storedUserStyle
+            userStyleRepository.userStyle = storedUserStyle
         } else {
             // The system doesn't support preference persistence we need to do it ourselves.
             val preferencesFile =
                 "watchface_prefs_${watchFaceHostApi.getContext().javaClass.typeName}.txt"
 
-            userStyleManager.userStyle = UserStyleManager.idMapToStyleMap(
+            userStyleRepository.userStyle = UserStyleRepository.idMapToStyleMap(
                 readPrefs(watchFaceHostApi.getContext(), preferencesFile),
-                userStyleManager.userStyleCategories
+                userStyleRepository.userStyleCategories
             )
 
-            userStyleManager.addUserStyleListener(object : UserStyleManager.UserStyleListener {
-                @SuppressLint("SyntheticAccessor")
-                override fun onUserStyleChanged(
-                    userStyle: Map<UserStyleCategory, UserStyleCategory.Option>
-                ) {
-                    writePrefs(watchFaceHostApi.getContext(), preferencesFile, userStyle)
-                }
-            })
+            userStyleRepository.addUserStyleListener(
+                object : UserStyleRepository.UserStyleListener {
+                    @SuppressLint("SyntheticAccessor")
+                    override fun onUserStyleChanged(
+                        userStyle: Map<UserStyleCategory, UserStyleCategory.Option>
+                    ) {
+                        writePrefs(watchFaceHostApi.getContext(), preferencesFile, userStyle)
+                    }
+                })
         }
     }
 
     private var inOnSetStyle = false
 
-    private inner class WfUserStyleListener : UserStyleManager.UserStyleListener {
+    private inner class WfUserStyleListener : UserStyleRepository.UserStyleListener {
         @SuppressWarnings("SyntheticAccessor")
         override fun onUserStyleChanged(
             userStyle: Map<UserStyleCategory, UserStyleCategory.Option>
@@ -523,16 +524,19 @@
 
         WatchFaceConfigActivity.registerWatchFace(componentName, object : WatchFaceConfigDelegate {
             override fun getUserStyleSchema() =
-                UserStyleManager.userStyleCategoriesToBundles(
-                    userStyleManager.userStyleCategories
+                UserStyleRepository.userStyleCategoriesToBundles(
+                    userStyleRepository.userStyleCategories
                 )
 
             override fun getUserStyle() =
-                UserStyleManager.styleMapToBundle(userStyleManager.userStyle)
+                UserStyleRepository.styleMapToBundle(userStyleRepository.userStyle)
 
             override fun setUserStyle(style: Bundle) {
-                userStyleManager.userStyle =
-                    UserStyleManager.bundleToStyleMap(style, userStyleManager.userStyleCategories)
+                userStyleRepository.userStyle =
+                    UserStyleRepository.bundleToStyleMap(
+                        style,
+                        userStyleRepository.userStyleCategories
+                    )
             }
 
             override fun getBackgroundComplicationId() =
@@ -566,11 +570,11 @@
         })
 
         watchFaceHostApi.registerWatchFaceType(watchFaceType)
-        watchFaceHostApi.registerUserStyleSchema(userStyleManager.userStyleCategories)
+        watchFaceHostApi.registerUserStyleSchema(userStyleRepository.userStyleCategories)
 
         watchState.addListener(systemStateListener)
-        userStyleManager.addUserStyleListener(styleListener)
-        sendCurrentUserStyle(userStyleManager.userStyle)
+        userStyleRepository.addUserStyleListener(styleListener)
+        sendCurrentUserStyle(userStyleRepository.userStyle)
 
         initFinished = true
     }
@@ -581,7 +585,7 @@
     internal fun onSetStyleInternal(style: Map<UserStyleCategory, UserStyleCategory.Option>) {
         // No need to echo the userStyle back.
         inOnSetStyle = true
-        userStyleManager.userStyle = style
+        userStyleRepository.userStyle = style
         inOnSetStyle = false
     }
 
@@ -591,7 +595,7 @@
         pendingPostDoubleTap.cancel()
         renderer.onDestroy()
         watchState.removeListener(systemStateListener)
-        userStyleManager.removeUserStyleListener(styleListener)
+        userStyleRepository.removeUserStyleListener(styleListener)
         WatchFaceConfigActivity.unregisterWatchFace(componentName)
     }
 
@@ -711,7 +715,7 @@
     /** @hide */
     @UiThread
     internal fun computeDelayTillNextFrame(beginFrameTimeMillis: Long, currentTimeMillis: Long):
-    Long {
+            Long {
         // Limit update rate to conserve power when the battery is low and not charging.
         val updateRateMillis =
             if (watchState.isBatteryLowAndNotCharging) {
@@ -719,7 +723,10 @@
             } else {
                 interactiveUpdateRateMillis
             }
-        var nextFrameTimeMillis = beginFrameTimeMillis + updateRateMillis
+        // Note beginFrameTimeMillis could be in the future if the user adjusted the time so we need
+        // to compute min(beginFrameTimeMillis, currentTimeMillis).
+        var nextFrameTimeMillis =
+            Math.min(beginFrameTimeMillis, currentTimeMillis) + updateRateMillis
         // Drop frames if needed (happens when onDraw is slow).
         if (nextFrameTimeMillis <= currentTimeMillis) {
             // Compute the next runtime after currentTimeMillis with the same phase as
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 66e2696..96a0cae4 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -45,7 +45,7 @@
 import androidx.annotation.IntDef
 import androidx.wear.complications.SystemProviders.ProviderId
 import androidx.wear.watchface.style.UserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 import java.util.concurrent.CountDownLatch
 
 /**
@@ -444,9 +444,9 @@
             override fun setUserStyle(userStyle: Bundle) {
                 runOnUiThread {
                     watchFace.onSetStyleInternal(
-                        UserStyleManager.bundleToStyleMap(
+                        UserStyleRepository.bundleToStyleMap(
                             userStyle,
-                            watchFace.userStyleManager.userStyleCategories
+                            watchFace.userStyleRepository.userStyleCategories
                         )
                     )
                 }
@@ -960,7 +960,7 @@
         override fun registerUserStyleSchema(styleSchema: List<UserStyleCategory>) {
             if (systemApiVersion >= 3) {
                 iWatchFaceService.registerUserStyleSchema(
-                    UserStyleManager.userStyleCategoriesToBundles(styleSchema)
+                    UserStyleRepository.userStyleCategoriesToBundles(styleSchema)
                 )
             }
         }
@@ -969,7 +969,9 @@
             userStyle: Map<UserStyleCategory, UserStyleCategory.Option>
         ) {
             if (systemApiVersion >= 3) {
-                iWatchFaceService.setCurrentUserStyle(UserStyleManager.styleMapToBundle(userStyle))
+                iWatchFaceService.setCurrentUserStyle(
+                    UserStyleRepository.styleMapToBundle(userStyle)
+                )
             }
         }
 
@@ -979,7 +981,7 @@
             if (systemApiVersion < 3) {
                 return null
             }
-            return UserStyleManager.bundleToStyleMap(
+            return UserStyleRepository.bundleToStyleMap(
                 iWatchFaceService.storedUserStyle ?: Bundle(),
                 schema
             )
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/ConfigFragment.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/ConfigFragment.kt
index de2db2c..fd50b91 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/ConfigFragment.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/ConfigFragment.kt
@@ -38,7 +38,7 @@
 import androidx.recyclerview.widget.RecyclerView
 import androidx.wear.complications.ProviderInfoRetriever
 import androidx.wear.watchface.R
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 import androidx.wear.widget.SwipeDismissFrameLayout
 import androidx.wear.widget.WearableLinearLayoutManager
 import androidx.wear.widget.WearableRecyclerView
@@ -195,7 +195,7 @@
                 watchFaceConfigActivity.fragmentController.showStyleConfigFragment(
                     configKey,
                     watchFaceConfigActivity.styleSchema,
-                    UserStyleManager.bundleToStyleMap(
+                    UserStyleRepository.bundleToStyleMap(
                         watchFaceConfigActivity.watchFaceConfigDelegate.getUserStyle(),
                         watchFaceConfigActivity.styleSchema
                     )
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/StyleConfigFragment.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/StyleConfigFragment.kt
index b545795..3322c8a 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/StyleConfigFragment.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/StyleConfigFragment.kt
@@ -36,7 +36,7 @@
 import androidx.wear.watchface.style.DoubleRangeUserStyleCategory
 import androidx.wear.watchface.style.ListUserStyleCategory
 import androidx.wear.watchface.style.UserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 import androidx.wear.widget.SwipeDismissFrameLayout
 import androidx.wear.widget.WearableLinearLayoutManager
 import androidx.wear.widget.WearableRecyclerView
@@ -73,7 +73,7 @@
                     STYLE_SCHEMA,
                     ArrayList(styleSchema.map { Bundle().apply { it.writeToBundle(this) } })
                 )
-                putBundle(STYLE_MAP, UserStyleManager.styleMapToBundle(styleMap))
+                putBundle(STYLE_MAP, UserStyleRepository.styleMapToBundle(styleMap))
             }
         }
     }
@@ -185,7 +185,7 @@
             (requireArguments().getParcelableArrayList<Bundle>(STYLE_SCHEMA))!!
                 .map { UserStyleCategory.createFromBundle(it) }
 
-        styleMap = UserStyleManager.bundleToStyleMap(
+        styleMap = UserStyleRepository.bundleToStyleMap(
             requireArguments().getBundle(STYLE_MAP)!!,
             styleSchema
         )
@@ -198,7 +198,7 @@
 
         // These will become IPCs eventually, hence the use of Bundles.
         watchFaceConfigActivity.watchFaceConfigDelegate.setUserStyle(
-            UserStyleManager.styleMapToBundle(styleMap)
+            UserStyleRepository.styleMapToBundle(styleMap)
         )
     }
 
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/WatchFaceConfigActivity.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/WatchFaceConfigActivity.kt
index 52848c1..442ddbe 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/WatchFaceConfigActivity.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ui/WatchFaceConfigActivity.kt
@@ -33,7 +33,7 @@
 import androidx.fragment.app.FragmentActivity
 import androidx.wear.complications.ComplicationHelperActivity
 import androidx.wear.watchface.style.UserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 
 /** @hide */
 @RestrictTo(LIBRARY)
@@ -208,7 +208,7 @@
             }
 
         styleSchema =
-            UserStyleManager.bundlesToUserStyleCategoryList(
+            UserStyleRepository.bundlesToUserStyleCategoryList(
                 watchFaceConfigDelegate.getUserStyleSchema()
             )
 
@@ -250,7 +250,7 @@
                 fragmentController.showStyleConfigFragment(
                     onlyStyleCategory.id,
                     styleSchema,
-                    UserStyleManager.bundleToStyleMap(
+                    UserStyleRepository.bundleToStyleMap(
                         watchFaceConfigDelegate.getUserStyle(),
                         styleSchema
                     )
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
index 72a714b..0ab6953 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
@@ -32,7 +32,7 @@
 import android.view.SurfaceHolder
 import androidx.test.core.app.ApplicationProvider
 import androidx.wear.watchface.style.UserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 import org.junit.runners.model.FrameworkMethod
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.internal.bytecode.InstrumentationConfiguration
@@ -41,7 +41,7 @@
     @WatchFaceType private val watchFaceType: Int,
     private val complicationsHolder: ComplicationsHolder,
     private val renderer: TestRenderer,
-    private val userStyleManager: UserStyleManager,
+    private val userStyleRepository: UserStyleRepository,
     private val watchState: WatchState,
     private val handler: Handler,
     private val interactiveFrameRateMs: Long
@@ -53,8 +53,8 @@
     var lastUserStyle: Map<UserStyleCategory, UserStyleCategory.Option>? = null
 
     init {
-        userStyleManager.addUserStyleListener(
-            object : UserStyleManager.UserStyleListener {
+        userStyleRepository.addUserStyleListener(
+            object : UserStyleRepository.UserStyleListener {
                 override fun onUserStyleChanged(
                     userStyle: Map<UserStyleCategory, UserStyleCategory.Option>
                 ) {
@@ -101,7 +101,7 @@
         watchFace = WatchFace.Builder(
             watchFaceType,
             interactiveFrameRateMs,
-            userStyleManager,
+            userStyleRepository,
             complicationsHolder,
             renderer,
             watchFaceHost,
@@ -205,10 +205,10 @@
 
 open class TestRenderer(
     surfaceHolder: SurfaceHolder,
-    userStyleManager: UserStyleManager,
+    userStyleRepository: UserStyleRepository,
     watchState: WatchState
 ) :
-    CanvasRenderer(surfaceHolder, userStyleManager, watchState, CanvasType.HARDWARE) {
+    CanvasRenderer(surfaceHolder, userStyleRepository, watchState, CanvasType.HARDWARE) {
     var lastOnDrawCalendar: Calendar? = null
     var lastDrawMode = DrawMode.INTERACTIVE
 
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index 33a074a..aec9c5f 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -39,7 +39,7 @@
 import androidx.wear.complications.rendering.ComplicationDrawable
 import androidx.wear.watchface.style.ListUserStyleCategory
 import androidx.wear.watchface.style.UserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 import androidx.wear.watchface.ui.WatchFaceConfigActivity
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
@@ -181,7 +181,7 @@
 
     private lateinit var renderer: TestRenderer
     private lateinit var complicationsHolder: ComplicationsHolder
-    private lateinit var userStyleManager: UserStyleManager
+    private lateinit var userStyleRepository: UserStyleRepository
     private lateinit var watchFace: WatchFace
     private lateinit var testWatchFaceService: TestWatchFaceService
     private lateinit var engineWrapper: WatchFaceService.EngineWrapper
@@ -209,14 +209,14 @@
         apiVersion: Int = 2
     ) {
         this.complicationsHolder = ComplicationsHolder(complications)
-        userStyleManager =
-            UserStyleManager(userStyleCategories)
-        renderer = TestRenderer(surfaceHolder, userStyleManager, systemState)
+        userStyleRepository =
+            UserStyleRepository(userStyleCategories)
+        renderer = TestRenderer(surfaceHolder, userStyleRepository, systemState)
         testWatchFaceService = TestWatchFaceService(
             watchFaceType,
             this.complicationsHolder,
             renderer,
-            userStyleManager,
+            userStyleRepository,
             systemState,
             handler,
             INTERACTIVE_UPDATE_RATE_MS
@@ -678,8 +678,7 @@
                 beginFrameTimeMillis = 0,
                 currentTimeMillis = INTERACTIVE_UPDATE_RATE_MS
             )
-        )
-            .isEqualTo(INTERACTIVE_UPDATE_RATE_MS)
+        ).isEqualTo(INTERACTIVE_UPDATE_RATE_MS)
     }
 
     @Test
@@ -694,8 +693,19 @@
                 beginFrameTimeMillis = 2,
                 currentTimeMillis = INTERACTIVE_UPDATE_RATE_MS + 3
             )
-        )
-            .isEqualTo(INTERACTIVE_UPDATE_RATE_MS - 1)
+        ).isEqualTo(INTERACTIVE_UPDATE_RATE_MS - 1)
+    }
+
+    @Test
+    fun computeDelayTillNextFrame_beginFrameTimeInTheFuture() {
+        initEngine(WatchFaceType.ANALOG, listOf(leftComplication, rightComplication), emptyList())
+
+        assertThat(
+            watchFace.computeDelayTillNextFrame(
+                beginFrameTimeMillis = 100,
+                currentTimeMillis = 10
+            )
+        ).isEqualTo(INTERACTIVE_UPDATE_RATE_MS)
     }
 
     @Test
@@ -744,21 +754,21 @@
         )
 
         // This should get persisted.
-        userStyleManager.userStyle = mapOf(
+        userStyleRepository.userStyle = mapOf(
             colorStyleCategory to blueStyleOption,
             watchHandStyleCategory to gothicStyleOption
         )
 
-        val styleManager2 = UserStyleManager(
+        val userStyleRepository2 = UserStyleRepository(
             listOf(colorStyleCategory, watchHandStyleCategory)
         )
 
-        val testRenderer2 = TestRenderer(surfaceHolder, styleManager2, systemState)
+        val testRenderer2 = TestRenderer(surfaceHolder, userStyleRepository2, systemState)
         val service2 = TestWatchFaceService(
             WatchFaceType.ANALOG,
             ComplicationsHolder(emptyList()),
             testRenderer2,
-            styleManager2,
+            userStyleRepository2,
             systemState,
             handler,
             INTERACTIVE_UPDATE_RATE_MS
@@ -769,10 +779,10 @@
         engine2.onSurfaceChanged(surfaceHolder, 0, 100, 100)
         sendBinder(engine2, apiVersion = 2)
 
-        assertThat(styleManager2.userStyle[colorStyleCategory]).isEqualTo(
+        assertThat(userStyleRepository2.userStyle[colorStyleCategory]).isEqualTo(
             blueStyleOption
         )
-        assertThat(styleManager2.userStyle[watchHandStyleCategory]).isEqualTo(
+        assertThat(userStyleRepository2.userStyle[watchHandStyleCategory]).isEqualTo(
             gothicStyleOption
         )
     }
@@ -799,21 +809,21 @@
         )
 
         // This should get persisted.
-        userStyleManager.userStyle = mapOf(
+        userStyleRepository.userStyle = mapOf(
             colorStyleCategory to blueStyleOption,
             watchHandStyleCategory to gothicStyleOption
         )
 
-        val styleManager2 = UserStyleManager(
+        val userStyleRepository2 = UserStyleRepository(
             listOf(colorStyleCategory, watchHandStyleCategory)
         )
 
-        val testRenderer2 = TestRenderer(surfaceHolder, styleManager2, systemState)
+        val testRenderer2 = TestRenderer(surfaceHolder, userStyleRepository2, systemState)
         val service2 = TestWatchFaceService(
             WatchFaceType.ANALOG,
             ComplicationsHolder(emptyList()),
             testRenderer2,
-            styleManager2,
+            userStyleRepository2,
             systemState,
             handler,
             INTERACTIVE_UPDATE_RATE_MS
@@ -824,10 +834,10 @@
         engine2.onSurfaceChanged(surfaceHolder, 0, 100, 100)
         sendBinder(engine2, apiVersion = 3)
 
-        assertThat(styleManager2.userStyle[colorStyleCategory]).isEqualTo(
+        assertThat(userStyleRepository2.userStyle[colorStyleCategory]).isEqualTo(
             blueStyleOption
         )
-        assertThat(styleManager2.userStyle[watchHandStyleCategory]).isEqualTo(
+        assertThat(userStyleRepository2.userStyle[watchHandStyleCategory]).isEqualTo(
             gothicStyleOption
         )
     }
@@ -835,7 +845,7 @@
     @Test
     fun persistedStyleOptionMismatchIgnored() {
         `when`(iWatchFaceService.getStoredUserStyle()).thenReturn(
-            UserStyleManager.styleMapToBundle(mapOf(watchHandStyleCategory to badStyleOption))
+            UserStyleRepository.styleMapToBundle(mapOf(watchHandStyleCategory to badStyleOption))
         )
 
         initEngine(
@@ -1016,16 +1026,16 @@
 
     @Test
     fun requestStyleBeforeSetBinder() {
-        var styleManager =
-            UserStyleManager(emptyList())
-        var testRenderer = TestRenderer(surfaceHolder, styleManager, systemState)
+        var userStyleRepository =
+            UserStyleRepository(emptyList())
+        var testRenderer = TestRenderer(surfaceHolder, userStyleRepository, systemState)
         val service = TestWatchFaceService(
             WatchFaceType.ANALOG,
             ComplicationsHolder(
                 listOf(leftComplication, rightComplication, backgroundComplication)
             ),
             testRenderer,
-            UserStyleManager(emptyList()),
+            UserStyleRepository(emptyList()),
             systemState,
             handler,
             INTERACTIVE_UPDATE_RATE_MS
@@ -1112,8 +1122,8 @@
 
     @Test
     fun shouldAnimateOverrideControlsEnteringAmbientMode() {
-        var styleManager = UserStyleManager(emptyList())
-        var testRenderer = object : TestRenderer(surfaceHolder, styleManager, systemState) {
+        var userStyleRepository = UserStyleRepository(emptyList())
+        var testRenderer = object : TestRenderer(surfaceHolder, userStyleRepository, systemState) {
             var animate = true
             override fun shouldAnimate() = animate
         }
@@ -1121,7 +1131,7 @@
             WatchFaceType.ANALOG,
             ComplicationsHolder(emptyList()),
             testRenderer,
-            UserStyleManager(emptyList()),
+            UserStyleRepository(emptyList()),
             systemState,
             handler,
             INTERACTIVE_UPDATE_RATE_MS
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/ui/WatchFaceConfigUiTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/ui/WatchFaceConfigUiTest.kt
index 9d78007..2b5af7c 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/ui/WatchFaceConfigUiTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/ui/WatchFaceConfigUiTest.kt
@@ -38,7 +38,7 @@
 import androidx.wear.watchface.createComplicationData
 import androidx.wear.watchface.style.ListUserStyleCategory
 import androidx.wear.watchface.style.UserStyleCategory
-import androidx.wear.watchface.style.UserStyleManager
+import androidx.wear.watchface.style.UserStyleRepository
 import com.google.common.truth.Truth.assertThat
 import com.nhaarman.mockitokotlin2.argumentCaptor
 import com.nhaarman.mockitokotlin2.eq
@@ -167,7 +167,7 @@
 
     private val configActivity = WatchFaceConfigActivity()
 
-    private lateinit var userStyleManager: UserStyleManager
+    private lateinit var userStyleRepository: UserStyleRepository
 
     private fun initConfigActivity(
         complications: List<Complication>,
@@ -176,12 +176,12 @@
         Mockito.`when`(surfaceHolder.surfaceFrame)
             .thenReturn(ONE_HUNDRED_BY_ONE_HUNDRED_RECT)
 
-        userStyleManager =
-            UserStyleManager(userStyleCategories)
+        userStyleRepository =
+            UserStyleRepository(userStyleCategories)
 
         val complicationSet = ComplicationsHolder(
             complications,
-            object : Renderer(surfaceHolder, userStyleManager, WatchState()) {
+            object : Renderer(surfaceHolder, userStyleRepository, WatchState()) {
                 override fun renderInternal(calendar: Calendar) {}
 
                 override fun takeScreenshot(calendar: Calendar, drawMode: Int): Bitmap {
@@ -197,14 +197,14 @@
             watchFaceComponentName,
             object : WatchFaceConfigDelegate {
                 override fun getUserStyleSchema() =
-                    UserStyleManager.userStyleCategoriesToBundles(userStyleCategories)
+                    UserStyleRepository.userStyleCategoriesToBundles(userStyleCategories)
 
                 override fun getUserStyle() =
-                    UserStyleManager.styleMapToBundle(userStyleManager.userStyle)
+                    UserStyleRepository.styleMapToBundle(userStyleRepository.userStyle)
 
                 override fun setUserStyle(style: Bundle) {
-                    userStyleManager.userStyle =
-                        UserStyleManager.bundleToStyleMap(style, userStyleCategories)
+                    userStyleRepository.userStyle =
+                        UserStyleRepository.bundleToStyleMap(style, userStyleCategories)
                 }
 
                 override fun getBackgroundComplicationId() =
@@ -355,19 +355,19 @@
         styleConfigFragment.readOptionsFromArguments()
         styleConfigFragment.watchFaceConfigActivity = configActivity
 
-        assertThat(userStyleManager.userStyle[colorStyleCategory]!!.id).isEqualTo(
+        assertThat(userStyleRepository.userStyle[colorStyleCategory]!!.id).isEqualTo(
             redStyleOption.id
         )
-        assertThat(userStyleManager.userStyle[watchHandStyleCategory]!!.id).isEqualTo(
+        assertThat(userStyleRepository.userStyle[watchHandStyleCategory]!!.id).isEqualTo(
             classicStyleOption.id
         )
 
         styleConfigFragment.onItemClick(configActivity.styleSchema[categoryIndex].options[1])
 
-        assertThat(userStyleManager.userStyle[colorStyleCategory]!!.id).isEqualTo(
+        assertThat(userStyleRepository.userStyle[colorStyleCategory]!!.id).isEqualTo(
             greenStyleOption.id
         )
-        assertThat(userStyleManager.userStyle[watchHandStyleCategory]!!.id).isEqualTo(
+        assertThat(userStyleRepository.userStyle[watchHandStyleCategory]!!.id).isEqualTo(
             classicStyleOption.id
         )
     }