[go: nahoru, domu]

Merge "Remove unused proto" into androidx-master-dev
diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml
index d44d46c..3fd78ec 100644
--- a/.github/workflows/presubmit.yml
+++ b/.github/workflows/presubmit.yml
@@ -49,30 +49,28 @@
         with:
           fetch-depth: 1
 
-      - name: "Cache ~/.gradle/caches"
-        uses: actions/cache@v2.1.2
+      - name: "Setup JDK 11"
+        id: setup-java
+        uses: actions/setup-java@v1
         with:
-          path: "~/.gradle/caches"
-          key: gradle-cache-${{ runner.os }}-${{ hashFiles('**/*.gradle') }}-${{ hashFiles('**/gradle.properties') }}
-          restore-keys: |
-            gradle-cache-${{ runner.os }}-
+          java-version: "11"
 
-      - name: "Cache ~/.gradlew/wrapper"
-        uses: actions/cache@v2.1.2
-        with:
-          path: ~/.gradle/wrapper
-          key: gradle-wrapper-${{ runner.os }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
+      - name: "Set environment variables"
+        shell: bash
+        run: |
+          set -x
+          echo "ANDROID_SDK_ROOT=$HOME/Library/Android/sdk" >> $GITHUB_ENV
+          echo "DIST_DIR=$HOME/dist" >> $GITHUB_ENV
 
-      - name: "Cache ~/.konan"
-        uses: actions/cache@v2.1.2
+      - name: "./gradlew buildOnServer"
+        uses: eskatos/gradle-command-action@v1
+        env:
+          JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          path: ~/.konan
-          key: konan-${{ runner.os }}
-
-      - name: "Build"
-        uses: androidx/build-on-server-action@main
-        with:
-          path: ${{ env.group-id }}
+          arguments: buildOnServer
+          build-root-directory: ${{ env.group-id }}
+          gradle-executable: ${{ env.group-id }}/gradlew
+          wrapper-directory: ${{ env.group-id }}/gradle/wrapper
 
       - name: "Upload build artifacts"
         continue-on-error: true
@@ -104,30 +102,28 @@
         with:
           fetch-depth: 1
 
-      - name: "Cache ~/.gradle/caches"
-        uses: actions/cache@v2.1.2
+      - name: "Setup JDK 11"
+        id: setup-java
+        uses: actions/setup-java@v1
         with:
-          path: "~/.gradle/caches"
-          key: gradle-cache-${{ runner.os }}-${{ hashFiles('**/*.gradle') }}-${{ hashFiles('**/gradle.properties') }}
-          restore-keys: |
-            gradle-cache-${{ runner.os }}-
+          java-version: "11"
 
-      - name: "Cache ~/.gradlew/wrapper"
-        uses: actions/cache@v2.1.2
-        with:
-          path: ~/.gradle/wrapper
-          key: gradle-wrapper-${{ runner.os }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
+      - name: "Set environment variables"
+        shell: bash
+        run: |
+          set -x
+          echo "ANDROID_SDK_ROOT=$HOME/Library/Android/sdk" >> $GITHUB_ENV
+          echo "DIST_DIR=$HOME/dist" >> $GITHUB_ENV
 
-      - name: "Cache ~/.konan"
-        uses: actions/cache@v2.1.2
+      - name: "./gradlew buildOnServer"
+        uses: eskatos/gradle-command-action@v1
+        env:
+          JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          path: ~/.konan
-          key: konan-${{ runner.os }}
-
-      - name: "Build"
-        uses: androidx/build-on-server-action@main
-        with:
-          path: ${{ env.group-id }}
+          arguments: buildOnServer
+          build-root-directory: ${{ env.group-id }}
+          gradle-executable: ${{ env.group-id }}/gradlew
+          wrapper-directory: ${{ env.group-id }}/gradle/wrapper
 
       - name: "Upload build artifacts"
         continue-on-error: true
@@ -159,30 +155,28 @@
         with:
           fetch-depth: 1
 
-      - name: "Cache ~/.gradle/caches"
-        uses: actions/cache@v2.1.2
+      - name: "Setup JDK 11"
+        id: setup-java
+        uses: actions/setup-java@v1
         with:
-          path: "~/.gradle/caches"
-          key: gradle-cache-${{ runner.os }}-${{ hashFiles('**/*.gradle') }}-${{ hashFiles('**/gradle.properties') }}
-          restore-keys: |
-            gradle-cache-${{ runner.os }}-
+          java-version: "11"
 
-      - name: "Cache ~/.gradlew/wrapper"
-        uses: actions/cache@v2.1.2
-        with:
-          path: ~/.gradle/wrapper
-          key: gradle-wrapper-${{ runner.os }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
+      - name: "Set environment variables"
+        shell: bash
+        run: |
+          set -x
+          echo "ANDROID_SDK_ROOT=$HOME/Library/Android/sdk" >> $GITHUB_ENV
+          echo "DIST_DIR=$HOME/dist" >> $GITHUB_ENV
 
-      - name: "Cache ~/.konan"
-        uses: actions/cache@v2.1.2
+      - name: "./gradlew buildOnServer"
+        uses: eskatos/gradle-command-action@v1
+        env:
+          JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          path: ~/.konan
-          key: konan-${{ runner.os }}
-
-      - name: "Build"
-        uses: androidx/build-on-server-action@main
-        with:
-          path: ${{ env.group-id }}
+          arguments: buildOnServer
+          build-root-directory: ${{ env.group-id }}
+          gradle-executable: ${{ env.group-id }}/gradlew
+          wrapper-directory: ${{ env.group-id }}/gradle/wrapper
 
       - name: "Upload build artifacts"
         continue-on-error: true
@@ -214,30 +208,28 @@
         with:
           fetch-depth: 1
 
-      - name: "Cache ~/.gradle/caches"
-        uses: actions/cache@v2.1.2
+      - name: "Setup JDK 11"
+        id: setup-java
+        uses: actions/setup-java@v1
         with:
-          path: "~/.gradle/caches"
-          key: gradle-cache-${{ runner.os }}-${{ hashFiles('**/*.gradle') }}-${{ hashFiles('**/gradle.properties') }}
-          restore-keys: |
-            gradle-cache-${{ runner.os }}-
+          java-version: "11"
 
-      - name: "Cache ~/.gradlew/wrapper"
-        uses: actions/cache@v2.1.2
-        with:
-          path: ~/.gradle/wrapper
-          key: gradle-wrapper-${{ runner.os }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
+      - name: "Set environment variables"
+        shell: bash
+        run: |
+          set -x
+          echo "ANDROID_SDK_ROOT=$HOME/Library/Android/sdk" >> $GITHUB_ENV
+          echo "DIST_DIR=$HOME/dist" >> $GITHUB_ENV
 
-      - name: "Cache ~/.konan"
-        uses: actions/cache@v2.1.2
+      - name: "./gradlew buildOnServer"
+        uses: eskatos/gradle-command-action@v1
+        env:
+          JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          path: ~/.konan
-          key: konan-${{ runner.os }}
-
-      - name: "Build"
-        uses: androidx/build-on-server-action@main
-        with:
-          path: ${{ env.group-id }}
+          arguments: buildOnServer
+          build-root-directory: ${{ env.group-id }}
+          gradle-executable: ${{ env.group-id }}/gradlew
+          wrapper-directory: ${{ env.group-id }}/gradle/wrapper
 
       - name: "Upload build artifacts"
         continue-on-error: true
@@ -269,32 +261,31 @@
         with:
           fetch-depth: 1
 
-      - name: "Cache ~/.gradle/caches"
-        uses: actions/cache@v2.1.2
+      - name: "Setup JDK 11"
+        id: setup-java
+        uses: actions/setup-java@v1
         with:
-          path: "~/.gradle/caches"
-          key: gradle-cache-${{ runner.os }}-${{ hashFiles('**/*.gradle') }}-${{ hashFiles('**/gradle.properties') }}
-          restore-keys: |
-            gradle-cache-${{ runner.os }}-
+          java-version: "11"
 
-      - name: "Cache ~/.gradlew/wrapper"
-        uses: actions/cache@v2.1.2
+      - name: "Set environment variables"
+        shell: bash
+        run: |
+          set -x
+          echo "ANDROID_SDK_ROOT=$HOME/Library/Android/sdk" >> $GITHUB_ENV
+          echo "DIST_DIR=$HOME/dist" >> $GITHUB_ENV
+
+      - name: "./gradlew buildOnServer"
+        uses: eskatos/gradle-command-action@v1
+        env:
+          JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          path: ~/.gradle/wrapper
-          key: gradle-wrapper-${{ runner.os }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
+          arguments: buildOnServer
+          build-root-directory: ${{ env.group-id }}
+          gradle-executable: ${{ env.group-id }}/gradlew
+          wrapper-directory: ${{ env.group-id }}/gradle/wrapper
 
-      - name: "Cache ~/.konan"
-        uses: actions/cache@v2.1.2
-        with:
-          path: ~/.konan
-          key: konan-${{ runner.os }}
 
-      - name: "Build"
-        uses: androidx/build-on-server-action@main
-        with:
-          path: ${{ env.group-id }}
-
-      - name: "Upload build artifacts"
+      - name: "upload build artifacts"
         continue-on-error: true
         if: always()
         uses: actions/upload-artifact@v2
@@ -324,30 +315,28 @@
         with:
           fetch-depth: 1
 
-      - name: "Cache ~/.gradle/caches"
-        uses: actions/cache@v2.1.2
+      - name: "Setup JDK 11"
+        id: setup-java
+        uses: actions/setup-java@v1
         with:
-          path: "~/.gradle/caches"
-          key: gradle-cache-${{ runner.os }}-${{ hashFiles('**/*.gradle') }}-${{ hashFiles('**/gradle.properties') }}
-          restore-keys: |
-            gradle-cache-${{ runner.os }}-
+          java-version: "11"
 
-      - name: "Cache ~/.gradlew/wrapper"
-        uses: actions/cache@v2.1.2
-        with:
-          path: ~/.gradle/wrapper
-          key: gradle-wrapper-${{ runner.os }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
+      - name: "Set environment variables"
+        shell: bash
+        run: |
+          set -x
+          echo "ANDROID_SDK_ROOT=$HOME/Library/Android/sdk" >> $GITHUB_ENV
+          echo "DIST_DIR=$HOME/dist" >> $GITHUB_ENV
 
-      - name: "Cache ~/.konan"
-        uses: actions/cache@v2.1.2
+      - name: "./gradlew buildOnServer"
+        uses: eskatos/gradle-command-action@v1
+        env:
+          JAVA_HOME: ${{ steps.setup-java.outputs.path }}
         with:
-          path: ~/.konan
-          key: konan-${{ runner.os }}
-
-      - name: "Build"
-        uses: androidx/build-on-server-action@main
-        with:
-          path: ${{ env.group-id }}
+          arguments: buildOnServer
+          build-root-directory: ${{ env.group-id }}
+          gradle-executable: ${{ env.group-id }}/gradlew
+          wrapper-directory: ${{ env.group-id }}/gradle/wrapper
 
       - name: "Upload build artifacts"
         continue-on-error: true
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java
index 080cf76..09ac4df 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java
@@ -16,6 +16,8 @@
 
 package androidx.appsearch.app;
 
+import static androidx.appsearch.app.AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES;
+import static androidx.appsearch.app.AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN;
 import static androidx.appsearch.app.AppSearchTestUtils.checkIsBatchResultSuccess;
 import static androidx.appsearch.app.AppSearchTestUtils.checkIsResultSuccess;
 import static androidx.appsearch.app.AppSearchTestUtils.convertSearchResultsToDocuments;
@@ -51,6 +53,27 @@
     }
 
     @AppSearchDocument
+    static class Card {
+        @AppSearchDocument.Uri String mUri;
+        @AppSearchDocument.Property
+                (indexingType = INDEXING_TYPE_PREFIXES, tokenizerType = TOKENIZER_TYPE_PLAIN)
+        String mString;        // 3a
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof Card)) {
+                return false;
+            }
+            Card otherCard = (Card) other;
+            assertThat(otherCard.mUri).isEqualTo(this.mUri);
+            return true;
+        }
+    }
+
+    @AppSearchDocument
     static class Gift {
         @AppSearchDocument.Uri String mUri;
 
@@ -62,7 +85,7 @@
         @AppSearchDocument.Property Collection<Boolean> mCollectBoolean;   // 1a
         @AppSearchDocument.Property Collection<byte[]> mCollectByteArr;    // 1a
         @AppSearchDocument.Property Collection<String> mCollectString;     // 1b
-        @AppSearchDocument.Property Collection<Gift> mCollectGift;         // 1c
+        @AppSearchDocument.Property Collection<Card> mCollectCard;         // 1c
 
         // Arrays
         @AppSearchDocument.Property Long[] mArrBoxLong;         // 2a
@@ -78,7 +101,7 @@
         @AppSearchDocument.Property byte[][] mArrUnboxByteArr;  // 2b
         @AppSearchDocument.Property Byte[] mBoxByteArr;         // 2a
         @AppSearchDocument.Property String[] mArrString;        // 2b
-        @AppSearchDocument.Property Gift[] mArrGift;            // 2c
+        @AppSearchDocument.Property Card[] mArrCard;            // 2c
 
         // Single values
         @AppSearchDocument.Property String mString;        // 3a
@@ -93,7 +116,7 @@
         @AppSearchDocument.Property Boolean mBoxBoolean;   // 3a
         @AppSearchDocument.Property boolean mUnboxBoolean; // 3b
         @AppSearchDocument.Property byte[] mUnboxByteArr;  // 3a
-        @AppSearchDocument.Property Gift mGift;            // 3c
+        @AppSearchDocument.Property Card mCard;            // 3c
 
         @Override
         public boolean equals(Object other) {
@@ -118,7 +141,7 @@
             assertThat(otherGift.mArrUnboxFloat).isEqualTo(this.mArrUnboxFloat);
             assertThat(otherGift.mArrUnboxLong).isEqualTo(this.mArrUnboxLong);
             assertThat(otherGift.mArrUnboxInt).isEqualTo(this.mArrUnboxInt);
-            assertThat(otherGift.mArrGift).isEqualTo(this.mArrGift);
+            assertThat(otherGift.mArrCard).isEqualTo(this.mArrCard);
 
             assertThat(otherGift.mCollectLong).isEqualTo(this.mCollectLong);
             assertThat(otherGift.mCollectInteger).isEqualTo(this.mCollectInteger);
@@ -126,7 +149,7 @@
             assertThat(otherGift.mCollectString).isEqualTo(this.mCollectString);
             assertThat(otherGift.mCollectDouble).isEqualTo(this.mCollectDouble);
             assertThat(otherGift.mCollectFloat).isEqualTo(this.mCollectFloat);
-            assertThat(otherGift.mCollectGift).isEqualTo(this.mCollectGift);
+            assertThat(otherGift.mCollectCard).isEqualTo(this.mCollectCard);
             checkCollectByteArr(otherGift.mCollectByteArr, this.mCollectByteArr);
 
             assertThat(otherGift.mString).isEqualTo(this.mString);
@@ -141,7 +164,7 @@
             assertThat(otherGift.mBoxBoolean).isEqualTo(this.mBoxBoolean);
             assertThat(otherGift.mUnboxBoolean).isEqualTo(this.mUnboxBoolean);
             assertThat(otherGift.mUnboxByteArr).isEqualTo(this.mUnboxByteArr);
-            assertThat(otherGift.mGift).isEqualTo(this.mGift);
+            assertThat(otherGift.mCard).isEqualTo(this.mCard);
             return true;
         }
 
@@ -160,7 +183,7 @@
         //TODO(b/156296904) add test for int, float, GenericDocument, and class with
         // @AppSearchDocument annotation
         checkIsResultSuccess(mSession.setSchema(
-                new SetSchemaRequest.Builder().addDataClass(Gift.class).build()));
+                new SetSchemaRequest.Builder().addDataClass(Card.class, Gift.class).build()));
 
         // Create a Gift object and assign values.
         Gift inputDataClass = new Gift();
@@ -180,11 +203,11 @@
         inputDataClass.mArrUnboxInt = new int[]{5, 4};
         inputDataClass.mArrUnboxLong = new long[]{7, 6};
 
-        Gift innerGift1 = new Gift();
-        innerGift1.mUri = "innerGift.uri1";
-        Gift innerGift2 = new Gift();
-        innerGift2.mUri = "innerGift.uri2";
-        inputDataClass.mArrGift = new Gift[]{innerGift1, innerGift2};
+        Card card1 = new Card();
+        card1.mUri = "card.uri1";
+        Card card2 = new Card();
+        card2.mUri = "card.uri2";
+        inputDataClass.mArrCard = new Card[]{card2, card2};
 
         inputDataClass.mCollectLong = Arrays.asList(inputDataClass.mArrBoxLong);
         inputDataClass.mCollectInteger = Arrays.asList(inputDataClass.mArrBoxInteger);
@@ -193,7 +216,7 @@
         inputDataClass.mCollectDouble = Arrays.asList(inputDataClass.mArrBoxDouble);
         inputDataClass.mCollectFloat = Arrays.asList(inputDataClass.mArrBoxFloat);
         inputDataClass.mCollectByteArr = Arrays.asList(inputDataClass.mArrUnboxByteArr);
-        inputDataClass.mCollectGift = Arrays.asList(innerGift1, innerGift2);
+        inputDataClass.mCollectCard = Arrays.asList(card2, card2);
 
         inputDataClass.mString = "String";
         inputDataClass.mBoxLong = 1L;
@@ -207,7 +230,7 @@
         inputDataClass.mBoxBoolean = true;
         inputDataClass.mUnboxBoolean = false;
         inputDataClass.mUnboxByteArr = new byte[]{1, 2, 3};
-        inputDataClass.mGift = innerGift1;
+        inputDataClass.mCard = card1;
 
         // Index the Gift document and query it.
         checkIsBatchResultSuccess(mSession.putDocuments(
@@ -231,7 +254,7 @@
     public void testAnnotationProcessor_QueryByType() throws Exception {
         checkIsResultSuccess(mSession.setSchema(
                 new SetSchemaRequest.Builder()
-                        .addDataClass(Gift.class)
+                        .addDataClass(Card.class, Gift.class)
                         .addSchema(AppSearchEmail.SCHEMA).build()));
 
         // Create documents and index them
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionTest.java
index 14f4e33..48129c8 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionTest.java
@@ -152,8 +152,8 @@
                 .addProperty(new AppSearchSchema.PropertyConfig.Builder("price")
                         .setDataType(PropertyConfig.DATA_TYPE_INT64)
                         .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setIndexingType(PropertyConfig.INDEXING_TYPE_EXACT_TERMS)
-                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_NONE)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_NONE)
                         .build())
                 .build();
         checkIsResultSuccess(
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/AppSearchDocument.java b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/AppSearchDocument.java
index ecb2b9e..1f1b399 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/AppSearchDocument.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/AppSearchDocument.java
@@ -191,6 +191,7 @@
          * <p>If not specified, defaults to {@link
          * AppSearchSchema.PropertyConfig#INDEXING_TYPE_NONE} (the field will not be indexed and
          * cannot be queried).
+         * TODO(b/171857731) renamed to TermMatchType when using String-specific indexing config.
          */
         @AppSearchSchema.PropertyConfig.IndexingType int indexingType()
                 default AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE;
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
index 0af8dc1..eca9c5a 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
@@ -188,6 +188,10 @@
 
         // Find tokenizer type
         int tokenizerType = Integer.parseInt(params.get("tokenizerType").toString());
+        if (Integer.parseInt(params.get("indexingType").toString()) == 0) {
+            //TODO(b/171857731) remove this hack after apply to Icing lib's change.
+            tokenizerType = 0;
+        }
         ClassName tokenizerEnum;
         if (tokenizerType == 0 || isPropertyDocument) {  // TOKENIZER_TYPE_NONE
             //It is only valid for tokenizer_type to be 'NONE' if the data type is
diff --git a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
index 86f579d..94a5317 100644
--- a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
+++ b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
@@ -454,7 +454,8 @@
                         + "@AppSearchDocument\n"
                         + "public class Gift {\n"
                         + "  @AppSearchDocument.Uri String uri;\n"
-                        + "  @AppSearchDocument.Property(tokenizerType=100) String str;\n"
+                        + "  @AppSearchDocument.Property(indexingType=1, tokenizerType=100)\n"
+                        + "  String str;\n"
                         + "}\n");
         CompilationSubject.assertThat(compilation).hadErrorContaining("Unknown tokenizer type 100");
     }
@@ -485,7 +486,8 @@
                         + "@AppSearchDocument\n"
                         + "public class Gift {\n"
                         + "  @AppSearchDocument.Uri String uri;\n"
-                        + "  @AppSearchDocument.Property(indexingType=100) String str;\n"
+                        + "  @AppSearchDocument.Property(indexingType=100, tokenizerType=1)\n"
+                        + "  String str;\n"
                         + "}\n");
         CompilationSubject.assertThat(compilation).hadErrorContaining("Unknown indexing type 100");
     }
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA
index 4dfb30e..b256fa5 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA
@@ -26,43 +26,43 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("stringProp")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("integerProp")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("longProp")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("floatProp")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("doubleProp")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("booleanProp")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("bytesProp")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Field.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Field.JAVA
index f9bea13..e315345 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Field.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Field.JAVA
@@ -21,7 +21,7 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("price")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Getter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Getter.JAVA
index c30fa56..1cf9253 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Getter.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Getter.JAVA
@@ -21,7 +21,7 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("price")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA
index 50f7553..13f4f18 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA
@@ -24,25 +24,25 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("repeatReq")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("repeatNoReq")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("req")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("noReq")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA
index 7855d50..87283d4 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA
@@ -21,7 +21,7 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("indexNone")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("indexExact")
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA
index 8a91767..8900092 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA
@@ -21,7 +21,7 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrString")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA
index 45459ae..d843153 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA
@@ -21,7 +21,7 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("newName")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA
index 8a6e8f6b..890d43d 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA
@@ -21,7 +21,7 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("price")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA
index 9f0d759..be28a52 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA
@@ -26,25 +26,25 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("listOfString")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("setOfInt")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("repeatedByteArray")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("byteArray")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA
index e4d80c9..8499df6 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA
@@ -21,19 +21,19 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("price")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("cat")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("dog")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_AllSupportedTypes.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_AllSupportedTypes.JAVA
index 9692936..d3ee29b 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_AllSupportedTypes.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_AllSupportedTypes.JAVA
@@ -32,43 +32,43 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("collectLong")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("collectInteger")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("collectDouble")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("collectFloat")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("collectBoolean")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("collectByteArr")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("collectString")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("collectGift")
@@ -81,79 +81,79 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrBoxLong")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrUnboxLong")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrBoxInteger")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrUnboxInt")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrBoxDouble")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrUnboxDouble")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrBoxFloat")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrUnboxFloat")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrBoxBoolean")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrUnboxBoolean")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrUnboxByteArr")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("boxByteArr")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrString")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("arrGift")
@@ -166,73 +166,73 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("string")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("boxLong")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("unboxLong")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("boxInteger")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("unboxInt")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("boxDouble")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("unboxDouble")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("boxFloat")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("unboxFloat")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("boxBoolean")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("unboxBoolean")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("unboxByteArr")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("gift")
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
index 71ec562..9be6cdb 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
@@ -27,7 +27,7 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("tokPlain")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_MultipleSetters.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_MultipleSetters.JAVA
index 8a6e8f6b..890d43d 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_MultipleSetters.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_MultipleSetters.JAVA
@@ -21,7 +21,7 @@
           .addProperty(new AppSearchSchema.PropertyConfig.Builder("price")
             .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
-            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
             .build())
           .build();
diff --git a/benchmark/benchmark/build.gradle b/benchmark/benchmark/build.gradle
index b0d1aa1..98a9387 100644
--- a/benchmark/benchmark/build.gradle
+++ b/benchmark/benchmark/build.gradle
@@ -22,8 +22,17 @@
     id("androidx.benchmark")
 }
 
+android {
+    defaultConfig {
+        // 18 needed for UI automator dependency, via benchmark-perfetto
+        minSdkVersion 18
+    }
+}
+
 dependencies {
     androidTestImplementation(project(":benchmark:benchmark-junit4"))
+    androidTestImplementation(project(":benchmark:benchmark-perfetto"))
+    androidTestImplementation(project(":tracing:tracing-ktx"))
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
     androidTestImplementation(ANDROIDX_TEST_RULES)
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoOverheadBenchmark.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoOverheadBenchmark.kt
new file mode 100644
index 0000000..f3f224c
--- /dev/null
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/PerfettoOverheadBenchmark.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.benchmark.benchmark
+
+import android.os.Trace
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.benchmark.perfetto.PerfettoRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.tracing.trace
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class PerfettoOverheadBenchmark {
+    @get:Rule
+    val benchmarkRule = BenchmarkRule()
+
+    @get:Rule
+    val perfettoRule = PerfettoRule()
+
+    /**
+     * Empty baseline, no tracing. Expect similar results to [TrivialJavaBenchmark.nothing].
+     */
+    @Test
+    fun empty() = benchmarkRule.measureRepeated {}
+
+    /**
+     * The trace section within runWithTimingDisabled, even though not measured, can impact the
+     * results of a small benchmark significantly.
+     */
+    @Test
+    fun runWithTimingDisabled() = benchmarkRule.measureRepeated {
+        runWithTimingDisabled { /* nothing*/ }
+    }
+
+    /**
+     * Trace section adds ~5us (depending on many factors) in this ideal case, but will be
+     * significantly worse in a real benchmark, as there's more computation to interfere with.
+     */
+    @Test
+    fun traceBeginEnd() = benchmarkRule.measureRepeated {
+        Trace.beginSection("foo")
+        Trace.endSection()
+    }
+
+    /**
+     * Dupe of [traceBeginEnd], just using [trace].
+     */
+    @Test
+    fun traceBlock() = benchmarkRule.measureRepeated {
+        trace("foo") { /* nothing */ }
+    }
+}
diff --git a/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoRule.kt b/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoRule.kt
index eb69b8c..e562cb8 100644
--- a/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoRule.kt
+++ b/benchmark/perfetto/src/main/java/androidx/benchmark/perfetto/PerfettoRule.kt
@@ -91,6 +91,7 @@
         block()
         val dst = destinationPath(traceName)
         stop(dst.absolutePath)
+        Log.d(PerfettoRule.TAG, "Finished recording to ${dst.absolutePath}")
         reportAdditionalFileToCopy("perfetto_trace", dst.absolutePath)
     } finally {
         cancel()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
index 833bb8e..06ce451 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
@@ -35,6 +35,7 @@
 import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
 import androidx.compose.ui.graphics.painter.ImagePainter
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.layout.ContentScale
@@ -47,7 +48,9 @@
 import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.testutils.assertPixels
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
@@ -293,6 +296,46 @@
     }
 
     @Test
+    fun testImageScalesNonuniformly() {
+        val imageComposableWidth = imageWidth * 3
+        val imageComposableHeight = imageHeight * 7
+
+        rule.setContent {
+            val density = DensityAmbient.current
+            val size = (containerSize * 2 / density.density).dp
+            val imageAsset = ImageAsset(imageWidth, imageHeight)
+            CanvasDrawScope().draw(
+                density,
+                LayoutDirection.Ltr,
+                Canvas(imageAsset),
+                Size(imageWidth.toFloat(), imageHeight.toFloat())
+            ) {
+                drawRect(color = Color.Blue)
+            }
+            Box(
+                Modifier.preferredSize(size)
+                    .background(color = Color.White)
+                    .wrapContentSize(Alignment.Center)
+            ) {
+                Image(
+                    asset = imageAsset,
+                    modifier = Modifier
+                        .testTag(contentTag)
+                        .preferredSize(
+                            (imageComposableWidth / density.density).dp,
+                            (imageComposableHeight / density.density).dp
+                        ),
+                    // Scale the image non-uniformly within the bounds of the composable
+                    contentScale = ContentScale.FillBounds,
+                    alignment = Alignment.BottomEnd
+                )
+            }
+        }
+
+        rule.onNodeWithTag(contentTag).captureToImage().assertPixels { Color.Blue }
+    }
+
+    @Test
     fun testImageFixedSizeAlignedBottomEnd() {
         val imageComposableWidth = imageWidth * 2
         val imageComposableHeight = imageHeight * 2
diff --git a/compose/integration-tests/benchmark/build.gradle b/compose/integration-tests/benchmark/build.gradle
index f46802f..f9efbbfe2 100644
--- a/compose/integration-tests/benchmark/build.gradle
+++ b/compose/integration-tests/benchmark/build.gradle
@@ -33,6 +33,7 @@
     kotlinPlugin project(":compose:compiler:compiler")
 
     implementation project(":benchmark:benchmark-junit4")
+    implementation project(":benchmark:benchmark-perfetto")
     implementation project(":compose:foundation:foundation-layout")
     implementation project(":compose:integration-tests")
     implementation project(":compose:runtime:runtime")
diff --git a/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/VectorBenchmark.kt b/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/VectorBenchmark.kt
index 64f3096..bfe8f59 100644
--- a/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/VectorBenchmark.kt
+++ b/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/VectorBenchmark.kt
@@ -35,7 +35,7 @@
  */
 @LargeTest
 @RunWith(AndroidJUnit4::class)
-class VectorBenchmark {
+open class VectorBenchmark {
     @get:Rule
     val benchmarkRule = ComposeBenchmarkRule()
 
diff --git a/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/VectorBenchmarkWithTracing.kt b/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/VectorBenchmarkWithTracing.kt
new file mode 100644
index 0000000..d4e1688
--- /dev/null
+++ b/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/VectorBenchmarkWithTracing.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.ui.benchmark.test
+
+import androidx.benchmark.perfetto.PerfettoRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+/**
+ * Duplicate of [VectorBenchmark], but which adds tracing.
+ *
+ * Note: Per PerfettoRule, these benchmarks will be ignored < API 29
+ */
+@Suppress("ClassName")
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class VectorBenchmarkWithTracing : VectorBenchmark() {
+    @get:Rule
+    val perfettoRule = PerfettoRule()
+}
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 62005ed..63fd054 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -1591,18 +1591,21 @@
   }
 
   @androidx.compose.runtime.Stable public interface ContentScale {
-    method public float scale-AhF4CD4(long srcSize, long dstSize);
+    method public long computeScaleFactor-AhF4CD4(long srcSize, long dstSize);
+    method @Deprecated public default float scale-AhF4CD4(long srcSize, long dstSize);
     field public static final androidx.compose.ui.layout.ContentScale.Companion Companion;
   }
 
   public static final class ContentScale.Companion {
     method public androidx.compose.ui.layout.ContentScale getCrop();
+    method public androidx.compose.ui.layout.ContentScale getFillBounds();
     method public androidx.compose.ui.layout.ContentScale getFillHeight();
     method public androidx.compose.ui.layout.ContentScale getFillWidth();
     method public androidx.compose.ui.layout.ContentScale getFit();
     method public androidx.compose.ui.layout.ContentScale getInside();
     method public androidx.compose.ui.layout.FixedScale getNone();
     property public final androidx.compose.ui.layout.ContentScale Crop;
+    property public final androidx.compose.ui.layout.ContentScale FillBounds;
     property public final androidx.compose.ui.layout.ContentScale FillHeight;
     property public final androidx.compose.ui.layout.ContentScale FillWidth;
     property public final androidx.compose.ui.layout.ContentScale Fit;
@@ -1616,9 +1619,9 @@
   @androidx.compose.runtime.Immutable public final class FixedScale implements androidx.compose.ui.layout.ContentScale {
     ctor public FixedScale(float value);
     method public float component1();
+    method public long computeScaleFactor-AhF4CD4(long srcSize, long dstSize);
     method @androidx.compose.runtime.Immutable public androidx.compose.ui.layout.FixedScale copy(float value);
     method public float getValue();
-    method public float scale-AhF4CD4(long srcSize, long dstSize);
     property public final float value;
   }
 
@@ -1800,6 +1803,36 @@
     method public void onRemeasurementAvailable(androidx.compose.ui.layout.Remeasurement remeasurement);
   }
 
+  @androidx.compose.runtime.Immutable public final inline class ScaleFactor {
+    ctor public ScaleFactor();
+    method @androidx.compose.runtime.Stable public static inline operator float component1-impl(long $this);
+    method @androidx.compose.runtime.Stable public static inline operator float component2-impl(long $this);
+    method public static long constructor-impl(internal long packedValue);
+    method public static long copy-_hLwfpc(long $this, optional float scaleX, optional float scaleY);
+    method @androidx.compose.runtime.Stable public static operator long div-_hLwfpc(long $this, float operand);
+    method @androidx.compose.runtime.Immutable public static inline boolean equals-impl(long p, Object? p1);
+    method public static boolean equals-impl0(long p1, long p2);
+    method public static float getScaleX-impl(long $this);
+    method public static float getScaleY-impl(long $this);
+    method @androidx.compose.runtime.Immutable public static inline int hashCode-impl(long p);
+    method @androidx.compose.runtime.Stable public static operator long times-_hLwfpc(long $this, float operand);
+    method public static String toString-impl(long $this);
+    field public static final androidx.compose.ui.layout.ScaleFactor.Companion Companion;
+  }
+
+  public static final class ScaleFactor.Companion {
+    method public long getUnspecified-_hLwfpc();
+    property public final long Unspecified;
+  }
+
+  public final class ScaleFactorKt {
+    method @androidx.compose.runtime.Stable public static long ScaleFactor(float scaleX, float scaleY);
+    method @androidx.compose.runtime.Stable public static operator long div-ngKnWWw(long, long scaleFactor);
+    method @androidx.compose.runtime.Stable public static long lerp-bKVCie4(long start, long stop, float fraction);
+    method @androidx.compose.runtime.Stable public static operator long times-Sp6zcS4(long, long size);
+    method @androidx.compose.runtime.Stable public static operator long times-ngKnWWw(long, long scaleFactor);
+  }
+
   public final class SubcomposeLayoutKt {
     method @androidx.compose.runtime.Composable public static <T> void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope<T>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
   }
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 62005ed..63fd054 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -1591,18 +1591,21 @@
   }
 
   @androidx.compose.runtime.Stable public interface ContentScale {
-    method public float scale-AhF4CD4(long srcSize, long dstSize);
+    method public long computeScaleFactor-AhF4CD4(long srcSize, long dstSize);
+    method @Deprecated public default float scale-AhF4CD4(long srcSize, long dstSize);
     field public static final androidx.compose.ui.layout.ContentScale.Companion Companion;
   }
 
   public static final class ContentScale.Companion {
     method public androidx.compose.ui.layout.ContentScale getCrop();
+    method public androidx.compose.ui.layout.ContentScale getFillBounds();
     method public androidx.compose.ui.layout.ContentScale getFillHeight();
     method public androidx.compose.ui.layout.ContentScale getFillWidth();
     method public androidx.compose.ui.layout.ContentScale getFit();
     method public androidx.compose.ui.layout.ContentScale getInside();
     method public androidx.compose.ui.layout.FixedScale getNone();
     property public final androidx.compose.ui.layout.ContentScale Crop;
+    property public final androidx.compose.ui.layout.ContentScale FillBounds;
     property public final androidx.compose.ui.layout.ContentScale FillHeight;
     property public final androidx.compose.ui.layout.ContentScale FillWidth;
     property public final androidx.compose.ui.layout.ContentScale Fit;
@@ -1616,9 +1619,9 @@
   @androidx.compose.runtime.Immutable public final class FixedScale implements androidx.compose.ui.layout.ContentScale {
     ctor public FixedScale(float value);
     method public float component1();
+    method public long computeScaleFactor-AhF4CD4(long srcSize, long dstSize);
     method @androidx.compose.runtime.Immutable public androidx.compose.ui.layout.FixedScale copy(float value);
     method public float getValue();
-    method public float scale-AhF4CD4(long srcSize, long dstSize);
     property public final float value;
   }
 
@@ -1800,6 +1803,36 @@
     method public void onRemeasurementAvailable(androidx.compose.ui.layout.Remeasurement remeasurement);
   }
 
+  @androidx.compose.runtime.Immutable public final inline class ScaleFactor {
+    ctor public ScaleFactor();
+    method @androidx.compose.runtime.Stable public static inline operator float component1-impl(long $this);
+    method @androidx.compose.runtime.Stable public static inline operator float component2-impl(long $this);
+    method public static long constructor-impl(internal long packedValue);
+    method public static long copy-_hLwfpc(long $this, optional float scaleX, optional float scaleY);
+    method @androidx.compose.runtime.Stable public static operator long div-_hLwfpc(long $this, float operand);
+    method @androidx.compose.runtime.Immutable public static inline boolean equals-impl(long p, Object? p1);
+    method public static boolean equals-impl0(long p1, long p2);
+    method public static float getScaleX-impl(long $this);
+    method public static float getScaleY-impl(long $this);
+    method @androidx.compose.runtime.Immutable public static inline int hashCode-impl(long p);
+    method @androidx.compose.runtime.Stable public static operator long times-_hLwfpc(long $this, float operand);
+    method public static String toString-impl(long $this);
+    field public static final androidx.compose.ui.layout.ScaleFactor.Companion Companion;
+  }
+
+  public static final class ScaleFactor.Companion {
+    method public long getUnspecified-_hLwfpc();
+    property public final long Unspecified;
+  }
+
+  public final class ScaleFactorKt {
+    method @androidx.compose.runtime.Stable public static long ScaleFactor(float scaleX, float scaleY);
+    method @androidx.compose.runtime.Stable public static operator long div-ngKnWWw(long, long scaleFactor);
+    method @androidx.compose.runtime.Stable public static long lerp-bKVCie4(long start, long stop, float fraction);
+    method @androidx.compose.runtime.Stable public static operator long times-Sp6zcS4(long, long size);
+    method @androidx.compose.runtime.Stable public static operator long times-ngKnWWw(long, long scaleFactor);
+  }
+
   public final class SubcomposeLayoutKt {
     method @androidx.compose.runtime.Composable public static <T> void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope<T>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
   }
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index eee4efa..a7df2e5 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -1591,18 +1591,21 @@
   }
 
   @androidx.compose.runtime.Stable public interface ContentScale {
-    method public float scale-AhF4CD4(long srcSize, long dstSize);
+    method public long computeScaleFactor-AhF4CD4(long srcSize, long dstSize);
+    method @Deprecated public default float scale-AhF4CD4(long srcSize, long dstSize);
     field public static final androidx.compose.ui.layout.ContentScale.Companion Companion;
   }
 
   public static final class ContentScale.Companion {
     method public androidx.compose.ui.layout.ContentScale getCrop();
+    method public androidx.compose.ui.layout.ContentScale getFillBounds();
     method public androidx.compose.ui.layout.ContentScale getFillHeight();
     method public androidx.compose.ui.layout.ContentScale getFillWidth();
     method public androidx.compose.ui.layout.ContentScale getFit();
     method public androidx.compose.ui.layout.ContentScale getInside();
     method public androidx.compose.ui.layout.FixedScale getNone();
     property public final androidx.compose.ui.layout.ContentScale Crop;
+    property public final androidx.compose.ui.layout.ContentScale FillBounds;
     property public final androidx.compose.ui.layout.ContentScale FillHeight;
     property public final androidx.compose.ui.layout.ContentScale FillWidth;
     property public final androidx.compose.ui.layout.ContentScale Fit;
@@ -1633,9 +1636,9 @@
   @androidx.compose.runtime.Immutable public final class FixedScale implements androidx.compose.ui.layout.ContentScale {
     ctor public FixedScale(float value);
     method public float component1();
+    method public long computeScaleFactor-AhF4CD4(long srcSize, long dstSize);
     method @androidx.compose.runtime.Immutable public androidx.compose.ui.layout.FixedScale copy(float value);
     method public float getValue();
-    method public float scale-AhF4CD4(long srcSize, long dstSize);
     property public final float value;
   }
 
@@ -1847,6 +1850,36 @@
     method public void onRemeasurementAvailable(androidx.compose.ui.layout.Remeasurement remeasurement);
   }
 
+  @androidx.compose.runtime.Immutable public final inline class ScaleFactor {
+    ctor public ScaleFactor();
+    method @androidx.compose.runtime.Stable public static inline operator float component1-impl(long $this);
+    method @androidx.compose.runtime.Stable public static inline operator float component2-impl(long $this);
+    method public static long constructor-impl(internal long packedValue);
+    method public static long copy-_hLwfpc(long $this, optional float scaleX, optional float scaleY);
+    method @androidx.compose.runtime.Stable public static operator long div-_hLwfpc(long $this, float operand);
+    method @androidx.compose.runtime.Immutable public static inline boolean equals-impl(long p, Object? p1);
+    method public static boolean equals-impl0(long p1, long p2);
+    method public static float getScaleX-impl(long $this);
+    method public static float getScaleY-impl(long $this);
+    method @androidx.compose.runtime.Immutable public static inline int hashCode-impl(long p);
+    method @androidx.compose.runtime.Stable public static operator long times-_hLwfpc(long $this, float operand);
+    method public static String toString-impl(long $this);
+    field public static final androidx.compose.ui.layout.ScaleFactor.Companion Companion;
+  }
+
+  public static final class ScaleFactor.Companion {
+    method public long getUnspecified-_hLwfpc();
+    property public final long Unspecified;
+  }
+
+  public final class ScaleFactorKt {
+    method @androidx.compose.runtime.Stable public static long ScaleFactor(float scaleX, float scaleY);
+    method @androidx.compose.runtime.Stable public static operator long div-ngKnWWw(long, long scaleFactor);
+    method @androidx.compose.runtime.Stable public static long lerp-bKVCie4(long start, long stop, float fraction);
+    method @androidx.compose.runtime.Stable public static operator long times-Sp6zcS4(long, long size);
+    method @androidx.compose.runtime.Stable public static operator long times-ngKnWWw(long, long scaleFactor);
+  }
+
   public final class SubcomposeLayoutKt {
     method @androidx.compose.runtime.Composable public static <T> void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope<T>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
   }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
index 6d4a922c..b5ef674 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
@@ -26,6 +26,7 @@
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Providers
+import androidx.compose.testutils.assertPixels
 import androidx.compose.ui.AlignTopLeft
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.AtLeastSize
@@ -44,6 +45,7 @@
 import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.graphics.asImageAsset
 import androidx.compose.ui.graphics.compositeOver
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.painter.ImagePainter
@@ -565,6 +567,35 @@
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
+    fun testImagePainterScalesNonUniformly() {
+        // The composable dimensions are larger than the ImageAsset. By not passing in
+        // a ContentScale parameter to the painter, the ImageAsset should be stretched
+        // non-uniformly to fully occupy the bounds of the composable
+        val boxWidth = 60
+        val boxHeight = 40
+        val srcImage = ImageAsset(10, 20)
+        val canvas = Canvas(srcImage)
+        val paint = Paint().apply { this.color = Color.Red }
+        canvas.drawRect(0f, 0f, 40f, 20f, paint)
+
+        val testTag = "testTag"
+
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .testTag(testTag)
+                    .background(color = Color.Gray)
+                    .width((boxWidth / DensityAmbient.current.density).dp)
+                    .height((boxHeight / DensityAmbient.current.density).dp)
+                    .paint(ImagePainter(srcImage), contentScale = ContentScale.FillBounds)
+            )
+        }
+
+        rule.obtainScreenshotBitmap(boxWidth, boxHeight).asImageAsset().assertPixels { Color.Red }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
     fun testVectorPainterScalesContent() {
         // VectorPainter should handle scaling its content vector up to fill the
         // corresponding content bounds. Because the composable is twice the
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
index 742f475..4d47e25 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
@@ -27,6 +27,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.testutils.assertPixels
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.AtLeastSize
 import androidx.compose.ui.Modifier
@@ -255,6 +256,40 @@
         }
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testVectorScaleNonUniformly() {
+        val defaultWidth = 24.dp
+        val defaultHeight = 24.dp
+        val testTag = "testTag"
+        rule.setContent {
+            val vectorPainter = rememberVectorPainter(
+                defaultWidth = defaultWidth,
+                defaultHeight = defaultHeight
+            ) { viewportWidth, viewportHeight ->
+                Path(
+                    fill = SolidColor(Color.Blue),
+                    pathData = PathData {
+                        lineTo(viewportWidth, 0f)
+                        lineTo(viewportWidth, viewportHeight)
+                        lineTo(0f, viewportHeight)
+                        close()
+                    }
+                )
+            }
+            Image(
+                painter = vectorPainter,
+                modifier = Modifier
+                    .testTag(testTag)
+                    .preferredSize(defaultWidth * 7, defaultHeight * 3)
+                    .background(Color.Red),
+                contentScale = ContentScale.FillBounds
+            )
+        }
+
+        rule.onNodeWithTag(testTag).captureToImage().assertPixels { Color.Blue }
+    }
+
     @Composable
     private fun VectorTint(
         size: Int = 200,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt
index b609dd9..2993b7f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.graphics.drawscope.translate
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.times
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.IntrinsicMeasureScope
 import androidx.compose.ui.layout.LayoutModifier
@@ -183,7 +184,7 @@
             }
 
             val srcSize = Size(srcWidth, srcHeight)
-            srcSize * contentScale.scale(srcSize, dstSize)
+            srcSize * contentScale.computeScaleFactor(srcSize, dstSize)
         }
     }
 
@@ -246,7 +247,7 @@
         }
 
         val srcSize = Size(srcWidth, srcHeight)
-        val scale = contentScale.scale(srcSize, size)
+        val scale = contentScale.computeScaleFactor(srcSize, size)
 
         // Compute the offset to translate the content based on the given alignment
         // and size to draw based on the ContentScale parameter
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ContentScale.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ContentScale.kt
index cbf29a5..7ecf32c3 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ContentScale.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ContentScale.kt
@@ -22,8 +22,6 @@
 import kotlin.math.max
 import kotlin.math.min
 
-private const val OriginalScale = 1.0f
-
 /**
  * Represents a rule to apply to scale a source rectangle to be inscribed into a destination
  */
@@ -31,10 +29,26 @@
 interface ContentScale {
 
     /**
+     * Computes the scale factor to apply to the horizontal and vertical axes independently
+     * of one another to fit the source appropriately with the given destination
+     */
+    fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor
+
+    /**
      * Computes the scale factor to apply to both dimensions in order to fit the source
      * appropriately with the given destination size
      */
-    fun scale(srcSize: Size, dstSize: Size): Float
+    @Deprecated(
+        "use computeScaleFactor instead",
+        ReplaceWith(
+            "computeScaleFactor(srcSize, dstSize)",
+            "androidx.compose.ui.layout"
+        )
+    )
+    fun scale(srcSize: Size, dstSize: Size): Float =
+        // Returning scaleX here as previous implementations of ContentScale all provided
+        // uniform scale values which were identical for both scaleX and scaleY
+        computeScaleFactor(srcSize, dstSize).scaleX
 
     /**
      * Companion object containing commonly used [ContentScale] implementations
@@ -51,8 +65,10 @@
          */
         @Stable
         val Crop = object : ContentScale {
-            override fun scale(srcSize: Size, dstSize: Size): Float =
-                computeFillMaxDimension(srcSize, dstSize)
+            override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor =
+                computeFillMaxDimension(srcSize, dstSize).let {
+                    ScaleFactor(it, it)
+                }
         }
 
         /**
@@ -65,8 +81,10 @@
          */
         @Stable
         val Fit = object : ContentScale {
-            override fun scale(srcSize: Size, dstSize: Size): Float =
-                computeFillMinDimension(srcSize, dstSize)
+            override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor =
+                computeFillMinDimension(srcSize, dstSize).let {
+                    ScaleFactor(it, it)
+                }
         }
 
         /**
@@ -76,8 +94,10 @@
          */
         @Stable
         val FillHeight = object : ContentScale {
-            override fun scale(srcSize: Size, dstSize: Size): Float =
-                computeFillHeight(srcSize, dstSize)
+            override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor =
+                computeFillHeight(srcSize, dstSize).let {
+                    ScaleFactor(it, it)
+                }
         }
 
         /**
@@ -87,8 +107,10 @@
          */
         @Stable
         val FillWidth = object : ContentScale {
-            override fun scale(srcSize: Size, dstSize: Size): Float =
-                computeFillWidth(srcSize, dstSize)
+            override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor =
+                computeFillWidth(srcSize, dstSize).let {
+                    ScaleFactor(it, it)
+                }
         }
 
         /**
@@ -102,19 +124,37 @@
          */
         @Stable
         val Inside = object : ContentScale {
-            override fun scale(srcSize: Size, dstSize: Size): Float =
-                if (srcSize.width <= dstSize.width && srcSize.height <= dstSize.height) {
-                    OriginalScale
+
+            override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor {
+                return if (srcSize.width <= dstSize.width &&
+                    srcSize.height <= dstSize.height
+                ) {
+                    ScaleFactor(1.0f, 1.0f)
                 } else {
-                    computeFillMinDimension(srcSize, dstSize)
+                    computeFillMinDimension(srcSize, dstSize).let {
+                        ScaleFactor(it, it)
+                    }
                 }
+            }
         }
 
         /**
          * Do not apply any scaling to the source
          */
         @Stable
-        val None = FixedScale(OriginalScale)
+        val None = FixedScale(1.0f)
+
+        /**
+         * Scale horizontal and vertically non-uniformly to fill the destination bounds.
+         */
+        @Stable
+        val FillBounds = object : ContentScale {
+            override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor =
+                ScaleFactor(
+                    computeFillWidth(srcSize, dstSize),
+                    computeFillHeight(srcSize, dstSize)
+                )
+        }
     }
 }
 
@@ -124,7 +164,8 @@
  */
 @Immutable
 data class FixedScale(val value: Float) : ContentScale {
-    override fun scale(srcSize: Size, dstSize: Size): Float = value
+    override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor =
+        ScaleFactor(value, value)
 }
 
 private fun computeFillMaxDimension(srcSize: Size, dstSize: Size): Float {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ScaleFactor.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ScaleFactor.kt
new file mode 100644
index 0000000..256ac27
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/ScaleFactor.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.layout
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.util.packFloats
+import androidx.compose.ui.util.toStringAsFixed
+import androidx.compose.ui.util.unpackFloat1
+import androidx.compose.ui.util.unpackFloat2
+
+/**
+ * Constructs a [ScaleFactor] from the given x and y scale values
+ */
+@Stable
+fun ScaleFactor(scaleX: Float, scaleY: Float) = ScaleFactor(packFloats(scaleX, scaleY))
+
+/**
+ * Holds 2 dimensional scaling factors for horizontal and vertical axes
+ */
+@Suppress("EXPERIMENTAL_FEATURE_WARNING")
+@Immutable
+inline class ScaleFactor(@PublishedApi internal val packedValue: Long) {
+
+    /**
+     * Returns the scale factor to apply along the horizontal axis
+     */
+    @Stable
+    val scaleX: Float
+        get() {
+            // Explicitly compare against packed values to avoid
+            // auto-boxing of ScaleFactor.Unspecified
+            check(this.packedValue != ScaleFactor.Unspecified.packedValue) {
+                "ScaleFactor is unspecified"
+            }
+            return unpackFloat1(packedValue)
+        }
+
+    /**
+     * Returns the scale factor to apply along the vertical axis
+     */
+    @Stable
+    val scaleY: Float
+        get() {
+            // Explicitly compare against packed values to avoid
+            // auto-boxing of Size.Unspecified
+            check(this.packedValue != ScaleFactor.Unspecified.packedValue) {
+                "ScaleFactor is unspecified"
+            }
+            return unpackFloat2(packedValue)
+        }
+
+    @Suppress("NOTHING_TO_INLINE")
+    @Stable
+    inline operator fun component1(): Float = scaleX
+
+    @Suppress("NOTHING_TO_INLINE")
+    @Stable
+    inline operator fun component2(): Float = scaleY
+
+    /**
+     * Returns a copy of this ScaleFactor instance optionally overriding the
+     * scaleX or scaleY parameters
+     */
+    fun copy(scaleX: Float = this.scaleX, scaleY: Float = this.scaleY) = ScaleFactor(scaleX, scaleY)
+
+    /**
+     * Multiplication operator.
+     *
+     * Returns a [ScaleFactor] with scale x and y values multiplied by the operand
+     */
+    @Stable
+    operator fun times(operand: Float) = ScaleFactor(scaleX * operand, scaleY * operand)
+
+    /**
+     * Division operator.
+     *
+     * Returns a [ScaleFactor] with scale x and y values divided by the operand
+     */
+    @Stable
+    operator fun div(operand: Float) = ScaleFactor(scaleX / operand, scaleY / operand)
+
+    override fun toString() = "ScaleFactor(${scaleX.toStringAsFixed(1)}, " +
+        "${scaleY.toStringAsFixed(1)})"
+
+    companion object {
+
+        /**
+         * A ScaleFactor whose [scaleX] and [scaleY] parameters are unspecified. This is a sentinel
+         * value used to initialize a non-null parameter.
+         * Access to scaleX or scaleY on an unspecified size is not allowed
+         */
+        @Stable
+        val Unspecified = ScaleFactor(Float.NaN, Float.NaN)
+    }
+}
+
+/**
+ * Multiplication operator with [Size].
+ *
+ * Return a new [Size] with the width and height multiplied by the [ScaleFactor.scaleX] and
+ * [ScaleFactor.scaleY] respectively
+ */
+@Stable
+operator fun Size.times(scaleFactor: ScaleFactor): Size =
+    Size(this.width * scaleFactor.scaleX, this.height * scaleFactor.scaleY)
+
+/**
+ * Multiplication operator with [Size] with reverse parameter types to maintain
+ * commutative properties of multiplication
+ *
+ * Return a new [Size] with the width and height multiplied by the [ScaleFactor.scaleX] and
+ * [ScaleFactor.scaleY] respectively
+ */
+@Stable
+operator fun ScaleFactor.times(size: Size): Size = size * this
+
+/**
+ * Division operator with [Size]
+ *
+ * Return a new [Size] with the width and height divided by [ScaleFactor.scaleX] and
+ * [ScaleFactor.scaleY] respectively
+ */
+@Stable
+operator fun Size.div(scaleFactor: ScaleFactor): Size =
+    Size(width / scaleFactor.scaleX, height / scaleFactor.scaleY)
+
+/**
+ * Linearly interpolate between two [ScaleFactor] parameters
+ *
+ * The [fraction] argument represents position on the timeline, with 0.0 meaning
+ * that the interpolation has not started, returning [start] (or something
+ * equivalent to [start]), 1.0 meaning that the interpolation has finished,
+ * returning [stop] (or something equivalent to [stop]), and values in between
+ * meaning that the interpolation is at the relevant point on the timeline
+ * between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and
+ * 1.0, so negative values and values greater than 1.0 are valid (and can
+ * easily be generated by curves).
+ *
+ * Values for [fraction] are usually obtained from an [Animation<Float>], such as
+ * an `AnimationController`.
+ */
+@Stable
+fun lerp(start: ScaleFactor, stop: ScaleFactor, fraction: Float): ScaleFactor {
+    return ScaleFactor(
+        androidx.compose.ui.util.lerp(start.scaleX, stop.scaleX, fraction),
+        androidx.compose.ui.util.lerp(start.scaleY, stop.scaleY, fraction)
+    )
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/layout/ContentScaleTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/layout/ContentScaleTest.kt
index 8d646fc..7134996 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/layout/ContentScaleTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/layout/ContentScaleTest.kt
@@ -27,92 +27,112 @@
 
     @Test
     fun testScaleNone() {
-        val scale = ContentScale.None.scale(
+        val scale = ContentScale.None.computeScaleFactor(
             srcSize = Size(100.0f, 100.0f),
             dstSize = Size(200.0f, 200.0f)
         )
-        assertEquals(1.0f, scale)
+        assertEquals(1.0f, scale.scaleX)
+        assertEquals(1.0f, scale.scaleY)
     }
 
     @Test
     fun testContentScaleFit() {
-        val scale = ContentScale.Fit.scale(
+        val scale = ContentScale.Fit.computeScaleFactor(
             srcSize = Size(200.0f, 100.0f),
             dstSize = Size(100.0f, 200.0f)
         )
-        assertEquals(.5f, scale)
+        assertEquals(.5f, scale.scaleX)
+        assertEquals(.5f, scale.scaleY)
     }
 
     @Test
     fun testContentScaleFillWidth() {
-        val scale = ContentScale.FillWidth.scale(
+        val scale = ContentScale.FillWidth.computeScaleFactor(
             srcSize = Size(400.0f, 100.0f),
             dstSize = Size(100.0f, 200.0f)
         )
-        assertEquals(0.25f, scale)
+        assertEquals(0.25f, scale.scaleX)
+        assertEquals(0.25f, scale.scaleY)
     }
 
     @Test
     fun testScaleFillHeight() {
-        val scale = ContentScale.FillHeight.scale(
+        val scale = ContentScale.FillHeight.computeScaleFactor(
             srcSize = Size(400.0f, 100.0f),
             dstSize = Size(100.0f, 200.0f)
         )
-        assertEquals(2.0f, scale)
+        assertEquals(2.0f, scale.scaleX)
+        assertEquals(2.0f, scale.scaleY)
     }
 
     @Test
     fun testContentScaleCrop() {
-        val scale = ContentScale.Crop.scale(
+        val scale = ContentScale.Crop.computeScaleFactor(
             srcSize = Size(400.0f, 100.0f),
             dstSize = Size(100.0f, 200.0f)
         )
-        assertEquals(2.0f, scale)
+        assertEquals(2.0f, scale.scaleX)
+        assertEquals(2.0f, scale.scaleY)
     }
 
     @Test
     fun testContentScaleInside() {
-        val scale = ContentScale.Inside.scale(
+        val scale = ContentScale.Inside.computeScaleFactor(
             srcSize = Size(400.0f, 100.0f),
             dstSize = Size(100.0f, 200.0f)
         )
-        assertEquals(0.25f, scale)
+        assertEquals(0.25f, scale.scaleX)
+        assertEquals(0.25f, scale.scaleY)
     }
 
     @Test
     fun testContentScaleInsideLargeDst() {
         // If the src is smaller than the destination, ensure no scaling is done
-        val scale = ContentScale.Inside.scale(
+        val scale = ContentScale.Inside.computeScaleFactor(
             srcSize = Size(400.0f, 100.0f),
             dstSize = Size(900.0f, 200.0f)
         )
-        assertEquals(1.0f, scale)
+        assertEquals(1.0f, scale.scaleX)
+        assertEquals(1.0f, scale.scaleY)
     }
 
     @Test
     fun testContentFitInsideLargeDst() {
-        val scale = ContentScale.Fit.scale(
+        val scale = ContentScale.Fit.computeScaleFactor(
             srcSize = Size(400.0f, 100.0f),
             dstSize = Size(900.0f, 200.0f)
         )
-        assertEquals(2.0f, scale)
+        assertEquals(2.0f, scale.scaleX)
+        assertEquals(2.0f, scale.scaleY)
     }
 
     @Test
     fun testContentScaleCropWidth() {
-        val scale = ContentScale.Crop.scale(
+        val scale = ContentScale.Crop.computeScaleFactor(
             srcSize = Size(100.0f, 400.0f),
             dstSize = Size(200.0f, 200.0f)
         )
-        assertEquals(2.00f, scale)
+        assertEquals(2.00f, scale.scaleX)
+        assertEquals(2.00f, scale.scaleY)
     }
 
     @Test
     fun testContentScaleCropHeight() {
-        val scale = ContentScale.Crop.scale(
+        val scale = ContentScale.Crop.computeScaleFactor(
             srcSize = Size(300.0f, 100.0f),
             dstSize = Size(200.0f, 200.0f)
         )
-        assertEquals(2.00f, scale)
+        assertEquals(2.00f, scale.scaleX)
+        assertEquals(2.00f, scale.scaleY)
+    }
+
+    @Test
+    fun testContentScaleFillBoundsUp() {
+        val scale = ContentScale.FillBounds.computeScaleFactor(
+            srcSize = Size(100f, 100f),
+            dstSize = Size(300f, 700f)
+        )
+        assertEquals(3.0f, scale.scaleX)
+        assertEquals(7.0f, scale.scaleY)
     }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/layout/ScaleFactorTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/layout/ScaleFactorTest.kt
new file mode 100644
index 0000000..f2b8c87
--- /dev/null
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/layout/ScaleFactorTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.layout
+
+import androidx.compose.ui.geometry.Size
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ScaleFactorTest {
+
+    @Test
+    fun testScaleFactorConstructor() {
+        val scaleFactor = ScaleFactor(2f, 3f)
+        assertEquals(2f, scaleFactor.scaleX)
+        assertEquals(3f, scaleFactor.scaleY)
+    }
+
+    @Test
+    fun testDestructuring() {
+        val (scaleX, scaleY) = ScaleFactor(7f, 12f)
+        assertEquals(7f, scaleX)
+        assertEquals(12f, scaleY)
+    }
+
+    @Test
+    fun testCopy() {
+        val scaleFactor = ScaleFactor(11f, 4f)
+        assertEquals(scaleFactor, scaleFactor.copy())
+    }
+
+    @Test
+    fun testCopyOverwriteScaleX() {
+        val scaleFactor = ScaleFactor(7f, 2f)
+        assertEquals(ScaleFactor(3f, 2f), scaleFactor.copy(scaleX = 3f))
+    }
+
+    @Test
+    fun testCopyOverwriteScaleY() {
+        val scaleFactor = ScaleFactor(2f, 9f)
+        assertEquals(ScaleFactor(2f, 27f), scaleFactor.copy(scaleY = 27f))
+    }
+
+    @Test
+    fun testScaleFactorMultiplication() {
+        assertEquals(ScaleFactor(2f, 8f), ScaleFactor(1f, 4f) * 2f)
+    }
+
+    @Test
+    fun testScaleFactorDivision() {
+        assertEquals(ScaleFactor(1f, 4f), ScaleFactor(2f, 8f) / 2f)
+    }
+
+    @Test
+    fun testUnspecifiedScaleXQueryThrows() {
+        try {
+            ScaleFactor.Unspecified.scaleX
+            fail("Attempt to access ScaleFactor.Unspecified.scaleX is not allowed")
+        } catch (e: Throwable) {
+            // no-op
+        }
+    }
+
+    @Test
+    fun testUnspecifiedScaleYQueryThrows() {
+        try {
+            ScaleFactor.Unspecified.scaleY
+            fail("Attempt to access ScaleFactor.Unspecified.scaleY is not allowed")
+        } catch (e: Throwable) {
+            // no-op
+        }
+    }
+
+    @Test
+    fun testSizeMultiplication() {
+        val scaleFactor = ScaleFactor(2f, 3f)
+        val size = Size(100f, 200f)
+        val expected = Size(200f, 600f)
+        // verify commutative property of multiplication
+        assertEquals(expected, size * scaleFactor)
+        assertEquals(expected, scaleFactor * size)
+    }
+
+    @Test
+    fun testScaleFactorLerp() {
+        val scaleFactor1 = ScaleFactor(1f, 10f)
+        val scaleFactor2 = ScaleFactor(3f, 20f)
+        assertEquals(ScaleFactor(2f, 15f), lerp(scaleFactor1, scaleFactor2, 0.5f))
+    }
+
+    @Test
+    fun testSizeDivision() {
+        assertEquals(Size(1f, 2f), Size(100f, 300f) / ScaleFactor(100f, 150f))
+    }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 5fae244..fae9aa0 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -148,9 +148,9 @@
 includeProject(":benchmark:benchmark-gradle-plugin", "benchmark/gradle-plugin", [BuildType.MAIN])
 includeProject(":benchmark:benchmark-junit4", "benchmark/junit4")
 includeProject(":benchmark:benchmark-macro", "benchmark/macro", [BuildType.MAIN])
-includeProject(":benchmark:benchmark-macro-runtime", "benchmark/benchmark-macro-runtime", [BuildType.MAIN])
+includeProject(":benchmark:benchmark-macro-runtime", "benchmark/benchmark-macro-runtime", [BuildType.MAIN, BuildType.COMPOSE])
+includeProject(":benchmark:benchmark-perfetto", "benchmark/perfetto", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":benchmark:benchmark-simple-macro-benchmark", "benchmark/benchmark-simple-macro-benchmark", [BuildType.MAIN])
-includeProject(":benchmark:benchmark-perfetto", "benchmark/perfetto", [BuildType.MAIN])
 includeProject(":benchmark:integration-tests:dry-run-benchmark", "benchmark/integration-tests/dry-run-benchmark", [BuildType.MAIN])
 includeProject(":benchmark:integration-tests:startup-benchmark", "benchmark/integration-tests/startup-benchmark", [BuildType.MAIN])
 includeProject(":benchmark:integration-tests:benchmark-simple-macro-benchmark-target", "benchmark/integration-tests/benchmark-simple-macro-benchmark-target", [BuildType.MAIN])
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index 76987d6..2741333 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -50,11 +50,18 @@
   public final class WindowManager {
     ctor public WindowManager(android.content.Context);
     ctor public WindowManager(android.content.Context, androidx.window.WindowBackend);
+    method public androidx.window.WindowMetrics getCurrentWindowMetrics();
+    method public androidx.window.WindowMetrics getMaximumWindowMetrics();
     method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void registerLayoutChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
     method public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
   }
 
+  public final class WindowMetrics {
+    ctor public WindowMetrics(android.graphics.Rect);
+    method public android.graphics.Rect getBounds();
+  }
+
 }
 
diff --git a/window/window/api/public_plus_experimental_current.txt b/window/window/api/public_plus_experimental_current.txt
index 76987d6..2741333 100644
--- a/window/window/api/public_plus_experimental_current.txt
+++ b/window/window/api/public_plus_experimental_current.txt
@@ -50,11 +50,18 @@
   public final class WindowManager {
     ctor public WindowManager(android.content.Context);
     ctor public WindowManager(android.content.Context, androidx.window.WindowBackend);
+    method public androidx.window.WindowMetrics getCurrentWindowMetrics();
+    method public androidx.window.WindowMetrics getMaximumWindowMetrics();
     method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void registerLayoutChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
     method public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
   }
 
+  public final class WindowMetrics {
+    ctor public WindowMetrics(android.graphics.Rect);
+    method public android.graphics.Rect getBounds();
+  }
+
 }
 
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index 76987d6..2741333 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -50,11 +50,18 @@
   public final class WindowManager {
     ctor public WindowManager(android.content.Context);
     ctor public WindowManager(android.content.Context, androidx.window.WindowBackend);
+    method public androidx.window.WindowMetrics getCurrentWindowMetrics();
+    method public androidx.window.WindowMetrics getMaximumWindowMetrics();
     method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void registerLayoutChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
     method public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
   }
 
+  public final class WindowMetrics {
+    ctor public WindowMetrics(android.graphics.Rect);
+    method public android.graphics.Rect getBounds();
+  }
+
 }
 
diff --git a/window/window/src/androidTest/java/androidx/window/TestWindowBoundsHelper.java b/window/window/src/androidTest/java/androidx/window/TestWindowBoundsHelper.java
index 90d3156..15a069e 100644
--- a/window/window/src/androidTest/java/androidx/window/TestWindowBoundsHelper.java
+++ b/window/window/src/androidTest/java/androidx/window/TestWindowBoundsHelper.java
@@ -33,6 +33,7 @@
 class TestWindowBoundsHelper extends WindowBoundsHelper {
     private Rect mGlobalOverriddenBounds;
     private final HashMap<Activity, Rect> mOverriddenBounds = new HashMap<>();
+    private final HashMap<Activity, Rect> mOverriddenMaximumBounds = new HashMap<>();
 
     /**
      * Overrides the bounds returned from this helper for the given context. Passing null {@code
@@ -46,6 +47,14 @@
     }
 
     /**
+     * Overrides the max bounds returned from this helper for the given context. Passing {@code
+     * null} {@code bounds} has the effect of clearing the bounds override.
+     */
+    void setMaximumBoundsForActivity(@NonNull Activity activity, @Nullable Rect bounds) {
+        mOverriddenMaximumBounds.put(activity, bounds);
+    }
+
+    /**
      * Overrides the bounds returned from this helper for all supplied contexts. Passing null
      * {@code bounds} has the effect of clearing the global override.
      */
@@ -68,6 +77,17 @@
         return super.computeCurrentWindowBounds(activity);
     }
 
+    @NonNull
+    @Override
+    Rect computeMaximumWindowBounds(Activity activity) {
+        Rect bounds = mOverriddenMaximumBounds.get(activity);
+        if (bounds != null) {
+            return bounds;
+        }
+
+        return super.computeMaximumWindowBounds(activity);
+    }
+
     /**
      * Clears any overrides set with {@link #setCurrentBounds(Rect)} or
      * {@link #setCurrentBoundsForActivity(Activity, Rect)}.
@@ -75,5 +95,6 @@
     void reset() {
         mGlobalOverriddenBounds = null;
         mOverriddenBounds.clear();
+        mOverriddenMaximumBounds.clear();
     }
 }
diff --git a/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java b/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java
index 93cfa19..cf6eab3 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java
+++ b/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.graphics.Point;
@@ -45,7 +46,9 @@
             new ActivityScenarioRule<>(TestActivity.class);
 
     @Test
-    public void testGetCurrentWindowBounds_matchParentWindowSize_avoidCutouts() {
+    public void testGetCurrentWindowBounds_matchParentWindowSize_avoidCutouts_preR() {
+        assumePlatformBeforeR();
+
         testGetCurrentWindowBoundsMatchesRealDisplaySize(activity -> {
             assumeFalse(isInMultiWindowMode(activity));
 
@@ -61,7 +64,9 @@
     }
 
     @Test
-    public void testGetCurrentWindowBounds_fixedWindowSize_avoidCutouts() {
+    public void testGetCurrentWindowBounds_fixedWindowSize_avoidCutouts_preR() {
+        assumePlatformBeforeR();
+
         testGetCurrentWindowBoundsMatchesRealDisplaySize(activity -> {
             assumeFalse(isInMultiWindowMode(activity));
 
@@ -77,7 +82,9 @@
     }
 
     @Test
-    public void testGetCurrentWindowBounds_matchParentWindowSize_layoutBehindCutouts() {
+    public void testGetCurrentWindowBounds_matchParentWindowSize_layoutBehindCutouts_preR() {
+        assumePlatformBeforeR();
+
         testGetCurrentWindowBoundsMatchesRealDisplaySize(activity -> {
             assumeFalse(isInMultiWindowMode(activity));
 
@@ -93,7 +100,9 @@
     }
 
     @Test
-    public void testGetCurrentWindowBounds_fixedWindowSize_layoutBehindCutouts() {
+    public void testGetCurrentWindowBounds_fixedWindowSize_layoutBehindCutouts_preR() {
+        assumePlatformBeforeR();
+
         testGetCurrentWindowBoundsMatchesRealDisplaySize(activity -> {
             assumeFalse(isInMultiWindowMode(activity));
 
@@ -108,22 +117,137 @@
         });
     }
 
+    @Test
+    public void testGetCurrentWindowBounds_postR() {
+        assumePlatformROrAbove();
+
+        runActionsAcrossActivityLifecycle(activity -> { }, activity -> {
+            Rect bounds = WindowBoundsHelper.getInstance().computeCurrentWindowBounds(activity);
+            Rect windowMetricsBounds =
+                    activity.getWindowManager().getCurrentWindowMetrics().getBounds();
+            assertEquals(windowMetricsBounds, bounds);
+        });
+    }
+
+    @Test
+    public void testGetMaximumWindowBounds_matchParentWindowSize_avoidCutouts_preR() {
+        assumePlatformBeforeR();
+
+        testGetMaximumWindowBoundsMatchesRealDisplaySize(activity -> {
+            assumeFalse(isInMultiWindowMode(activity));
+
+            WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
+            lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+            lp.height = WindowManager.LayoutParams.MATCH_PARENT;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                lp.layoutInDisplayCutoutMode =
+                        WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
+            }
+            activity.getWindow().setAttributes(lp);
+        });
+    }
+
+    @Test
+    public void testGetMaximumWindowBounds_fixedWindowSize_avoidCutouts_preR() {
+        assumePlatformBeforeR();
+
+        testGetMaximumWindowBoundsMatchesRealDisplaySize(activity -> {
+            assumeFalse(isInMultiWindowMode(activity));
+
+            WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
+            lp.width = 100;
+            lp.height = 100;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                lp.layoutInDisplayCutoutMode =
+                        WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
+            }
+            activity.getWindow().setAttributes(lp);
+        });
+    }
+
+    @Test
+    public void testGetMaximumWindowBounds_matchParentWindowSize_layoutBehindCutouts_preR() {
+        assumePlatformBeforeR();
+
+        testGetMaximumWindowBoundsMatchesRealDisplaySize(activity -> {
+            assumeFalse(isInMultiWindowMode(activity));
+
+            WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
+            lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+            lp.height = WindowManager.LayoutParams.MATCH_PARENT;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                lp.layoutInDisplayCutoutMode =
+                        WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+            }
+            activity.getWindow().setAttributes(lp);
+        });
+    }
+
+    @Test
+    public void testGetMaximumWindowBounds_fixedWindowSize_layoutBehindCutouts_preR() {
+        assumePlatformBeforeR();
+
+        testGetMaximumWindowBoundsMatchesRealDisplaySize(activity -> {
+            assumeFalse(isInMultiWindowMode(activity));
+
+            WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
+            lp.width = 100;
+            lp.height = 100;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                lp.layoutInDisplayCutoutMode =
+                        WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+            }
+            activity.getWindow().setAttributes(lp);
+        });
+    }
+
+    @Test
+    public void testGetMaximumWindowBounds_postR() {
+        assumePlatformROrAbove();
+
+        runActionsAcrossActivityLifecycle(activity -> { }, activity -> {
+            Rect bounds = WindowBoundsHelper.getInstance().computeMaximumWindowBounds(activity);
+            Rect windowMetricsBounds =
+                    activity.getWindowManager().getMaximumWindowMetrics().getBounds();
+            assertEquals(windowMetricsBounds, bounds);
+        });
+    }
+
     private void testGetCurrentWindowBoundsMatchesRealDisplaySize(
             ActivityScenario.ActivityAction<TestActivity> initialAction) {
+        ActivityScenario.ActivityAction<TestActivity> assertWindowBoundsMatchesDisplayAction =
+                new AssertCurrentWindowBoundsEqualsRealDisplaySizeAction();
+        runActionsAcrossActivityLifecycle(initialAction, assertWindowBoundsMatchesDisplayAction);
+    }
+
+    private void testGetMaximumWindowBoundsMatchesRealDisplaySize(
+            ActivityScenario.ActivityAction<TestActivity> initialAction) {
+        ActivityScenario.ActivityAction<TestActivity> assertWindowBoundsMatchesDisplayAction =
+                new AssertMaximumWindowBoundsEqualsRealDisplaySizeAction();
+        runActionsAcrossActivityLifecycle(initialAction, assertWindowBoundsMatchesDisplayAction);
+    }
+
+    /**
+     * Creates and launches an activity performing the supplied actions at various points in the
+     * activity lifecycle.
+     *
+     * @param initialAction the action that will run once before the activity is created.
+     * @param verifyAction the action to run once after each change in activity lifecycle state.
+     */
+    private void runActionsAcrossActivityLifecycle(
+            ActivityScenario.ActivityAction<TestActivity> initialAction,
+            ActivityScenario.ActivityAction<TestActivity> verifyAction) {
         ActivityScenario<TestActivity> scenario = mActivityScenarioRule.getScenario();
         scenario.onActivity(initialAction);
 
-        ActivityScenario.ActivityAction<TestActivity> assertWindowBoundsAction =
-                new AssertWindowBoundsEqualsRealDisplaySizeAction();
-
         scenario.moveToState(Lifecycle.State.CREATED);
-        scenario.onActivity(assertWindowBoundsAction);
+        scenario.onActivity(verifyAction);
 
         scenario.moveToState(Lifecycle.State.STARTED);
-        scenario.onActivity(assertWindowBoundsAction);
+        scenario.onActivity(verifyAction);
 
         scenario.moveToState(Lifecycle.State.RESUMED);
-        scenario.onActivity(assertWindowBoundsAction);
+        scenario.onActivity(verifyAction);
     }
 
     private static boolean isInMultiWindowMode(Activity activity) {
@@ -133,7 +257,15 @@
         return false;
     }
 
-    private static final class AssertWindowBoundsEqualsRealDisplaySizeAction implements
+    private static void assumePlatformBeforeR() {
+        assumeTrue(Build.VERSION.SDK_INT < Build.VERSION_CODES.R);
+    }
+
+    private static void assumePlatformROrAbove() {
+        assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+    }
+
+    private static final class AssertCurrentWindowBoundsEqualsRealDisplaySizeAction implements
             ActivityScenario.ActivityAction<TestActivity> {
         @Override
         public void perform(TestActivity activity) {
@@ -153,4 +285,25 @@
                     realDisplaySize.y, bounds.height());
         }
     }
+
+    private static final class AssertMaximumWindowBoundsEqualsRealDisplaySizeAction implements
+            ActivityScenario.ActivityAction<TestActivity> {
+        @Override
+        public void perform(TestActivity activity) {
+            Display display;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+                display = activity.getDisplay();
+            } else {
+                display = activity.getWindowManager().getDefaultDisplay();
+            }
+
+            Point realDisplaySize = WindowBoundsHelper.getRealSizeForDisplay(display);
+
+            Rect bounds = WindowBoundsHelper.getInstance().computeMaximumWindowBounds(activity);
+            assertEquals("Window bounds width does not match real display width",
+                    realDisplaySize.x, bounds.width());
+            assertEquals("Window bounds height does not match real display height",
+                    realDisplaySize.y, bounds.height());
+        }
+    }
 }
diff --git a/window/window/src/androidTest/java/androidx/window/WindowManagerTest.java b/window/window/src/androidTest/java/androidx/window/WindowManagerTest.java
index 957e9cb..dddc9ef 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowManagerTest.java
+++ b/window/window/src/androidTest/java/androidx/window/WindowManagerTest.java
@@ -16,12 +16,15 @@
 
 package androidx.window;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
 import android.content.ContextWrapper;
+import android.graphics.Rect;
 
 import androidx.core.util.Consumer;
 import androidx.test.core.app.ApplicationProvider;
@@ -30,6 +33,7 @@
 
 import com.google.common.util.concurrent.MoreExecutors;
 
+import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,6 +44,11 @@
 @RunWith(AndroidJUnit4.class)
 public final class WindowManagerTest extends WindowTestBase {
 
+    @After
+    public void tearDown() {
+        WindowBoundsHelper.setForTesting(null);
+    }
+
     @Test
     public void testConstructor_activity() {
         new WindowManager(mock(Activity.class), mock(WindowBackend.class));
@@ -89,4 +98,36 @@
         wm.unregisterDeviceStateChangeCallback(consumer);
         verify(backend).unregisterDeviceStateChangeCallback(eq(consumer));
     }
+
+    @Test
+    public void testGetCurrentWindowMetrics() {
+        WindowBackend backend = mock(WindowBackend.class);
+        Activity activity = mock(Activity.class);
+        WindowManager wm = new WindowManager(activity, backend);
+
+        Rect bounds = new Rect(1, 2, 3, 4);
+        TestWindowBoundsHelper mWindowBoundsHelper = new TestWindowBoundsHelper();
+        mWindowBoundsHelper.setCurrentBoundsForActivity(activity, bounds);
+        WindowBoundsHelper.setForTesting(mWindowBoundsHelper);
+
+        WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
+        assertNotNull(windowMetrics);
+        assertEquals(bounds, windowMetrics.getBounds());
+    }
+
+    @Test
+    public void testGetMaximumWindowMetrics() {
+        WindowBackend backend = mock(WindowBackend.class);
+        Activity activity = mock(Activity.class);
+        WindowManager wm = new WindowManager(activity, backend);
+
+        Rect bounds = new Rect(0, 2, 4, 5);
+        TestWindowBoundsHelper mWindowBoundsHelper = new TestWindowBoundsHelper();
+        mWindowBoundsHelper.setMaximumBoundsForActivity(activity, bounds);
+        WindowBoundsHelper.setForTesting(mWindowBoundsHelper);
+
+        WindowMetrics windowMetrics = wm.getMaximumWindowMetrics();
+        assertNotNull(windowMetrics);
+        assertEquals(bounds, windowMetrics.getBounds());
+    }
 }
diff --git a/window/window/src/androidTest/java/androidx/window/WindowMetricsTest.java b/window/window/src/androidTest/java/androidx/window/WindowMetricsTest.java
new file mode 100644
index 0000000..126e31e
--- /dev/null
+++ b/window/window/src/androidTest/java/androidx/window/WindowMetricsTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.window;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.graphics.Rect;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link WindowMetrics} class. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class WindowMetricsTest {
+
+    @Test
+    public void testGetBounds() {
+        Rect bounds = new Rect(1, 2, 3, 4);
+        WindowMetrics windowMetrics = new WindowMetrics(bounds);
+        assertEquals(bounds, windowMetrics.getBounds());
+    }
+
+    @Test
+    public void testEquals_sameBounds() {
+        Rect bounds = new Rect(1, 2, 3, 4);
+        WindowMetrics windowMetrics0 = new WindowMetrics(bounds);
+        WindowMetrics windowMetrics1 = new WindowMetrics(bounds);
+
+        assertEquals(windowMetrics0, windowMetrics1);
+    }
+
+    @Test
+    public void testEquals_differentBounds() {
+        Rect bounds0 = new Rect(1, 2, 3, 4);
+        WindowMetrics windowMetrics0 = new WindowMetrics(bounds0);
+
+        Rect bounds1 = new Rect(6, 7, 8, 9);
+        WindowMetrics windowMetrics1 = new WindowMetrics(bounds1);
+
+        assertNotEquals(windowMetrics0, windowMetrics1);
+    }
+
+    @Test
+    public void testHashCode_matchesIfEqual() {
+        Rect bounds = new Rect(1, 2, 3, 4);
+        WindowMetrics windowMetrics0 = new WindowMetrics(bounds);
+        WindowMetrics windowMetrics1 = new WindowMetrics(bounds);
+
+        assertEquals(windowMetrics0.hashCode(), windowMetrics1.hashCode());
+    }
+}
diff --git a/window/window/src/main/java/androidx/window/WindowBoundsHelper.java b/window/window/src/main/java/androidx/window/WindowBoundsHelper.java
index 882f30c..d2db428 100644
--- a/window/window/src/main/java/androidx/window/WindowBoundsHelper.java
+++ b/window/window/src/main/java/androidx/window/WindowBoundsHelper.java
@@ -104,14 +104,13 @@
      * <p>
      * Note: The value of this is based on the last windowing state reported to the client.
      *
+     * @see android.view.WindowManager#getCurrentWindowMetrics()
      * @see android.view.WindowMetrics#getBounds()
      */
     @NonNull
     Rect computeCurrentWindowBounds(Activity activity) {
         if (Build.VERSION.SDK_INT >= R) {
-            android.view.WindowManager platformWindowManager =
-                    activity.getSystemService(android.view.WindowManager.class);
-            return platformWindowManager.getCurrentWindowMetrics().getBounds();
+            return activity.getWindowManager().getCurrentWindowMetrics().getBounds();
         } else if (Build.VERSION.SDK_INT >= Q) {
             return computeWindowBoundsQ(activity);
         } else if (Build.VERSION.SDK_INT >= P) {
@@ -123,6 +122,27 @@
         }
     }
 
+    /**
+     * Computes the maximum size and position of the area the window can expect with
+     * {@link android.view.WindowManager.LayoutParams#MATCH_PARENT MATCH_PARENT} width and height
+     * and any combination of flags that would allow the window to extend behind display cutouts.
+     * <p>
+     * The value returned from this method will always match {@link Display#getRealSize(Point)} on
+     * {@link Build.VERSION_CODES#Q Android 10} and below.
+     *
+     * @see android.view.WindowManager#getMaximumWindowMetrics()
+     */
+    @NonNull
+    Rect computeMaximumWindowBounds(Activity activity) {
+        if (Build.VERSION.SDK_INT >= R) {
+            return activity.getWindowManager().getMaximumWindowMetrics().getBounds();
+        } else {
+            Display display = activity.getWindowManager().getDefaultDisplay();
+            Point displaySize = getRealSizeForDisplay(display);
+            return new Rect(0, 0, displaySize.x, displaySize.y);
+        }
+    }
+
     /** Computes the window bounds for {@link Build.VERSION_CODES#Q}. */
     @NonNull
     @RequiresApi(Q)
diff --git a/window/window/src/main/java/androidx/window/WindowManager.java b/window/window/src/main/java/androidx/window/WindowManager.java
index 9304d49..76c3633 100644
--- a/window/window/src/main/java/androidx/window/WindowManager.java
+++ b/window/window/src/main/java/androidx/window/WindowManager.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.graphics.Rect;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -107,6 +108,57 @@
     }
 
     /**
+     * Returns the {@link WindowMetrics} according to the current system state.
+     * <p>
+     * The metrics describe the size of the area the window would occupy with
+     * {@link android.view.WindowManager.LayoutParams#MATCH_PARENT MATCH_PARENT} width and height
+     * and any combination of flags that would allow the window to extend behind display cutouts.
+     * <p>
+     * The value of this is based on the <b>current</b> windowing state of the system. For
+     * example, for activities in multi-window mode, the metrics returned are based on the
+     * current bounds that the user has selected for the {@link android.app.Activity Activity}'s
+     * window.
+     *
+     * @see #getMaximumWindowMetrics()
+     * @see android.view.WindowManager#getCurrentWindowMetrics()
+     */
+    @NonNull
+    public WindowMetrics getCurrentWindowMetrics() {
+        Activity activity = getActivityFromContext(mContext);
+        Rect currentBounds = WindowBoundsHelper.getInstance().computeCurrentWindowBounds(activity);
+        return new WindowMetrics(currentBounds);
+    }
+
+    /**
+     * Returns the largest {@link WindowMetrics} an app may expect in the current system state.
+     * <p>
+     * The metrics describe the size of the largest potential area the window might occupy with
+     * {@link android.view.WindowManager.LayoutParams#MATCH_PARENT MATCH_PARENT} width and height
+     * and any combination of flags that would allow the window to extend behind display cutouts.
+     * <p>
+     * The value of this is based on the largest <b>potential</b> windowing state of the system.
+     * For example, for activities in multi-window mode the metrics returned are based on what the
+     * bounds would be if the user expanded the window to cover the entire screen.
+     * <p>
+     * Note that this might still be smaller than the size of the physical display if certain
+     * areas of the display are not available to windows created for the associated {@link Context}.
+     * For example, devices with foldable displays that wrap around the enclosure may split the
+     * physical display into different regions, one for the front and one for the back, each acting
+     * as different logical displays. In this case {@link #getMaximumWindowMetrics()} would return
+     * the region describing the side of the device the associated {@link Context context's}
+     * window is placed.
+     *
+     * @see #getCurrentWindowMetrics()
+     * @see android.view.WindowManager#getMaximumWindowMetrics()
+     */
+    @NonNull
+    public WindowMetrics getMaximumWindowMetrics() {
+        Activity activity = getActivityFromContext(mContext);
+        Rect maxBounds = WindowBoundsHelper.getInstance().computeMaximumWindowBounds(activity);
+        return new WindowMetrics(maxBounds);
+    }
+
+    /**
      * Unwraps the hierarchy of {@link ContextWrapper}-s until {@link Activity} is reached.
      * @return Base {@link Activity} context or {@code null} if not available.
      */
diff --git a/window/window/src/main/java/androidx/window/WindowMetrics.java b/window/window/src/main/java/androidx/window/WindowMetrics.java
new file mode 100644
index 0000000..9faa5f6
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/WindowMetrics.java
@@ -0,0 +1,75 @@
+/*
+ * 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.window;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Display;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Metrics about a {@link android.view.Window}, consisting of its bounds.
+ * <p>
+ * This is usually obtained from {@link WindowManager#getCurrentWindowMetrics()} or
+ * {@link WindowManager#getMaximumWindowMetrics()}.
+ *
+ * @see WindowManager#getCurrentWindowMetrics()
+ * @see WindowManager#getMaximumWindowMetrics()
+ */
+public final class WindowMetrics {
+    @NonNull
+    private final Rect mBounds;
+
+    /**
+     * Constructs a new {@link WindowMetrics} instance.
+     *
+     * @param bounds rect describing the bounds of the window, see {@link #getBounds}.
+     */
+    public WindowMetrics(@NonNull Rect bounds) {
+        mBounds = new Rect(bounds);
+    }
+
+    /**
+     * Returns a new {@link Rect} describing the bounds of the area the window occupies.
+     * <p>
+     * <b>Note that the size of the reported bounds can have different size than
+     * {@link Display#getSize(Point)}.</b> This method reports the window size including all system
+     * decorations, while {@link Display#getSize(Point)} reports the area excluding navigation bars
+     * and display cutout areas.
+     *
+     * @return window bounds in pixels.
+     */
+    @NonNull
+    public Rect getBounds() {
+        return new Rect(mBounds);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        WindowMetrics that = (WindowMetrics) o;
+        return mBounds.equals(that.mBounds);
+    }
+
+    @Override
+    public int hashCode() {
+        return mBounds.hashCode();
+    }
+}