Merge "Add DisableAbortCapturesOnStopQuirk" into androidx-main
diff --git a/busytown/impl/merge-kzips.sh b/busytown/impl/merge-kzips.sh
index 6178d47..100f04f 100755
--- a/busytown/impl/merge-kzips.sh
+++ b/busytown/impl/merge-kzips.sh
@@ -39,13 +39,10 @@
 mkdir -p "$DIST_DIR"
 export DIST_DIR="$DIST_DIR"
 
+# Default KZIP_NAME to the latest Git commit hash
+: ${KZIP_NAME:=$(git rev-parse HEAD)}
 
-# If the SUPERPROJECT_REVISION is defined as a sha, use this as the default value
-if [[ ${SUPERPROJECT_REVISION:-} =~ [0-9a-f]{40} ]]; then
-  : ${KZIP_NAME:=${SUPERPROJECT_REVISION:-}}
-fi
-
-: ${KZIP_NAME:=${BUILD_NUMBER:-}}
+# Fallback to a UUID if Git commit hash is not there
 : ${KZIP_NAME:=$(uuidgen)}
 
 
@@ -61,4 +58,3 @@
 fi
 
 "$BUILD_TOOLS_DIR/merge_zips" "$DIST_DIR/$allkzip" @<(find "$OUT_DIR/androidx" -name '*.kzip')
-
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index 0dc50be..46ef6e5 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -71,12 +71,12 @@
 import android.provider.MediaStore;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Range;
 import android.util.Rational;
 import android.view.Display;
 import android.view.GestureDetector;
 import android.view.Menu;
+import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -130,9 +130,11 @@
 import androidx.camera.core.ViewPort;
 import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.camera.core.impl.Quirks;
+import androidx.camera.core.impl.StreamSpec;
 import androidx.camera.core.impl.utils.AspectRatioUtil;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.camera.testing.impl.StreamSharingForceEnabledEffect;
 import androidx.camera.video.ExperimentalPersistentRecording;
 import androidx.camera.video.FileOutputOptions;
 import androidx.camera.video.MediaStoreOutputOptions;
@@ -166,10 +168,12 @@
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -192,7 +196,12 @@
     private static final String TAG = "CameraXActivity";
     private static final String[] REQUIRED_PERMISSIONS;
     private static final List<DynamicRangeUiData> DYNAMIC_RANGE_UI_DATA = new ArrayList<>();
-    private static final List<Pair<Range<Integer>, String>> FPS_OPTIONS = new ArrayList<>();
+
+    // StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED is not public
+    @SuppressLint("RestrictedApiAndroidX")
+    private static final Range<Integer> FPS_UNSPECIFIED = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED;
+    private static final Map<Integer, Range<Integer>> ID_TO_FPS_RANGE_MAP = new HashMap<>();
+    private static final Map<Integer, Integer> ID_TO_ASPECT_RATIO_MAP = new HashMap<>();
 
     static {
         // From Android T, skips the permission check of WRITE_EXTERNAL_STORAGE since it won't be
@@ -243,10 +252,14 @@
         //  `CameraInfo.getSupportedFrameRateRanges()`, but we may want to try unsupported cases too
         //  sometimes for testing, so the unsupported ones still should be options (perhaps greyed
         //  out or struck-through).
-        FPS_OPTIONS.add(new Pair<>(new Range<>(0, 0), "Unspecified"));
-        FPS_OPTIONS.add(new Pair<>(new Range<>(15, 15), "15"));
-        FPS_OPTIONS.add(new Pair<>(new Range<>(30, 30), "30"));
-        FPS_OPTIONS.add(new Pair<>(new Range<>(60, 60), "60"));
+        ID_TO_FPS_RANGE_MAP.put(R.id.fps_unspecified, FPS_UNSPECIFIED);
+        ID_TO_FPS_RANGE_MAP.put(R.id.fps_15, new Range<>(15, 15));
+        ID_TO_FPS_RANGE_MAP.put(R.id.fps_30, new Range<>(30, 30));
+        ID_TO_FPS_RANGE_MAP.put(R.id.fps_60, new Range<>(60, 60));
+
+        ID_TO_ASPECT_RATIO_MAP.put(R.id.aspect_ratio_default, AspectRatio.RATIO_DEFAULT);
+        ID_TO_ASPECT_RATIO_MAP.put(R.id.aspect_ratio_4_3, AspectRatio.RATIO_4_3);
+        ID_TO_ASPECT_RATIO_MAP.put(R.id.aspect_ratio_16_9, AspectRatio.RATIO_16_9);
     }
 
     //Use this activity title when Camera Pipe configuration is used by core test app
@@ -364,7 +377,6 @@
     private Button mZoomIn2XToggle;
     private Button mZoomResetToggle;
     private Button mButtonImageOutputFormat;
-    private Button mButtonFps;
     private Toast mEvToast = null;
     private Toast mPSToast = null;
     private ToggleButton mPreviewStabilizationToggle;
@@ -381,7 +393,9 @@
     private final Set<DynamicRange> mSelectableDynamicRanges = new HashSet<>();
     private int mVideoMirrorMode = MIRROR_MODE_ON_FRONT_ONLY;
     private boolean mIsPreviewStabilizationOn = false;
-    private int mFpsMenuId = 0;
+    private Range<Integer> mFpsRange = FPS_UNSPECIFIED;
+    private boolean mForceEnableStreamSharing;
+    private boolean mDisableViewPort;
 
     SessionMediaUriSet mSessionImagesUriSet = new SessionMediaUriSet();
     SessionMediaUriSet mSessionVideosUriSet = new SessionMediaUriSet();
@@ -1288,7 +1302,6 @@
         mZoomSeekBar.setVisibility(View.GONE);
         mZoomRatioLabel.setVisibility(View.GONE);
         mTextView.setVisibility(View.GONE);
-        mButtonFps.setVisibility(View.GONE);
 
         if (testCase.equals(PREVIEW_TEST_CASE) || testCase.equals(SWITCH_TEST_CASE)) {
             mTorchButton.setVisibility(View.GONE);
@@ -1401,10 +1414,11 @@
         mPlusEV.setEnabled(isExposureCompensationSupported());
         mDecEV.setEnabled(isExposureCompensationSupported());
         mZoomIn2XToggle.setEnabled(is2XZoomSupported());
-        mButtonFps.setEnabled(mPreviewToggle.isChecked() || mVideoToggle.isChecked());
 
         // this function may make some view visible again, so need to update for E2E tests again
         updateAppUIForE2ETest();
+
+        invalidateOptionsMenu();
     }
 
     // Set or reset content description for e2e testing.
@@ -1605,42 +1619,6 @@
                 findViewById(R.id.video_mute),
                 (newState) -> updateDynamicRangeUiState()
         );
-        mButtonFps = findViewById(R.id.fps);
-        if (mFpsMenuId == 0) {
-            mButtonFps.setText("FPS\nUnsp.");
-        } else {
-            mButtonFps.setText("FPS\n" + FPS_OPTIONS.get(mFpsMenuId).second);
-        }
-        mButtonFps.setOnClickListener(view -> {
-            PopupMenu popup = new PopupMenu(this, view);
-            Menu menu = popup.getMenu();
-
-            for (int i = 0; i < FPS_OPTIONS.size(); i++) {
-                menu.add(0, i, Menu.NONE, FPS_OPTIONS.get(i).second);
-            }
-
-            menu.findItem(mFpsMenuId).setChecked(true);
-
-            // Make menu single checkable
-            menu.setGroupCheckable(0, true, true);
-
-            popup.setOnMenuItemClickListener(item -> {
-                int itemId = item.getItemId();
-                if (itemId != mFpsMenuId) {
-                    mFpsMenuId = itemId;
-                    if (mFpsMenuId == 0) {
-                        mButtonFps.setText("FPS\nUnsp.");
-                    } else {
-                        mButtonFps.setText("FPS\n" + FPS_OPTIONS.get(mFpsMenuId).second);
-                    }
-                    // FPS changed, rebind UseCases
-                    tryBindUseCases();
-                }
-                return true;
-            });
-
-            popup.show();
-        });
 
         setUpButtonEvents();
         setupViewFinderGestureControls();
@@ -1758,6 +1736,83 @@
         setupPermissions();
     }
 
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.actionbar_menu, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        updateMenuItems(menu);
+        return true;
+    }
+
+    private void updateMenuItems(Menu menu) {
+        menu.findItem(requireNonNull(getKeyByValue(ID_TO_FPS_RANGE_MAP, mFpsRange))).setChecked(
+                true);
+        menu.findItem(R.id.fps).setEnabled(mPreviewToggle.isChecked() || mVideoToggle.isChecked());
+
+        menu.findItem(requireNonNull(
+                getKeyByValue(ID_TO_ASPECT_RATIO_MAP, mTargetAspectRatio))).setChecked(true);
+
+        menu.findItem(R.id.stream_sharing).setChecked(mForceEnableStreamSharing);
+        // StreamSharing requires both Preview & VideoCapture use cases in core-test-app
+        // (since ImageCapture can't be added due to lack of effect)
+        menu.findItem(R.id.stream_sharing).setEnabled(
+                mPreviewToggle.isChecked() && mVideoToggle.isChecked());
+
+        menu.findItem(R.id.view_port).setChecked(mDisableViewPort);
+    }
+
+    private static <T, E> T getKeyByValue(Map<T, E> map, E value) {
+        for (Map.Entry<T, E> entry : map.entrySet()) {
+            if (Objects.equals(value, entry.getValue())) {
+                return entry.getKey();
+            }
+        }
+        return null; // No key found for the given value
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+        // Handle item selection.
+        Log.d(TAG, "onOptionsItemSelected: item = " + item);
+
+        int groupId = item.getGroupId();
+        int itemId = item.getItemId();
+
+        if (groupId == R.id.fps_group) {
+            if (ID_TO_FPS_RANGE_MAP.containsKey(itemId)) {
+                mFpsRange = ID_TO_FPS_RANGE_MAP.get(itemId);
+            } else {
+                Log.e(TAG, "Unknown item " + item.getTitle());
+                return super.onOptionsItemSelected(item);
+            }
+        } else if (groupId == R.id.aspect_ratio_group) {
+            if (ID_TO_ASPECT_RATIO_MAP.containsKey(itemId)) {
+                mTargetAspectRatio = requireNonNull(ID_TO_ASPECT_RATIO_MAP.get(itemId));
+            } else {
+                Log.e(TAG, "Unknown item " + item.getTitle());
+                return super.onOptionsItemSelected(item);
+            }
+        } else if (itemId == R.id.stream_sharing) {
+            mForceEnableStreamSharing = !mForceEnableStreamSharing;
+        } else if (itemId == R.id.view_port) {
+            mDisableViewPort = !mDisableViewPort;
+        } else {
+            Log.d(TAG, "Not handling item " + item.getTitle());
+            return super.onOptionsItemSelected(item);
+        }
+
+        item.setChecked(!item.isChecked());
+
+        // Some configuration option may be changed, rebind UseCases
+        tryBindUseCases();
+
+        return super.onOptionsItemSelected(item);
+    }
+
     /**
      * Writes text data to a file in public external directory for reading during tests.
      */
@@ -1960,7 +2015,7 @@
                     .setPreviewStabilizationEnabled(mIsPreviewStabilizationOn)
                     .setDynamicRange(
                             mVideoToggle.isChecked() ? DynamicRange.UNSPECIFIED : mDynamicRange)
-                    .setTargetFrameRate(FPS_OPTIONS.get(mFpsMenuId).first)
+                    .setTargetFrameRate(mFpsRange)
                     .build();
             resetViewIdlingResource();
             // Use the listener of the future to make sure the Preview setup the new surface.
@@ -1987,6 +2042,7 @@
         if (mAnalysisToggle.isChecked()) {
             ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                     .setTargetName("ImageAnalysis")
+                    .setTargetAspectRatio(mTargetAspectRatio)
                     .build();
             useCases.add(imageAnalysis);
             // Make the analysis idling resource non-idle, until the required frames received.
@@ -2005,11 +2061,11 @@
                 if (mVideoQuality != QUALITY_AUTO) {
                     builder.setQualitySelector(QualitySelector.from(mVideoQuality));
                 }
-                mRecorder = builder.build();
+                mRecorder = builder.setAspectRatio(mTargetAspectRatio).build();
                 mVideoCapture = new VideoCapture.Builder<>(mRecorder)
                         .setMirrorMode(mVideoMirrorMode)
                         .setDynamicRange(mDynamicRange)
-                        .setTargetFrameRate(FPS_OPTIONS.get(mFpsMenuId).first)
+                        .setTargetFrameRate(mFpsRange)
                         .build();
             }
             useCases.add(mVideoCapture);
@@ -2122,15 +2178,29 @@
      * Binds use cases to the current lifecycle.
      */
     private Camera bindToLifecycleSafely(List<UseCase> useCases) {
-        ViewPort viewPort = new ViewPort.Builder(new Rational(mViewFinder.getWidth(),
-                mViewFinder.getHeight()),
-                mViewFinder.getDisplay().getRotation())
-                .setScaleType(ViewPort.FILL_CENTER).build();
-        UseCaseGroup.Builder useCaseGroupBuilder = new UseCaseGroup.Builder().setViewPort(
-                viewPort);
+        Log.d(TAG, "bindToLifecycleSafely: mDisableViewPort = " + mDisableViewPort
+                + ", mForceEnableStreamSharing = " + mForceEnableStreamSharing);
+
+        UseCaseGroup.Builder useCaseGroupBuilder = new UseCaseGroup.Builder();
         for (UseCase useCase : useCases) {
             useCaseGroupBuilder.addUseCase(useCase);
         }
+
+        if (!mDisableViewPort) {
+            ViewPort viewPort = new ViewPort.Builder(new Rational(mViewFinder.getWidth(),
+                    mViewFinder.getHeight()),
+                    mViewFinder.getDisplay().getRotation())
+                    .setScaleType(ViewPort.FILL_CENTER).build();
+            useCaseGroupBuilder.setViewPort(viewPort);
+        }
+
+        // Force-enable stream sharing
+        if (mForceEnableStreamSharing) {
+            @SuppressLint("RestrictedApiAndroidX")
+            StreamSharingForceEnabledEffect effect = new StreamSharingForceEnabledEffect();
+            useCaseGroupBuilder.addEffect(effect);
+        }
+
         mCamera = mCameraProvider.bindToLifecycle(this, mCurrentCameraSelector,
                 useCaseGroupBuilder.build());
         setupZoomSeeker();
diff --git a/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml b/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
index bc7e3db..c65182e 100644
--- a/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
@@ -437,20 +437,6 @@
         app:layout_constraintStart_toEndOf="@+id/seekBar"
         app:layout_constraintTop_toTopOf="@+id/seekBar" />
 
-    <Button
-        android:id="@+id/fps"
-        android:layout_width="46dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="5dp"
-        android:layout_marginTop="1dp"
-        android:background="@android:drawable/btn_default"
-        android:text="FPS"
-        android:textSize="7dp"
-        android:translationZ="1dp"
-        app:layout_constraintTop_toBottomOf="@id/preview_stabilization"
-        app:layout_constraintLeft_toLeftOf="parent"
-        />
-
     <androidx.camera.view.ScreenFlashView
         android:id="@+id/screen_flash_view"
         android:layout_width="match_parent"
diff --git a/camera/integration-tests/coretestapp/src/main/res/menu/actionbar_menu.xml b/camera/integration-tests/coretestapp/src/main/res/menu/actionbar_menu.xml
new file mode 100644
index 0000000..6e2c5fb
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/main/res/menu/actionbar_menu.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        android:id="@+id/fps"
+        android:title="@string/fps"
+        app:showAsAction="never">
+        <menu>
+            <group
+                android:id="@+id/fps_group"
+                android:checkableBehavior="single">
+                <item
+                    android:id="@+id/fps_unspecified"
+                    android:checked="true"
+                    android:title="Unspecified"
+                    app:showAsAction="never" />
+                <item
+                    android:id="@+id/fps_15"
+                    android:title="15"
+                    app:showAsAction="never" />
+                <item
+                    android:id="@+id/fps_30"
+                    android:title="30"
+                    app:showAsAction="never" />
+                <item
+                    android:id="@+id/fps_60"
+                    android:title="60"
+                    app:showAsAction="never" />
+            </group>
+        </menu>
+    </item>
+    <item
+        android:id="@+id/aspect_ratio"
+        android:title="@string/aspect_ratio"
+        app:showAsAction="never">
+        <menu>
+            <group
+                android:id="@+id/aspect_ratio_group"
+                android:checkableBehavior="single">
+                <item
+                    android:id="@+id/aspect_ratio_default"
+                    android:checked="true"
+                    android:title="Default"
+                    app:showAsAction="never" />
+                <item
+                    android:id="@+id/aspect_ratio_4_3"
+                    android:title="4:3"
+                    app:showAsAction="never" />
+                <item
+                    android:id="@+id/aspect_ratio_16_9"
+                    android:title="16:9"
+                    app:showAsAction="never" />
+            </group>
+        </menu>
+    </item>
+    <item
+        android:id="@+id/stream_sharing"
+        android:checkable="true"
+        android:title="@string/force_stream_sharing"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/view_port"
+        android:checkable="true"
+        android:title="@string/disable_view_port"
+        app:showAsAction="never" />
+
+</menu>
diff --git a/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml b/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml
index 949bfd4..34828bd 100644
--- a/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml
@@ -47,5 +47,9 @@
     <string name="toggle_video_dyn_rng_hdr_dolby_vision_10">Dlby\n10bit</string>
     <string name="toggle_video_dyn_rng_hdr_dolby_vision_8">Dlby\n8bit</string>
     <string name="toggle_video_dyn_rng_unknown">\?</string>
+    <string name="fps">FPS</string>
+    <string name="aspect_ratio">Aspect Ratio</string>
+    <string name="force_stream_sharing">Force Stream Sharing</string>
+    <string name="disable_view_port">Disable View Port</string>
 
 </resources>
\ No newline at end of file
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index fbb8549..22f9744 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -314,6 +314,46 @@
     property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldHorizontalOrder {
+    method public void forEach(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,kotlin.Unit> action);
+    method public void forEachIndexed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,kotlin.Unit> action);
+    method public void forEachIndexedReversed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,kotlin.Unit> action);
+    method public operator androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole get(int index);
+    method public int getSize();
+    method public int indexOf(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
+    property public int size;
+  }
+
+  public final class ThreePaneScaffoldKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverride> getLocalThreePaneScaffoldOverride();
+    property @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverride> LocalThreePaneScaffoldOverride;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface ThreePaneScaffoldOverride {
+    method @androidx.compose.runtime.Composable public void ThreePaneScaffold(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverrideContext);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldOverrideContext {
+    method public androidx.compose.ui.Modifier getModifier();
+    method public kotlin.jvm.functions.Function1<androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? getPaneExpansionDragHandle();
+    method public androidx.compose.material3.adaptive.layout.PaneExpansionState getPaneExpansionState();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder getPaneOrder();
+    method public kotlin.jvm.functions.Function0<kotlin.Unit> getPrimaryPane();
+    method public androidx.compose.material3.adaptive.layout.PaneScaffoldDirective getScaffoldDirective();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState getScaffoldState();
+    method public kotlin.jvm.functions.Function0<kotlin.Unit> getSecondaryPane();
+    method public kotlin.jvm.functions.Function0<kotlin.Unit>? getTertiaryPane();
+    property public final androidx.compose.ui.Modifier modifier;
+    property public final kotlin.jvm.functions.Function1<androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle;
+    property public final androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder paneOrder;
+    property public final kotlin.jvm.functions.Function0<kotlin.Unit> primaryPane;
+    property public final androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState;
+    property public final kotlin.jvm.functions.Function0<kotlin.Unit> secondaryPane;
+    property public final kotlin.jvm.functions.Function0<kotlin.Unit>? tertiaryPane;
+  }
+
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ThreePaneScaffoldPaneScope extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldPaneScope<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> {
   }
 
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index fbb8549..22f9744 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -314,6 +314,46 @@
     property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class ThreePaneScaffoldHorizontalOrder {
+    method public void forEach(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,kotlin.Unit> action);
+    method public void forEachIndexed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,kotlin.Unit> action);
+    method public void forEachIndexedReversed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,kotlin.Unit> action);
+    method public operator androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole get(int index);
+    method public int getSize();
+    method public int indexOf(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole role);
+    property public int size;
+  }
+
+  public final class ThreePaneScaffoldKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverride> getLocalThreePaneScaffoldOverride();
+    property @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverride> LocalThreePaneScaffoldOverride;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface ThreePaneScaffoldOverride {
+    method @androidx.compose.runtime.Composable public void ThreePaneScaffold(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverrideContext);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldOverrideContext {
+    method public androidx.compose.ui.Modifier getModifier();
+    method public kotlin.jvm.functions.Function1<androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? getPaneExpansionDragHandle();
+    method public androidx.compose.material3.adaptive.layout.PaneExpansionState getPaneExpansionState();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder getPaneOrder();
+    method public kotlin.jvm.functions.Function0<kotlin.Unit> getPrimaryPane();
+    method public androidx.compose.material3.adaptive.layout.PaneScaffoldDirective getScaffoldDirective();
+    method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState getScaffoldState();
+    method public kotlin.jvm.functions.Function0<kotlin.Unit> getSecondaryPane();
+    method public kotlin.jvm.functions.Function0<kotlin.Unit>? getTertiaryPane();
+    property public final androidx.compose.ui.Modifier modifier;
+    property public final kotlin.jvm.functions.Function1<androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle;
+    property public final androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder paneOrder;
+    property public final kotlin.jvm.functions.Function0<kotlin.Unit> primaryPane;
+    property public final androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective;
+    property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState;
+    property public final kotlin.jvm.functions.Function0<kotlin.Unit> secondaryPane;
+    property public final kotlin.jvm.functions.Function0<kotlin.Unit>? tertiaryPane;
+  }
+
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface ThreePaneScaffoldPaneScope extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope androidx.compose.material3.adaptive.layout.ExtendedPaneScaffoldPaneScope<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole,androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue> {
   }
 
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
index 2da0e85..df52d0c 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -19,6 +19,8 @@
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.ProvidableCompositionLocal
+import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -46,6 +48,54 @@
 import kotlin.math.max
 import kotlin.math.min
 
+/** Interface that allows libraries to override the behavior of [ThreePaneScaffold]. */
+@ExperimentalMaterial3AdaptiveApi
+interface ThreePaneScaffoldOverride {
+    /** Behavior function that is called by the [ThreePaneScaffold] composable. */
+    @Composable fun ThreePaneScaffoldOverrideContext.ThreePaneScaffold()
+}
+
+/**
+ * Parameters available to [ThreePaneScaffold].
+ *
+ * @property modifier The modifier to be applied to the layout.
+ * @property scaffoldDirective The top-level directives about how the scaffold should arrange its
+ *   panes.
+ * @property scaffoldState The current state of the scaffold, containing information about the
+ *   adapted value of each pane of the scaffold and the transitions/animations in progress.
+ * @property paneOrder The horizontal order of the panes from start to end in the scaffold.
+ * @property secondaryPane The content of the secondary pane that has a priority lower then the
+ *   primary pane but higher than the tertiary pane.
+ * @property tertiaryPane The content of the tertiary pane that has the lowest priority.
+ * @property primaryPane The content of the primary pane that has the highest priority.
+ * @property paneExpansionDragHandle the pane expansion drag handle to allow users to drag to change
+ *   pane expansion state, `null` by default.
+ * @property paneExpansionState the state object of pane expansion state.
+ */
+@ExperimentalMaterial3AdaptiveApi
+class ThreePaneScaffoldOverrideContext
+internal constructor(
+    val modifier: Modifier,
+    val scaffoldDirective: PaneScaffoldDirective,
+    val scaffoldState: ThreePaneScaffoldState,
+    val paneOrder: ThreePaneScaffoldHorizontalOrder,
+    val primaryPane: @Composable () -> Unit,
+    val secondaryPane: @Composable () -> Unit,
+    val tertiaryPane: (@Composable () -> Unit)?,
+    val paneExpansionState: PaneExpansionState,
+    val paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)?,
+    internal val motionScopeImpl: ThreePaneScaffoldMotionScopeImpl
+)
+
+/** CompositionLocal containing the currently-selected [ThreePaneScaffoldOverride]. */
+@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:ExperimentalMaterial3AdaptiveApi
+@ExperimentalMaterial3AdaptiveApi
+val LocalThreePaneScaffoldOverride: ProvidableCompositionLocal<ThreePaneScaffoldOverride> =
+    compositionLocalOf {
+        DefaultThreePaneScaffoldOverride
+    }
+
 /**
  * A pane scaffold composable that can display up to three panes according to the instructions
  * provided by [ThreePaneScaffoldValue] in the order that [ThreePaneScaffoldHorizontalOrder]
@@ -139,47 +189,69 @@
             remember(currentTransition, this) {
                 ThreePaneScaffoldScopeImpl(motionScope, transitionScope, this)
             }
-        // Create PaneWrappers for each of the panes and map the transitions according to each pane
-        // role and order.
+        with(LocalThreePaneScaffoldOverride.current) {
+            ThreePaneScaffoldOverrideContext(
+                    modifier = modifier,
+                    scaffoldDirective = scaffoldDirective,
+                    scaffoldState = scaffoldState,
+                    paneOrder = paneOrder,
+                    primaryPane = {
+                        rememberThreePaneScaffoldPaneScope(
+                                ThreePaneScaffoldRole.Primary,
+                                scaffoldScope,
+                                paneMotions[ThreePaneScaffoldRole.Primary]
+                            )
+                            .primaryPane()
+                    },
+                    secondaryPane = {
+                        rememberThreePaneScaffoldPaneScope(
+                                ThreePaneScaffoldRole.Secondary,
+                                scaffoldScope,
+                                paneMotions[ThreePaneScaffoldRole.Secondary]
+                            )
+                            .secondaryPane()
+                    },
+                    tertiaryPane =
+                        if (tertiaryPane == null) null
+                        else {
+                            {
+                                rememberThreePaneScaffoldPaneScope(
+                                        ThreePaneScaffoldRole.Tertiary,
+                                        scaffoldScope,
+                                        paneMotions[ThreePaneScaffoldRole.Tertiary]
+                                    )
+                                    .tertiaryPane()
+                            }
+                        },
+                    paneExpansionState = expansionState,
+                    paneExpansionDragHandle =
+                        if (paneExpansionDragHandle == null) null
+                        else {
+                            { paneExpansionState ->
+                                scaffoldScope.paneExpansionDragHandle(paneExpansionState)
+                            }
+                        },
+                    motionScopeImpl = motionScope
+                )
+                .ThreePaneScaffold()
+        }
+    }
+}
+
+/** [ThreePaneScaffoldOverride] used when no override is specified. */
+@ExperimentalMaterial3AdaptiveApi
+private object DefaultThreePaneScaffoldOverride : ThreePaneScaffoldOverride {
+    @Composable
+    override fun ThreePaneScaffoldOverrideContext.ThreePaneScaffold() {
+        val layoutDirection = LocalLayoutDirection.current
+        val ltrPaneOrder =
+            remember(paneOrder, layoutDirection) { paneOrder.toLtrOrder(layoutDirection) }
         val contents =
             listOf<@Composable () -> Unit>(
-                {
-                    remember(scaffoldScope) {
-                            ThreePaneScaffoldPaneScopeImpl(
-                                ThreePaneScaffoldRole.Primary,
-                                scaffoldScope
-                            )
-                        }
-                        .apply { updatePaneMotion(paneMotions) }
-                        .primaryPane()
-                },
-                {
-                    remember(scaffoldScope) {
-                            ThreePaneScaffoldPaneScopeImpl(
-                                ThreePaneScaffoldRole.Secondary,
-                                scaffoldScope
-                            )
-                        }
-                        .apply { updatePaneMotion(paneMotions) }
-                        .secondaryPane()
-                },
-                {
-                    if (tertiaryPane != null) {
-                        remember(scaffoldScope) {
-                                ThreePaneScaffoldPaneScopeImpl(
-                                    ThreePaneScaffoldRole.Tertiary,
-                                    scaffoldScope
-                                )
-                            }
-                            .apply { updatePaneMotion(paneMotions) }
-                            .tertiaryPane()
-                    }
-                },
-                {
-                    if (paneExpansionDragHandle != null) {
-                        scaffoldScope.paneExpansionDragHandle(expansionState)
-                    }
-                }
+                primaryPane,
+                secondaryPane,
+                tertiaryPane ?: {},
+                { paneExpansionDragHandle?.invoke(paneExpansionState) }
             )
 
         val measurePolicy =
@@ -187,9 +259,9 @@
                     ThreePaneContentMeasurePolicy(
                         scaffoldDirective,
                         scaffoldState.targetState,
-                        expansionState,
+                        paneExpansionState,
                         ltrPaneOrder,
-                        motionScope
+                        motionScopeImpl
                     )
                 }
                 .apply {
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
index 0081f7b..f24f7fa 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
@@ -20,22 +20,14 @@
 import androidx.compose.runtime.Immutable
 import androidx.compose.ui.unit.LayoutDirection
 
-/**
- * Represents the horizontal order of panes in a [ThreePaneScaffold] from start to end. Note that
- * the values of [firstPane], [secondPane] and [thirdPane] have to be different, otherwise
- * [IllegalArgumentException] will be thrown.
- *
- * @param firstPane The first pane from the start of the [ThreePaneScaffold]
- * @param secondPane The second pane from the start of the [ThreePaneScaffold]
- * @param thirdPane The third pane from the start of the [ThreePaneScaffold]
- * @constructor create an instance of [ThreePaneScaffoldHorizontalOrder]
- */
+/** Represents the horizontal order of panes in a [ThreePaneScaffold] from start to end. */
 @ExperimentalMaterial3AdaptiveApi
 @Immutable
-internal class ThreePaneScaffoldHorizontalOrder(
-    val firstPane: ThreePaneScaffoldRole,
-    val secondPane: ThreePaneScaffoldRole,
-    val thirdPane: ThreePaneScaffoldRole
+class ThreePaneScaffoldHorizontalOrder
+internal constructor(
+    internal val firstPane: ThreePaneScaffoldRole,
+    internal val secondPane: ThreePaneScaffoldRole,
+    internal val thirdPane: ThreePaneScaffoldRole
 ) : PaneScaffoldHorizontalOrder<ThreePaneScaffoldRole> {
     init {
         require(firstPane != secondPane && secondPane != thirdPane && firstPane != thirdPane) {
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
index 4ba5bd0..e3f7625 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
@@ -21,8 +21,10 @@
 import androidx.compose.foundation.gestures.draggable
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.LookaheadScope
@@ -73,16 +75,21 @@
 }
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+internal fun rememberThreePaneScaffoldPaneScope(
+    paneRole: ThreePaneScaffoldRole,
+    scaffoldScope: ThreePaneScaffoldScope,
+    paneMotion: PaneMotion
+): ThreePaneScaffoldPaneScope =
+    remember(scaffoldScope) { ThreePaneScaffoldPaneScopeImpl(paneRole, scaffoldScope) }
+        .apply { this.paneMotion = paneMotion }
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 internal class ThreePaneScaffoldPaneScopeImpl(
     override val paneRole: ThreePaneScaffoldRole,
     scaffoldScope: ThreePaneScaffoldScope,
 ) : ThreePaneScaffoldPaneScope, ThreePaneScaffoldScope by scaffoldScope {
     override var paneMotion: PaneMotion by mutableStateOf(PaneMotion.ExitToLeft)
-        private set
-
-    fun updatePaneMotion(paneMotions: ThreePaneMotion) {
-        paneMotion = paneMotions[paneRole]
-    }
 }
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
diff --git a/credentials/registry/registry-provider/src/main/java/androidx/credentials/registry/provider/ProviderGetCredentialRequest.kt b/credentials/registry/registry-provider/src/main/java/androidx/credentials/registry/provider/ProviderGetCredentialRequest.kt
index 89a12f3..65815eb 100644
--- a/credentials/registry/registry-provider/src/main/java/androidx/credentials/registry/provider/ProviderGetCredentialRequest.kt
+++ b/credentials/registry/registry-provider/src/main/java/androidx/credentials/registry/provider/ProviderGetCredentialRequest.kt
@@ -35,6 +35,9 @@
  * A null return means that entry ID isn't supported for the given type of the use case at all. For
  * example, a [androidx.credentials.provider.PasswordCredentialEntry] does not have an id property
  * and so this getter will return null if the selected entry was a password credential.
+ *
+ * For how to handle a user selection and extract the [ProviderGetCredentialRequest] containing the
+ * selection information, see [RegistryManager.ACTION_GET_CREDENTIAL].
  */
 @get:JvmName("getSelectedEntryId")
 public val ProviderGetCredentialRequest.selectedEntryId: String?
diff --git a/credentials/registry/registry-provider/src/main/java/androidx/credentials/registry/provider/RegistryManager.kt b/credentials/registry/registry-provider/src/main/java/androidx/credentials/registry/provider/RegistryManager.kt
index c601099..4d85bb0 100644
--- a/credentials/registry/registry-provider/src/main/java/androidx/credentials/registry/provider/RegistryManager.kt
+++ b/credentials/registry/registry-provider/src/main/java/androidx/credentials/registry/provider/RegistryManager.kt
@@ -44,10 +44,13 @@
          * when the user selects a credential that belongs to your application. Your activity will
          * be launched and you should use the
          * [androidx.credentials.provider.PendingIntentHandler.retrieveProviderGetCredentialRequest]
-         * API to retrieve information about the user selection and the verifier request contained
-         * in [androidx.credentials.provider.ProviderGetCredentialRequest]. Next, perform the
-         * necessary steps (e.g. consent collection, credential lookup) to generate a response for
-         * the given request. Pass the result back using one of the
+         * API to retrieve information about the user selection (you can do this through
+         * [androidx.credentials.registry.provider.selectedEntryId]), the verifier request, and
+         * other caller app information contained in
+         * [androidx.credentials.provider.ProviderGetCredentialRequest].
+         *
+         * Next, perform the necessary steps (e.g. consent collection, credential lookup) to
+         * generate a response for the given request. Pass the result back using one of the
          * [androidx.credentials.provider.PendingIntentHandler.setGetCredentialResponse] and
          * [androidx.credentials.provider.PendingIntentHandler.setGetCredentialException] APIs.
          */
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index b138827..1e85fb5 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -325,12 +325,13 @@
 
     @Test
     @LargeTest
-    public void testPngWithExif() throws Throwable {
+    public void testPngWithExifAndXmp() throws Throwable {
         File imageFile =
                 copyFromResourceToFile(
-                        R.raw.png_with_exif_byte_order_ii, "png_with_exif_byte_order_ii.png");
-        readFromFilesWithExif(imageFile, ExpectedAttributes.PNG_WITH_EXIF_BYTE_ORDER_II);
-        testWritingExif(imageFile, ExpectedAttributes.PNG_WITH_EXIF_BYTE_ORDER_II);
+                        R.raw.png_with_exif_and_xmp_byte_order_ii,
+                        "png_with_exif_and_xmp_byte_order_ii.png");
+        readFromFilesWithExif(imageFile, ExpectedAttributes.PNG_WITH_EXIF_AND_XMP_BYTE_ORDER_II);
+        testWritingExif(imageFile, ExpectedAttributes.PNG_WITH_EXIF_AND_XMP_BYTE_ORDER_II);
     }
 
     @Test
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExpectedAttributes.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExpectedAttributes.java
index 0a51698..8b39aa1 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExpectedAttributes.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExpectedAttributes.java
@@ -151,13 +151,15 @@
                     .setXmpOffsetAndLength(1809, 13197)
                     .build();
 
-    /** Expected attributes for {@link R.raw#png_with_exif_byte_order_ii}. */
-    public static final ExpectedAttributes PNG_WITH_EXIF_BYTE_ORDER_II =
+    /** Expected attributes for {@link R.raw#png_with_exif_and_xmp_byte_order_ii}. */
+    public static final ExpectedAttributes PNG_WITH_EXIF_AND_XMP_BYTE_ORDER_II =
             JPEG_WITH_EXIF_BYTE_ORDER_II
                     .buildUpon()
                     .setThumbnailOffset(212271)
                     .setMakeOffset(211525)
                     .setFocalLength("41/10")
+                    // TODO: b/332793608 - Add expected XMP values and offset/length when
+                    //  ExifInterface can parse the iTXt chunk.
                     .build();
 
     /** Expected attributes for {@link R.raw#webp_with_exif}. */
diff --git a/exifinterface/exifinterface/src/androidTest/res/raw/png_with_exif_byte_order_ii.png b/exifinterface/exifinterface/src/androidTest/res/raw/png_with_exif_and_xmp_byte_order_ii.png
similarity index 100%
rename from exifinterface/exifinterface/src/androidTest/res/raw/png_with_exif_byte_order_ii.png
rename to exifinterface/exifinterface/src/androidTest/res/raw/png_with_exif_and_xmp_byte_order_ii.png
Binary files differ
diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index f52cec7..4ed56d3 100644
--- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -3101,9 +3101,9 @@
             (byte) 0x47, (byte) 0x0d, (byte) 0x0a, (byte) 0x1a, (byte) 0x0a};
     // See "Extensions to the PNG 1.2 Specification, Version 1.5.0",
     // 3.7. eXIf Exchangeable Image File (Exif) Profile
-    private static final int PNG_CHUNK_TYPE_EXIF = intFromBytes('e', 'X', 'I', 'f');
-    private static final int PNG_CHUNK_TYPE_IHDR = intFromBytes('I', 'H', 'D', 'R');
-    private static final int PNG_CHUNK_TYPE_IEND = intFromBytes('I', 'E', 'N', 'D');
+    private static final int PNG_CHUNK_TYPE_EXIF = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f';
+    private static final int PNG_CHUNK_TYPE_IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R';
+    private static final int PNG_CHUNK_TYPE_IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D';
     private static final int PNG_CHUNK_TYPE_BYTE_LENGTH = 4;
     private static final int PNG_CHUNK_CRC_BYTE_LENGTH = 4;
 
@@ -8328,12 +8328,4 @@
         }
         return false;
     }
-
-    /*
-     * Combines the lower eight bits of each parameter into a 32-bit int. {@code b1} is the highest
-     * byte of the result, {@code b4} is the lowest.
-     */
-    private static int intFromBytes(int b1, int b2, int b3, int b4) {
-        return ((b1 & 0xFF) << 24) | ((b2 & 0xFF) << 16) | ((b3 & 0xFF) << 8) | (b4 & 0xFF);
-    }
 }
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
index e1f40ab..14dde25 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
@@ -25,8 +25,9 @@
 import androidx.health.connect.client.changes.DeletionChange
 import androidx.health.connect.client.changes.UpsertionChange
 import androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi
-import androidx.health.connect.client.impl.converters.datatype.RECORDS_CLASS_NAME_MAP
 import androidx.health.connect.client.impl.platform.aggregate.AGGREGATE_METRICS_ADDED_IN_SDK_EXT_10
+import androidx.health.connect.client.impl.platform.records.SDK_TO_PLATFORM_RECORD_CLASS
+import androidx.health.connect.client.impl.platform.records.SDK_TO_PLATFORM_RECORD_CLASS_EXT_13
 import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_PREFIX
 import androidx.health.connect.client.readRecord
 import androidx.health.connect.client.records.BloodPressureRecord
@@ -109,9 +110,15 @@
 
     @After
     fun tearDown() = runTest {
-        for (recordType in RECORDS_CLASS_NAME_MAP.keys) {
+        for (recordType in SDK_TO_PLATFORM_RECORD_CLASS.keys) {
             healthConnectClient.deleteRecords(recordType, TimeRangeFilter.none())
         }
+
+        if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 13) {
+            for (recordType in SDK_TO_PLATFORM_RECORD_CLASS_EXT_13.keys) {
+                healthConnectClient.deleteRecords(recordType, TimeRangeFilter.none())
+            }
+        }
     }
 
     @Test
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/aggregate/HealthConnectClientAggregationExtensionsTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/aggregate/HealthConnectClientAggregationExtensionsTest.kt
index 653ea0c..b48b88b 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/aggregate/HealthConnectClientAggregationExtensionsTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/aggregate/HealthConnectClientAggregationExtensionsTest.kt
@@ -23,7 +23,8 @@
 import android.os.ext.SdkExtensions
 import androidx.health.connect.client.HealthConnectClient
 import androidx.health.connect.client.impl.HealthConnectClientUpsideDownImpl
-import androidx.health.connect.client.impl.converters.datatype.RECORDS_CLASS_NAME_MAP
+import androidx.health.connect.client.impl.platform.records.SDK_TO_PLATFORM_RECORD_CLASS
+import androidx.health.connect.client.impl.platform.records.SDK_TO_PLATFORM_RECORD_CLASS_EXT_13
 import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_PREFIX
 import androidx.health.connect.client.records.BloodPressureRecord
 import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
@@ -87,9 +88,15 @@
 
     @After
     fun tearDown() = runTest {
-        for (recordType in RECORDS_CLASS_NAME_MAP.keys) {
+        for (recordType in SDK_TO_PLATFORM_RECORD_CLASS.keys) {
             healthConnectClient.deleteRecords(recordType, TimeRangeFilter.none())
         }
+
+        if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 13) {
+            for (recordType in SDK_TO_PLATFORM_RECORD_CLASS_EXT_13.keys) {
+                healthConnectClient.deleteRecords(recordType, TimeRangeFilter.none())
+            }
+        }
     }
 
     @Test
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt
index cf92c55..e7a4dd8 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt
@@ -49,6 +49,7 @@
 import androidx.health.connect.client.records.RespiratoryRateRecord
 import androidx.health.connect.client.records.RestingHeartRateRecord
 import androidx.health.connect.client.records.SexualActivityRecord
+import androidx.health.connect.client.records.SkinTemperatureRecord
 import androidx.health.connect.client.records.SleepSessionRecord
 import androidx.health.connect.client.records.SpeedRecord
 import androidx.health.connect.client.records.StepsCadenceRecord
@@ -91,6 +92,7 @@
         "RespiratoryRate" to RespiratoryRateRecord::class,
         "RestingHeartRate" to RestingHeartRateRecord::class,
         "SexualActivity" to SexualActivityRecord::class,
+        "SkinTemperature" to SkinTemperatureRecord::class,
         "SleepSession" to SleepSessionRecord::class,
         "SpeedSeries" to SpeedRecord::class, // Keep legacy Series suffix
         "IntermenstrualBleeding" to IntermenstrualBleedingRecord::class,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
index 5b61ecc..25d642fa8 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
@@ -55,6 +55,7 @@
 import androidx.health.connect.client.records.RespiratoryRateRecord
 import androidx.health.connect.client.records.RestingHeartRateRecord
 import androidx.health.connect.client.records.SexualActivityRecord
+import androidx.health.connect.client.records.SkinTemperatureRecord
 import androidx.health.connect.client.records.SleepSessionRecord
 import androidx.health.connect.client.records.SpeedRecord
 import androidx.health.connect.client.records.StepsCadenceRecord
@@ -523,6 +524,22 @@
                     endZoneOffset = endZoneOffset,
                     metadata = metadata
                 )
+            "SkinTemperature" ->
+                SkinTemperatureRecord(
+                    baseline = valuesMap["baseline"]?.doubleVal?.celsius,
+                    measurementLocation =
+                        mapEnum(
+                            "measurementLocation",
+                            SkinTemperatureRecord.MEASUREMENT_LOCATION_STRING_TO_INT_MAP,
+                            SkinTemperatureRecord.MEASUREMENT_LOCATION_UNKNOWN,
+                        ),
+                    startTime = startTime,
+                    startZoneOffset = startZoneOffset,
+                    endTime = endTime,
+                    endZoneOffset = endZoneOffset,
+                    deltas = subTypeDataListsMap["deltas"]?.toDeltasList() ?: emptyList(),
+                    metadata = metadata
+                )
             "SleepSession" ->
                 SleepSessionRecord(
                     title = getString("title"),
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
index e460b00..566a61b 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
@@ -22,11 +22,13 @@
 import androidx.health.connect.client.records.ExerciseRoute
 import androidx.health.connect.client.records.ExerciseSegment
 import androidx.health.connect.client.records.ExerciseSegment.Companion.EXERCISE_SEGMENT_TYPE_UNKNOWN
+import androidx.health.connect.client.records.SkinTemperatureRecord
 import androidx.health.connect.client.records.SleepSessionRecord
 import androidx.health.connect.client.records.SleepSessionRecord.Companion.STAGE_TYPE_STRING_TO_INT_MAP
 import androidx.health.connect.client.records.metadata.DataOrigin
 import androidx.health.connect.client.records.metadata.Device
 import androidx.health.connect.client.records.metadata.Metadata
+import androidx.health.connect.client.units.TemperatureDelta
 import androidx.health.connect.client.units.meters
 import androidx.health.platform.client.proto.DataProto
 import androidx.health.platform.client.proto.DataProto.DataPointOrBuilder
@@ -113,6 +115,15 @@
     )
 }
 
+internal fun DataProto.DataPoint.SubTypeDataList.toDeltasList(): List<SkinTemperatureRecord.Delta> {
+    return valuesList.map {
+        SkinTemperatureRecord.Delta(
+            time = Instant.ofEpochMilli(it.startTimeMillis),
+            delta = TemperatureDelta.celsius(it.valuesMap["delta"]?.doubleVal ?: 0.0),
+        )
+    }
+}
+
 internal fun DataProto.DataPoint.SubTypeDataList.toStageList(): List<SleepSessionRecord.Stage> {
     return valuesList.map {
         SleepSessionRecord.Stage(
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
index 8438aa2..1f75aa8 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
@@ -55,6 +55,7 @@
 import androidx.health.connect.client.records.RestingHeartRateRecord
 import androidx.health.connect.client.records.SeriesRecord
 import androidx.health.connect.client.records.SexualActivityRecord
+import androidx.health.connect.client.records.SkinTemperatureRecord
 import androidx.health.connect.client.records.SleepSessionRecord
 import androidx.health.connect.client.records.SpeedRecord
 import androidx.health.connect.client.records.StepsCadenceRecord
@@ -481,6 +482,28 @@
                     name?.let { putValues("name", stringVal(it)) }
                 }
                 .build()
+        is SkinTemperatureRecord ->
+            intervalProto()
+                .setDataType(protoDataType("SkinTemperature"))
+                .apply {
+                    if (baseline != null) {
+                        putValues("baseline", doubleVal(baseline.inCelsius))
+                    }
+                    if (deltas.isNotEmpty()) {
+                        putSubTypeDataLists(
+                            "deltas",
+                            DataProto.DataPoint.SubTypeDataList.newBuilder()
+                                .addAllValues(deltas.map { it.toProto() })
+                                .build(),
+                        )
+                    }
+                    enumValFromInt(
+                            measurementLocation,
+                            SkinTemperatureRecord.MEASUREMENT_LOCATION_INT_TO_STRING_MAP,
+                        )
+                        ?.let { putValues("measurementLocation", it) }
+                }
+                .build()
         is SleepSessionRecord ->
             intervalProto()
                 .setDataType(protoDataType("SleepSession"))
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt
index 3e5fa01..8ce8f01 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt
@@ -23,6 +23,7 @@
 import androidx.health.connect.client.records.ExerciseSegment
 import androidx.health.connect.client.records.InstantaneousRecord
 import androidx.health.connect.client.records.IntervalRecord
+import androidx.health.connect.client.records.SkinTemperatureRecord
 import androidx.health.connect.client.records.SleepSessionRecord
 import androidx.health.connect.client.records.metadata.Device
 import androidx.health.connect.client.records.metadata.DeviceTypes
@@ -92,6 +93,14 @@
         .build()
 }
 
+internal fun SkinTemperatureRecord.Delta.toProto(): DataProto.SubTypeDataValue {
+    return DataProto.SubTypeDataValue.newBuilder()
+        .setStartTimeMillis(time.toEpochMilli())
+        .setEndTimeMillis(time.toEpochMilli())
+        .putValues("delta", doubleVal(delta.inCelsius))
+        .build()
+}
+
 internal fun SleepSessionRecord.Stage.toProto(): DataProto.SubTypeDataValue {
     return DataProto.SubTypeDataValue.newBuilder()
         .setStartTimeMillis(startTime.toEpochMilli())
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
index 24dac4c..d9fb1f2 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
@@ -54,6 +54,7 @@
 import androidx.health.connect.client.records.RespiratoryRateRecord
 import androidx.health.connect.client.records.RestingHeartRateRecord
 import androidx.health.connect.client.records.SexualActivityRecord
+import androidx.health.connect.client.records.SkinTemperatureRecord
 import androidx.health.connect.client.records.SleepSessionRecord
 import androidx.health.connect.client.records.SpeedRecord
 import androidx.health.connect.client.records.StepsCadenceRecord
@@ -67,6 +68,7 @@
 import androidx.health.connect.client.records.metadata.Metadata
 import androidx.health.connect.client.units.BloodGlucose
 import androidx.health.connect.client.units.Length
+import androidx.health.connect.client.units.TemperatureDelta
 import androidx.health.connect.client.units.celsius
 import androidx.health.connect.client.units.grams
 import androidx.health.connect.client.units.kilocalories
@@ -805,6 +807,46 @@
     }
 
     @Test
+    fun testSkinTemperature() {
+        val data =
+            SkinTemperatureRecord(
+                baseline = 34.3.celsius,
+                measurementLocation = SkinTemperatureRecord.MEASUREMENT_LOCATION_WRIST,
+                startTime = START_TIME,
+                startZoneOffset = START_ZONE_OFFSET,
+                endTime = END_TIME,
+                endZoneOffset = END_ZONE_OFFSET,
+                metadata = TEST_METADATA,
+                deltas =
+                    listOf(
+                        SkinTemperatureRecord.Delta(
+                            time = Instant.ofEpochMilli(1234L),
+                            delta = TemperatureDelta.celsius(1.2),
+                        )
+                    )
+            )
+
+        checkProtoAndRecordTypeNameMatch(data)
+        assertThat(toRecord(data.toProto())).isEqualTo(data)
+    }
+
+    @Test
+    fun testSkinTemperatureWithEmptyDeltasList() {
+        val data =
+            SkinTemperatureRecord(
+                startTime = START_TIME,
+                startZoneOffset = START_ZONE_OFFSET,
+                endTime = END_TIME,
+                endZoneOffset = END_ZONE_OFFSET,
+                metadata = TEST_METADATA,
+                deltas = emptyList()
+            )
+
+        checkProtoAndRecordTypeNameMatch(data)
+        assertThat(toRecord(data.toProto())).isEqualTo(data)
+    }
+
+    @Test
     fun testSleepSession() {
         val data =
             SleepSessionRecord(
diff --git a/lifecycle/integration-tests/incrementality/build.gradle b/lifecycle/integration-tests/incrementality/build.gradle
index 7a8cfcf..2ab870e 100644
--- a/lifecycle/integration-tests/incrementality/build.gradle
+++ b/lifecycle/integration-tests/incrementality/build.gradle
@@ -44,6 +44,7 @@
             ":lifecycle:lifecycle-compiler:publish",
             ":lifecycle:lifecycle-common:publish",
             ":lifecycle:lifecycle-runtime:publish",
+            ":core:core-viewtree:publish",
             ":annotation:annotation:publish",
             ":arch:core:core-common:publish",
             ":arch:core:core-runtime:publish"
diff --git a/savedstate/savedstate/api/current.txt b/savedstate/savedstate/api/current.txt
index 3e3d6f9..c1e51e3 100644
--- a/savedstate/savedstate/api/current.txt
+++ b/savedstate/savedstate/api/current.txt
@@ -11,6 +11,8 @@
     method public inline operator boolean contains(String key);
     method public boolean contentDeepEquals(android.os.Bundle other);
     method public int contentDeepHashCode();
+    method public inline android.os.IBinder getBinder(String key);
+    method public inline android.os.IBinder getBinderOrElse(String key, kotlin.jvm.functions.Function0<? extends android.os.IBinder> defaultValue);
     method public inline boolean getBoolean(String key);
     method public inline boolean[] getBooleanArray(String key);
     method public inline boolean[] getBooleanArrayOrElse(String key, kotlin.jvm.functions.Function0<boolean[]> defaultValue);
@@ -19,6 +21,12 @@
     method public inline char[] getCharArray(String key);
     method public inline char[] getCharArrayOrElse(String key, kotlin.jvm.functions.Function0<char[]> defaultValue);
     method public inline char getCharOrElse(String key, kotlin.jvm.functions.Function0<java.lang.Character> defaultValue);
+    method public inline CharSequence getCharSequence(String key);
+    method public inline CharSequence[] getCharSequenceArray(String key);
+    method public inline CharSequence[] getCharSequenceArrayOrElse(String key, kotlin.jvm.functions.Function0<java.lang.CharSequence[]> defaultValue);
+    method public inline java.util.List<java.lang.CharSequence> getCharSequenceList(String key);
+    method public inline java.util.List<java.lang.CharSequence> getCharSequenceListOrElse(String key, kotlin.jvm.functions.Function0<? extends java.util.List<? extends java.lang.CharSequence>> defaultValue);
+    method public inline CharSequence getCharSequenceOrElse(String key, kotlin.jvm.functions.Function0<? extends java.lang.CharSequence> defaultValue);
     method public inline double getDouble(String key);
     method public inline double[] getDoubleArray(String key);
     method public inline double[] getDoubleArrayOrElse(String key, kotlin.jvm.functions.Function0<double[]> defaultValue);
@@ -38,11 +46,21 @@
     method public inline long[] getLongArrayOrElse(String key, kotlin.jvm.functions.Function0<long[]> defaultValue);
     method public inline long getLongOrElse(String key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
     method public inline <reified T extends android.os.Parcelable> T getParcelable(String key);
+    method public inline <reified T extends android.os.Parcelable> T[] getParcelableArray(String key);
+    method public inline <reified T extends android.os.Parcelable> T[] getParcelableArrayOrElse(String key, kotlin.jvm.functions.Function0<T[]> defaultValue);
     method public inline <reified T extends android.os.Parcelable> java.util.List<T> getParcelableList(String key);
     method public inline <reified T extends android.os.Parcelable> java.util.List<T> getParcelableListOrElse(String key, kotlin.jvm.functions.Function0<? extends java.util.List<? extends T>> defaultValue);
     method public inline <reified T extends android.os.Parcelable> T getParcelableOrElse(String key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
     method public inline android.os.Bundle getSavedState(String key);
     method public inline android.os.Bundle getSavedStateOrElse(String key, kotlin.jvm.functions.Function0<android.os.Bundle> defaultValue);
+    method public inline <reified T extends java.io.Serializable> T getSerializable(String key);
+    method public inline <reified T extends java.io.Serializable> T getSerializableOrElse(String key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+    method public inline android.util.Size getSize(String key);
+    method public inline android.util.SizeF getSizeF(String key);
+    method public inline android.util.SizeF getSizeFOrElse(String key, kotlin.jvm.functions.Function0<android.util.SizeF> defaultValue);
+    method public inline android.util.Size getSizeOrElse(String key, kotlin.jvm.functions.Function0<android.util.Size> defaultValue);
+    method public inline <reified T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(String key);
+    method public inline <reified T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArrayOrElse(String key, kotlin.jvm.functions.Function0<? extends android.util.SparseArray<T>> defaultValue);
     method public inline String getString(String key);
     method public inline String[] getStringArray(String key);
     method public inline String[] getStringArrayOrElse(String key, kotlin.jvm.functions.Function0<java.lang.String[]> defaultValue);
@@ -95,10 +113,14 @@
   @kotlin.jvm.JvmInline public final value class SavedStateWriter {
     method public inline void clear();
     method public inline void putAll(android.os.Bundle values);
+    method public inline void putBinder(String key, android.os.IBinder value);
     method public inline void putBoolean(String key, boolean value);
     method public inline void putBooleanArray(String key, boolean[] values);
     method public inline void putChar(String key, char value);
     method public inline void putCharArray(String key, char[] values);
+    method public inline void putCharSequence(String key, CharSequence value);
+    method public inline void putCharSequenceArray(String key, CharSequence[] values);
+    method public inline void putCharSequenceList(String key, java.util.List<? extends java.lang.CharSequence> values);
     method public inline void putDouble(String key, double value);
     method public inline void putDoubleArray(String key, double[] values);
     method public inline void putFloat(String key, float value);
@@ -110,8 +132,13 @@
     method public inline void putLongArray(String key, long[] values);
     method public inline void putNull(String key);
     method public inline <reified T extends android.os.Parcelable> void putParcelable(String key, T value);
+    method public inline <reified T extends android.os.Parcelable> void putParcelableArray(String key, T[] values);
     method public inline <reified T extends android.os.Parcelable> void putParcelableList(String key, java.util.List<? extends T> values);
     method public inline void putSavedState(String key, android.os.Bundle value);
+    method public inline <reified T extends java.io.Serializable> void putSerializable(String key, T value);
+    method public inline void putSize(String key, android.util.Size value);
+    method public inline void putSizeF(String key, android.util.SizeF value);
+    method public inline <reified T extends android.os.Parcelable> void putSparseParcelableArray(String key, android.util.SparseArray<T> values);
     method public inline void putString(String key, String value);
     method public inline void putStringArray(String key, String[] values);
     method public inline void putStringList(String key, java.util.List<java.lang.String> values);
diff --git a/savedstate/savedstate/api/restricted_current.txt b/savedstate/savedstate/api/restricted_current.txt
index 94ea74b..eb9ae61 100644
--- a/savedstate/savedstate/api/restricted_current.txt
+++ b/savedstate/savedstate/api/restricted_current.txt
@@ -12,6 +12,8 @@
     method public inline operator boolean contains(String key);
     method public boolean contentDeepEquals(android.os.Bundle other);
     method public int contentDeepHashCode();
+    method public inline android.os.IBinder getBinder(String key);
+    method public inline android.os.IBinder getBinderOrElse(String key, kotlin.jvm.functions.Function0<? extends android.os.IBinder> defaultValue);
     method public inline boolean getBoolean(String key);
     method public inline boolean[] getBooleanArray(String key);
     method public inline boolean[] getBooleanArrayOrElse(String key, kotlin.jvm.functions.Function0<boolean[]> defaultValue);
@@ -20,6 +22,12 @@
     method public inline char[] getCharArray(String key);
     method public inline char[] getCharArrayOrElse(String key, kotlin.jvm.functions.Function0<char[]> defaultValue);
     method public inline char getCharOrElse(String key, kotlin.jvm.functions.Function0<java.lang.Character> defaultValue);
+    method public inline CharSequence getCharSequence(String key);
+    method public inline CharSequence[] getCharSequenceArray(String key);
+    method public inline CharSequence[] getCharSequenceArrayOrElse(String key, kotlin.jvm.functions.Function0<java.lang.CharSequence[]> defaultValue);
+    method public inline java.util.List<java.lang.CharSequence> getCharSequenceList(String key);
+    method public inline java.util.List<java.lang.CharSequence> getCharSequenceListOrElse(String key, kotlin.jvm.functions.Function0<? extends java.util.List<? extends java.lang.CharSequence>> defaultValue);
+    method public inline CharSequence getCharSequenceOrElse(String key, kotlin.jvm.functions.Function0<? extends java.lang.CharSequence> defaultValue);
     method public inline double getDouble(String key);
     method public inline double[] getDoubleArray(String key);
     method public inline double[] getDoubleArrayOrElse(String key, kotlin.jvm.functions.Function0<double[]> defaultValue);
@@ -39,11 +47,21 @@
     method public inline long[] getLongArrayOrElse(String key, kotlin.jvm.functions.Function0<long[]> defaultValue);
     method public inline long getLongOrElse(String key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
     method public inline <reified T extends android.os.Parcelable> T getParcelable(String key);
+    method public inline <reified T extends android.os.Parcelable> T[] getParcelableArray(String key);
+    method public inline <reified T extends android.os.Parcelable> T[] getParcelableArrayOrElse(String key, kotlin.jvm.functions.Function0<T[]> defaultValue);
     method public inline <reified T extends android.os.Parcelable> java.util.List<T> getParcelableList(String key);
     method public inline <reified T extends android.os.Parcelable> java.util.List<T> getParcelableListOrElse(String key, kotlin.jvm.functions.Function0<? extends java.util.List<? extends T>> defaultValue);
     method public inline <reified T extends android.os.Parcelable> T getParcelableOrElse(String key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
     method public inline android.os.Bundle getSavedState(String key);
     method public inline android.os.Bundle getSavedStateOrElse(String key, kotlin.jvm.functions.Function0<android.os.Bundle> defaultValue);
+    method public inline <reified T extends java.io.Serializable> T getSerializable(String key);
+    method public inline <reified T extends java.io.Serializable> T getSerializableOrElse(String key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+    method public inline android.util.Size getSize(String key);
+    method public inline android.util.SizeF getSizeF(String key);
+    method public inline android.util.SizeF getSizeFOrElse(String key, kotlin.jvm.functions.Function0<android.util.SizeF> defaultValue);
+    method public inline android.util.Size getSizeOrElse(String key, kotlin.jvm.functions.Function0<android.util.Size> defaultValue);
+    method public inline <reified T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(String key);
+    method public inline <reified T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArrayOrElse(String key, kotlin.jvm.functions.Function0<? extends android.util.SparseArray<T>> defaultValue);
     method public inline String getString(String key);
     method public inline String[] getStringArray(String key);
     method public inline String[] getStringArrayOrElse(String key, kotlin.jvm.functions.Function0<java.lang.String[]> defaultValue);
@@ -115,10 +133,14 @@
     ctor @kotlin.PublishedApi internal SavedStateWriter(@kotlin.PublishedApi android.os.Bundle source);
     method public inline void clear();
     method public inline void putAll(android.os.Bundle values);
+    method public inline void putBinder(String key, android.os.IBinder value);
     method public inline void putBoolean(String key, boolean value);
     method public inline void putBooleanArray(String key, boolean[] values);
     method public inline void putChar(String key, char value);
     method public inline void putCharArray(String key, char[] values);
+    method public inline void putCharSequence(String key, CharSequence value);
+    method public inline void putCharSequenceArray(String key, CharSequence[] values);
+    method public inline void putCharSequenceList(String key, java.util.List<? extends java.lang.CharSequence> values);
     method public inline void putDouble(String key, double value);
     method public inline void putDoubleArray(String key, double[] values);
     method public inline void putFloat(String key, float value);
@@ -130,8 +152,13 @@
     method public inline void putLongArray(String key, long[] values);
     method public inline void putNull(String key);
     method public inline <reified T extends android.os.Parcelable> void putParcelable(String key, T value);
+    method public inline <reified T extends android.os.Parcelable> void putParcelableArray(String key, T[] values);
     method public inline <reified T extends android.os.Parcelable> void putParcelableList(String key, java.util.List<? extends T> values);
     method public inline void putSavedState(String key, android.os.Bundle value);
+    method public inline <reified T extends java.io.Serializable> void putSerializable(String key, T value);
+    method public inline void putSize(String key, android.util.Size value);
+    method public inline void putSizeF(String key, android.util.SizeF value);
+    method public inline <reified T extends android.os.Parcelable> void putSparseParcelableArray(String key, android.util.SparseArray<T> values);
     method public inline void putString(String key, String value);
     method public inline void putStringArray(String key, String[] values);
     method public inline void putStringList(String key, java.util.List<java.lang.String> values);
diff --git a/savedstate/savedstate/bcv/native/current.txt b/savedstate/savedstate/bcv/native/current.txt
index e9f880e..2491285 100644
--- a/savedstate/savedstate/bcv/native/current.txt
+++ b/savedstate/savedstate/bcv/native/current.txt
@@ -66,6 +66,12 @@
     final inline fun getCharArray(kotlin/String): kotlin/CharArray // androidx.savedstate/SavedStateReader.getCharArray|getCharArray(kotlin.String){}[0]
     final inline fun getCharArrayOrElse(kotlin/String, kotlin/Function0<kotlin/CharArray>): kotlin/CharArray // androidx.savedstate/SavedStateReader.getCharArrayOrElse|getCharArrayOrElse(kotlin.String;kotlin.Function0<kotlin.CharArray>){}[0]
     final inline fun getCharOrElse(kotlin/String, kotlin/Function0<kotlin/Char>): kotlin/Char // androidx.savedstate/SavedStateReader.getCharOrElse|getCharOrElse(kotlin.String;kotlin.Function0<kotlin.Char>){}[0]
+    final inline fun getCharSequence(kotlin/String): kotlin/CharSequence // androidx.savedstate/SavedStateReader.getCharSequence|getCharSequence(kotlin.String){}[0]
+    final inline fun getCharSequenceArray(kotlin/String): kotlin/Array<kotlin/CharSequence> // androidx.savedstate/SavedStateReader.getCharSequenceArray|getCharSequenceArray(kotlin.String){}[0]
+    final inline fun getCharSequenceArrayOrElse(kotlin/String, kotlin/Function0<kotlin/Array<kotlin/CharSequence>>): kotlin/Array<kotlin/CharSequence> // androidx.savedstate/SavedStateReader.getCharSequenceArrayOrElse|getCharSequenceArrayOrElse(kotlin.String;kotlin.Function0<kotlin.Array<kotlin.CharSequence>>){}[0]
+    final inline fun getCharSequenceList(kotlin/String): kotlin.collections/List<kotlin/CharSequence> // androidx.savedstate/SavedStateReader.getCharSequenceList|getCharSequenceList(kotlin.String){}[0]
+    final inline fun getCharSequenceListOrElse(kotlin/String, kotlin/Function0<kotlin.collections/List<kotlin/CharSequence>>): kotlin.collections/List<kotlin/CharSequence> // androidx.savedstate/SavedStateReader.getCharSequenceListOrElse|getCharSequenceListOrElse(kotlin.String;kotlin.Function0<kotlin.collections.List<kotlin.CharSequence>>){}[0]
+    final inline fun getCharSequenceOrElse(kotlin/String, kotlin/Function0<kotlin/CharSequence>): kotlin/CharSequence // androidx.savedstate/SavedStateReader.getCharSequenceOrElse|getCharSequenceOrElse(kotlin.String;kotlin.Function0<kotlin.CharSequence>){}[0]
     final inline fun getDouble(kotlin/String): kotlin/Double // androidx.savedstate/SavedStateReader.getDouble|getDouble(kotlin.String){}[0]
     final inline fun getDoubleArray(kotlin/String): kotlin/DoubleArray // androidx.savedstate/SavedStateReader.getDoubleArray|getDoubleArray(kotlin.String){}[0]
     final inline fun getDoubleArrayOrElse(kotlin/String, kotlin/Function0<kotlin/DoubleArray>): kotlin/DoubleArray // androidx.savedstate/SavedStateReader.getDoubleArrayOrElse|getDoubleArrayOrElse(kotlin.String;kotlin.Function0<kotlin.DoubleArray>){}[0]
@@ -112,6 +118,9 @@
     final inline fun putBooleanArray(kotlin/String, kotlin/BooleanArray) // androidx.savedstate/SavedStateWriter.putBooleanArray|putBooleanArray(kotlin.String;kotlin.BooleanArray){}[0]
     final inline fun putChar(kotlin/String, kotlin/Char) // androidx.savedstate/SavedStateWriter.putChar|putChar(kotlin.String;kotlin.Char){}[0]
     final inline fun putCharArray(kotlin/String, kotlin/CharArray) // androidx.savedstate/SavedStateWriter.putCharArray|putCharArray(kotlin.String;kotlin.CharArray){}[0]
+    final inline fun putCharSequence(kotlin/String, kotlin/CharSequence) // androidx.savedstate/SavedStateWriter.putCharSequence|putCharSequence(kotlin.String;kotlin.CharSequence){}[0]
+    final inline fun putCharSequenceArray(kotlin/String, kotlin/Array<kotlin/CharSequence>) // androidx.savedstate/SavedStateWriter.putCharSequenceArray|putCharSequenceArray(kotlin.String;kotlin.Array<kotlin.CharSequence>){}[0]
+    final inline fun putCharSequenceList(kotlin/String, kotlin.collections/List<kotlin/CharSequence>) // androidx.savedstate/SavedStateWriter.putCharSequenceList|putCharSequenceList(kotlin.String;kotlin.collections.List<kotlin.CharSequence>){}[0]
     final inline fun putDouble(kotlin/String, kotlin/Double) // androidx.savedstate/SavedStateWriter.putDouble|putDouble(kotlin.String;kotlin.Double){}[0]
     final inline fun putDoubleArray(kotlin/String, kotlin/DoubleArray) // androidx.savedstate/SavedStateWriter.putDoubleArray|putDoubleArray(kotlin.String;kotlin.DoubleArray){}[0]
     final inline fun putFloat(kotlin/String, kotlin/Float) // androidx.savedstate/SavedStateWriter.putFloat|putFloat(kotlin.String;kotlin.Float){}[0]
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
index b291dac..d3d59e0 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
@@ -20,9 +20,17 @@
 
 package androidx.savedstate
 
+import android.os.IBinder
 import android.os.Parcelable
+import android.util.Size
+import android.util.SizeF
+import android.util.SparseArray
 import androidx.core.os.BundleCompat.getParcelable
+import androidx.core.os.BundleCompat.getParcelableArray
 import androidx.core.os.BundleCompat.getParcelableArrayList
+import androidx.core.os.BundleCompat.getSerializable
+import androidx.core.os.BundleCompat.getSparseParcelableArray
+import java.io.Serializable
 
 @JvmInline
 actual value class SavedStateReader
@@ -31,6 +39,33 @@
     @PublishedApi internal actual val source: SavedState,
 ) {
 
+    /**
+     * Retrieves a [IBinder] object associated with the specified key. Throws an
+     * [IllegalStateException] if the key doesn't exist.
+     *
+     * @param key The key to retrieve the value for.
+     * @return The [IBinder] object associated with the key.
+     * @throws IllegalStateException If the key is not found.
+     */
+    inline fun getBinder(key: String): IBinder {
+        if (key !in this) keyNotFoundError(key)
+        return source.getBinder(key) ?: valueNotFoundError(key)
+    }
+
+    /**
+     * Retrieves a [IBinder] object associated with the specified key, or a default value if the key
+     * doesn't exist.
+     *
+     * @param key The key to retrieve the value for.
+     * @param defaultValue A function providing the default [IBinder] if the key is not found.
+     * @return The [IBinder] object associated with the key, or the default value if the key is not
+     *   found.
+     */
+    inline fun getBinderOrElse(key: String, defaultValue: () -> IBinder): IBinder {
+        if (key !in this) defaultValue()
+        return source.getBinder(key) ?: defaultValue()
+    }
+
     actual inline fun getBoolean(key: String): Boolean {
         if (key !in this) keyNotFoundError(key)
         return source.getBoolean(key, DEFAULT_BOOLEAN)
@@ -46,6 +81,19 @@
         return source.getChar(key, DEFAULT_CHAR)
     }
 
+    actual inline fun getCharSequence(key: String): CharSequence {
+        if (key !in this) keyNotFoundError(key)
+        return source.getCharSequence(key) ?: valueNotFoundError(key)
+    }
+
+    actual inline fun getCharSequenceOrElse(
+        key: String,
+        defaultValue: () -> CharSequence
+    ): CharSequence {
+        if (key !in this) defaultValue()
+        return source.getCharSequence(key) ?: defaultValue()
+    }
+
     actual inline fun getCharOrElse(key: String, defaultValue: () -> Char): Char {
         if (key !in this) defaultValue()
         return source.getChar(key, defaultValue())
@@ -118,6 +166,90 @@
         return getParcelable(source, key, T::class.java) ?: defaultValue()
     }
 
+    /**
+     * Retrieves a [Serializable] object associated with the specified key. Throws an
+     * [IllegalStateException] if the key doesn't exist.
+     *
+     * @param key The key to retrieve the value for.
+     * @return The [Serializable] object associated with the key.
+     * @throws IllegalStateException If the key is not found.
+     */
+    inline fun <reified T : Serializable> getSerializable(key: String): T {
+        if (key !in this) keyNotFoundError(key)
+        return getSerializable(source, key, T::class.java) ?: valueNotFoundError(key)
+    }
+
+    /**
+     * Retrieves a [Serializable] object associated with the specified key, or a default value if
+     * the key doesn't exist.
+     *
+     * @param key The key to retrieve the value for.
+     * @param defaultValue A function providing the default [Serializable] if the key is not found.
+     * @return The [Serializable] object associated with the key, or the default value if the key is
+     *   not found.
+     */
+    inline fun <reified T : Serializable> getSerializableOrElse(
+        key: String,
+        defaultValue: () -> T
+    ): T {
+        if (key !in this) defaultValue()
+        return getSerializable(source, key, T::class.java) ?: defaultValue()
+    }
+
+    /**
+     * Retrieves a [Size] object associated with the specified key. Throws an
+     * [IllegalStateException] if the key doesn't exist.
+     *
+     * @param key The key to retrieve the value for.
+     * @return The [Size] object associated with the key.
+     * @throws IllegalStateException If the key is not found.
+     */
+    inline fun getSize(key: String): Size {
+        if (key !in this) keyNotFoundError(key)
+        return source.getSize(key) ?: valueNotFoundError(key)
+    }
+
+    /**
+     * Retrieves a [Size] object associated with the specified key, or a default value if the key
+     * doesn't exist.
+     *
+     * @param key The key to retrieve the value for.
+     * @param defaultValue A function providing the default [Size] if the key is not found.
+     * @return The [Size] object associated with the key, or the default value if the key is not
+     *   found.
+     */
+    inline fun getSizeOrElse(key: String, defaultValue: () -> Size): Size {
+        if (key !in this) defaultValue()
+        return source.getSize(key) ?: defaultValue()
+    }
+
+    /**
+     * Retrieves a [SizeF] object associated with the specified key. Throws an
+     * [IllegalStateException] if the key doesn't exist.
+     *
+     * @param key The key to retrieve the value for.
+     * @return The [SizeF] object associated with the key.
+     * @throws IllegalStateException If the key is not found.
+     */
+    inline fun getSizeF(key: String): SizeF {
+        if (key !in this) keyNotFoundError(key)
+        return source.getSizeF(key) ?: valueNotFoundError(key)
+    }
+
+    /**
+     * Retrieves a [SizeF] object associated with the specified key, or a default value if the key
+     * doesn't exist.
+     *
+     * @param key The key to retrieve the value for.
+     * @param defaultValue A function providing the default [SizeF] if the key is not found.
+     * @return The [SizeF] object associated with the key, or the default value if the key is not
+     *   found.
+     */
+    inline fun getSizeFOrElse(key: String, defaultValue: () -> SizeF): SizeF {
+        if (key !in this) defaultValue()
+        return source.getSizeF(key) ?: defaultValue()
+    }
+
     actual inline fun getString(key: String): String {
         if (key !in this) keyNotFoundError(key)
         return source.getString(key) ?: valueNotFoundError(key)
@@ -138,6 +270,19 @@
         return source.getIntegerArrayList(key) ?: defaultValue()
     }
 
+    actual inline fun getCharSequenceList(key: String): List<CharSequence> {
+        if (key !in this) keyNotFoundError(key)
+        return source.getCharSequenceArrayList(key) ?: valueNotFoundError(key)
+    }
+
+    actual inline fun getCharSequenceListOrElse(
+        key: String,
+        defaultValue: () -> List<CharSequence>
+    ): List<CharSequence> {
+        if (key !in this) defaultValue()
+        return source.getCharSequenceArrayList(key) ?: defaultValue()
+    }
+
     actual inline fun getStringList(key: String): List<String> {
         if (key !in this) keyNotFoundError(key)
         return source.getStringArrayList(key) ?: valueNotFoundError(key)
@@ -170,7 +315,7 @@
      *
      * @param key The [key] to retrieve the value for.
      * @param defaultValue A function providing the default value if the [key] is not found or the
-     *   retrieved value is not a list of [Parcelable].
+     *   retrieved value is not a [List] of [Parcelable].
      * @return The list of elements of [Parcelable] associated with the [key], or the default value
      *   if the [key] is not found.
      */
@@ -205,6 +350,21 @@
         return source.getCharArray(key) ?: defaultValue()
     }
 
+    @Suppress("ArrayReturn")
+    actual inline fun getCharSequenceArray(key: String): Array<CharSequence> {
+        if (key !in this) keyNotFoundError(key)
+        return source.getCharSequenceArray(key) ?: valueNotFoundError(key)
+    }
+
+    @Suppress("ArrayReturn")
+    actual inline fun getCharSequenceArrayOrElse(
+        key: String,
+        defaultValue: () -> Array<CharSequence>
+    ): Array<CharSequence> {
+        if (key !in this) defaultValue()
+        return source.getCharSequenceArray(key) ?: defaultValue()
+    }
+
     actual inline fun getDoubleArray(key: String): DoubleArray {
         if (key !in this) keyNotFoundError(key)
         return source.getDoubleArray(key) ?: valueNotFoundError(key)
@@ -261,6 +421,75 @@
         return source.getStringArray(key) ?: defaultValue()
     }
 
+    /**
+     * Retrieves an [Array] of elements of [Parcelable] associated with the specified [key]. Throws
+     * an [IllegalStateException] if the [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @return The [Array] of elements of [Parcelable] associated with the [key].
+     * @throws IllegalStateException If the [key] is not found.
+     */
+    @Suppress("ArrayReturn")
+    inline fun <reified T : Parcelable> getParcelableArray(key: String): Array<T> {
+        if (key !in this) keyNotFoundError(key)
+        @Suppress("UNCHECKED_CAST")
+        return getParcelableArray(source, key, T::class.java) as? Array<T>
+            ?: valueNotFoundError(key)
+    }
+
+    /**
+     * Retrieves a [Array] of elements of [Parcelable] associated with the specified [key], or a
+     * default value if the [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @param defaultValue A function providing the default value if the [key] is not found or the
+     *   retrieved value is not a [Array] of [Parcelable].
+     * @return The [Array] of elements of [Parcelable] associated with the [key], or the default
+     *   value if the [key] is not found.
+     */
+    @Suppress("ArrayReturn")
+    inline fun <reified T : Parcelable> getParcelableArrayOrElse(
+        key: String,
+        defaultValue: () -> Array<T>
+    ): Array<T> {
+        if (key !in this) defaultValue()
+        @Suppress("UNCHECKED_CAST")
+        return getParcelableArray(source, key, T::class.java) as? Array<T> ?: defaultValue()
+    }
+
+    /**
+     * Retrieves an [SparseArray] of elements of [Parcelable] associated with the specified [key].
+     * Throws an [IllegalStateException] if the [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @return The [SparseArray] of elements of [Parcelable] associated with the [key].
+     * @throws IllegalStateException If the [key] is not found.
+     */
+    inline fun <reified T : Parcelable> getSparseParcelableArray(key: String): SparseArray<T> {
+        if (key !in this) keyNotFoundError(key)
+        return getSparseParcelableArray(source, key, T::class.java) as? SparseArray<T>
+            ?: valueNotFoundError(key)
+    }
+
+    /**
+     * Retrieves a [SparseArray] of elements of [Parcelable] associated with the specified [key], or
+     * a default value if the [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @param defaultValue A function providing the default value if the [key] is not found or the
+     *   retrieved value is not a [SparseArray] of [Parcelable].
+     * @return The [SparseArray] of elements of [Parcelable] associated with the [key], or the
+     *   default value if the [key] is not found.
+     */
+    inline fun <reified T : Parcelable> getSparseParcelableArrayOrElse(
+        key: String,
+        defaultValue: () -> SparseArray<T>
+    ): SparseArray<T> {
+        if (key !in this) defaultValue()
+        return getSparseParcelableArray(source, key, T::class.java) as? SparseArray<T>
+            ?: defaultValue()
+    }
+
     actual inline fun getSavedState(key: String): SavedState {
         if (key !in this) keyNotFoundError(key)
         return source.getBundle(key) ?: valueNotFoundError(key)
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt
index db7d120..a2795d0 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt
@@ -20,7 +20,12 @@
 
 package androidx.savedstate
 
+import android.os.IBinder
 import android.os.Parcelable
+import android.util.Size
+import android.util.SizeF
+import android.util.SparseArray
+import java.io.Serializable
 
 @JvmInline
 actual value class SavedStateWriter
@@ -29,6 +34,16 @@
     @PublishedApi internal actual val source: SavedState,
 ) {
 
+    /**
+     * Stores an [IBinder] value associated with the specified key in the [IBinder].
+     *
+     * @param key The key to associate the value with.
+     * @param value The [IBinder] value to store.
+     */
+    inline fun putBinder(key: String, value: IBinder) {
+        source.putBinder(key, value)
+    }
+
     actual inline fun putBoolean(key: String, value: Boolean) {
         source.putBoolean(key, value)
     }
@@ -37,6 +52,10 @@
         source.putChar(key, value)
     }
 
+    actual inline fun putCharSequence(key: String, value: CharSequence) {
+        source.putCharSequence(key, value)
+    }
+
     actual inline fun putDouble(key: String, value: Double) {
         source.putDouble(key, value)
     }
@@ -67,6 +86,36 @@
         source.putParcelable(key, value)
     }
 
+    /**
+     * Stores an [Serializable] value associated with the specified key in the [Serializable].
+     *
+     * @param key The key to associate the value with.
+     * @param value The [Serializable] value to store.
+     */
+    inline fun <reified T : Serializable> putSerializable(key: String, value: T) {
+        source.putSerializable(key, value)
+    }
+
+    /**
+     * Stores an [Size] value associated with the specified key in the [Size].
+     *
+     * @param key The key to associate the value with.
+     * @param value The [Size] value to store.
+     */
+    inline fun putSize(key: String, value: Size) {
+        source.putSize(key, value)
+    }
+
+    /**
+     * Stores an [SizeF] value associated with the specified key in the [SizeF].
+     *
+     * @param key The key to associate the value with.
+     * @param value The [SizeF] value to store.
+     */
+    inline fun putSizeF(key: String, value: SizeF) {
+        source.putSizeF(key, value)
+    }
+
     actual inline fun putString(key: String, value: String) {
         source.putString(key, value)
     }
@@ -75,16 +124,20 @@
         source.putIntegerArrayList(key, values.toArrayListUnsafe())
     }
 
+    actual inline fun putCharSequenceList(key: String, values: List<CharSequence>) {
+        source.putCharSequenceArrayList(key, values.toArrayListUnsafe())
+    }
+
     actual inline fun putStringList(key: String, values: List<String>) {
         source.putStringArrayList(key, values.toArrayListUnsafe())
     }
 
     /**
-     * Stores a list of elements of [Parcelable] associated with the specified key in the
+     * Stores a [List] of elements of [Parcelable] associated with the specified key in the
      * [SavedState].
      *
      * @param key The key to associate the value with.
-     * @param values The list of elements to store.
+     * @param values The [List] of elements to store.
      */
     inline fun <reified T : Parcelable> putParcelableList(key: String, values: List<T>) {
         source.putParcelableArrayList(key, values.toArrayListUnsafe())
@@ -98,6 +151,13 @@
         source.putCharArray(key, values)
     }
 
+    actual inline fun putCharSequenceArray(
+        key: String,
+        @Suppress("ArrayReturn") values: Array<CharSequence>
+    ) {
+        source.putCharSequenceArray(key, values)
+    }
+
     actual inline fun putDoubleArray(key: String, values: DoubleArray) {
         source.putDoubleArray(key, values)
     }
@@ -118,6 +178,34 @@
         source.putStringArray(key, values)
     }
 
+    /**
+     * Stores a [Array] of elements of [Parcelable] associated with the specified key in the
+     * [SavedState].
+     *
+     * @param key The key to associate the value with.
+     * @param values The [Array] of elements to store.
+     */
+    inline fun <reified T : Parcelable> putParcelableArray(
+        key: String,
+        @Suppress("ArrayReturn") values: Array<T>
+    ) {
+        source.putParcelableArray(key, values)
+    }
+
+    /**
+     * Stores a [SparseArray] of elements of [Parcelable] associated with the specified key in the
+     * [SavedState].
+     *
+     * @param key The key to associate the value with.
+     * @param values The [SparseArray] of elements to store.
+     */
+    inline fun <reified T : Parcelable> putSparseParcelableArray(
+        key: String,
+        values: SparseArray<T>
+    ) {
+        source.putSparseParcelableArray(key, values)
+    }
+
     actual inline fun putSavedState(key: String, value: SavedState) {
         source.putBundle(key, value)
     }
diff --git a/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/ParcelableSavedStateTest.android.kt b/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/ParcelableSavedStateTest.android.kt
deleted file mode 100644
index 0a0dd72..0000000
--- a/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/ParcelableSavedStateTest.android.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.savedstate
-
-import android.os.Parcel
-import android.os.Parcelable
-import androidx.kruth.assertThat
-import androidx.kruth.assertThrows
-import kotlin.test.Test
-
-internal class ParcelableSavedStateTest : RobolectricTest() {
-
-    @Test
-    fun getParcelable_whenSet_returns() {
-        val underTest = savedState { putParcelable(KEY_1, PARCELABLE_VALUE_1) }
-        val actual = underTest.read { getParcelable<TestParcelable>(KEY_1) }
-
-        assertThat(actual).isEqualTo(PARCELABLE_VALUE_1)
-    }
-
-    @Test
-    fun getParcelable_whenNotSet_throws() {
-        assertThrows<IllegalArgumentException> {
-            savedState().read { getParcelable<TestParcelable>(KEY_1) }
-        }
-    }
-
-    @Test
-    fun getParcelable_whenSet_differentType_returnsDefault() {
-        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
-
-        assertThrows<IllegalStateException> {
-            underTest.read { getParcelable<TestParcelable>(KEY_1) }
-        }
-    }
-
-    @Test
-    fun getParcelableOrElse_whenSet_returns() {
-        val underTest = savedState { putParcelable(KEY_1, PARCELABLE_VALUE_1) }
-        val actual = underTest.read { getParcelableOrElse(KEY_1) { PARCELABLE_VALUE_2 } }
-
-        assertThat(actual).isEqualTo(PARCELABLE_VALUE_1)
-    }
-
-    @Test
-    fun getParcelableOrElse_whenNotSet_returnsElse() {
-        val actual = savedState().read { getParcelableOrElse(KEY_1) { PARCELABLE_VALUE_1 } }
-
-        assertThat(actual).isEqualTo(PARCELABLE_VALUE_1)
-    }
-
-    @Test
-    fun getParcelableOrElse_whenSet_differentType_returnsDefault() {
-        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
-        val actual = underTest.read { getParcelableOrElse(KEY_1) { PARCELABLE_VALUE_1 } }
-
-        assertThat(actual).isEqualTo(PARCELABLE_VALUE_1)
-    }
-
-    @Test
-    fun getParcelableList_whenSet_returns() {
-        val expected = List(size = 5) { idx -> TestParcelable(idx) }
-
-        val underTest = savedState { putParcelableList(KEY_1, expected) }
-        val actual = underTest.read { getParcelableList<TestParcelable>(KEY_1) }
-
-        assertThat(actual).isEqualTo(expected)
-    }
-
-    @Test
-    fun getList_ofParcelable_whenNotSet_throws() {
-        assertThrows<IllegalArgumentException> {
-            savedState().read { getParcelableList<TestParcelable>(KEY_1) }
-        }
-    }
-
-    @Test
-    fun getList_whenSet_differentType_throws() {
-        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
-
-        assertThrows<IllegalStateException> {
-            underTest.read { getParcelableList<TestParcelable>(KEY_1) }
-        }
-    }
-
-    @Test
-    fun getListOrElse_ofParcelable_whenSet_returns() {
-        val expected = List(size = 5) { idx -> TestParcelable(idx) }
-
-        val underTest = savedState { putParcelableList(KEY_1, expected) }
-        val actual =
-            underTest.read { getParcelableListOrElse<TestParcelable>(KEY_1) { emptyList() } }
-
-        assertThat(actual).isEqualTo(expected)
-    }
-
-    @Test
-    fun getListOrElse_ofParcelable_whenNotSet_returnsElse() {
-        val actual =
-            savedState().read { getParcelableListOrElse<TestParcelable>(KEY_1) { emptyList() } }
-
-        assertThat(actual).isEqualTo(emptyList<TestParcelable>())
-    }
-
-    @Test
-    fun getListOrElse_whenSet_differentType_throws() {
-        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
-        val actual = underTest.read { getParcelableListOrElse(KEY_1) { emptyList() } }
-
-        assertThat(actual).isEqualTo(emptyList<Parcelable>())
-    }
-
-    private companion object {
-        const val KEY_1 = "KEY_1"
-        val PARCELABLE_VALUE_1 = TestParcelable(value = Int.MIN_VALUE)
-        val PARCELABLE_VALUE_2 = TestParcelable(value = Int.MAX_VALUE)
-    }
-
-    internal data class TestParcelable(val value: Int) : Parcelable {
-
-        override fun describeContents(): Int = 0
-
-        override fun writeToParcel(dest: Parcel, flags: Int) {
-            dest.writeInt(value)
-        }
-
-        companion object {
-            @Suppress("unused")
-            @JvmField
-            val CREATOR =
-                object : Parcelable.Creator<TestParcelable> {
-                    override fun createFromParcel(source: Parcel) =
-                        TestParcelable(value = source.readInt())
-
-                    override fun newArray(size: Int) = arrayOfNulls<TestParcelable>(size)
-                }
-        }
-    }
-}
diff --git a/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/SavedStateAndroidTest.android.kt b/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/SavedStateAndroidTest.android.kt
new file mode 100644
index 0000000..91f3395
--- /dev/null
+++ b/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/SavedStateAndroidTest.android.kt
@@ -0,0 +1,483 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.savedstate
+
+import android.os.IBinder
+import android.os.Parcel
+import android.os.Parcelable
+import android.util.Size
+import android.util.SizeF
+import android.util.SparseArray
+import androidx.kruth.assertThat
+import androidx.kruth.assertThrows
+import java.io.FileDescriptor
+import java.io.Serializable
+import kotlin.test.Test
+
+internal class ParcelableSavedStateTest : RobolectricTest() {
+
+    @Test
+    fun getBinder_whenSet_returns() {
+        val underTest = savedState { putBinder(KEY_1, BINDER_VALUE_1) }
+        val actual = underTest.read { getBinder(KEY_1) }
+
+        assertThat(actual).isEqualTo(BINDER_VALUE_1)
+    }
+
+    @Test
+    fun getBinder_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> { savedState().read { getBinder(KEY_1) } }
+    }
+
+    @Test
+    fun getBinder_whenSet_differentType_returnsDefault() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+        assertThrows<IllegalStateException> { underTest.read { getBinder(KEY_1) } }
+    }
+
+    @Test
+    fun getBinderOrElse_whenSet_returns() {
+        val underTest = savedState { putBinder(KEY_1, BINDER_VALUE_1) }
+        val actual = underTest.read { getBinderOrElse(KEY_1) { BINDER_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(BINDER_VALUE_1)
+    }
+
+    @Test
+    fun getBinderOrElse_whenNotSet_returnsElse() {
+        val actual = savedState().read { getBinderOrElse(KEY_1) { BINDER_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(BINDER_VALUE_2)
+    }
+
+    @Test
+    fun getBinderOrElse_whenSet_differentType_returnsDefault() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+        val actual = underTest.read { getBinderOrElse(KEY_1) { BINDER_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(BINDER_VALUE_2)
+    }
+
+    @Test
+    fun getSize_whenSet_returns() {
+        val underTest = savedState { putSize(KEY_1, SIZE_IN_PIXEL_VALUE_1) }
+        val actual = underTest.read { getSize(KEY_1) }
+
+        assertThat(actual).isEqualTo(SIZE_IN_PIXEL_VALUE_1)
+    }
+
+    @Test
+    fun getSize_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> { savedState().read { getSize(KEY_1) } }
+    }
+
+    @Test
+    fun getSize_whenSet_differentType_throws() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+        assertThrows<IllegalStateException> { underTest.read { getSize(KEY_1) } }
+    }
+
+    @Test
+    fun getSizeOrElse_whenSet_returns() {
+        val underTest = savedState { putSize(KEY_1, SIZE_IN_PIXEL_VALUE_1) }
+        val actual = underTest.read { getSizeOrElse(KEY_1) { SIZE_IN_PIXEL_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(SIZE_IN_PIXEL_VALUE_1)
+    }
+
+    @Test
+    fun getSizeOrElse_whenNotSet_returnsElse() {
+        val actual = savedState().read { getSizeOrElse(KEY_1) { SIZE_IN_PIXEL_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(SIZE_IN_PIXEL_VALUE_2)
+    }
+
+    @Test
+    fun getSizeOrElse_whenSet_differentType_returnsDefault() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+        val actual = underTest.read { getSizeOrElse(KEY_1) { SIZE_IN_PIXEL_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(SIZE_IN_PIXEL_VALUE_2)
+    }
+
+    @Test
+    fun getSizeF_whenSet_returns() {
+        val underTest = savedState { putSizeF(KEY_1, SIZE_IN_FLOAT_VALUE_1) }
+        val actual = underTest.read { getSizeF(KEY_1) }
+
+        assertThat(actual).isEqualTo(SIZE_IN_FLOAT_VALUE_1)
+    }
+
+    @Test
+    fun getSizeF_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> { savedState().read { getSizeF(KEY_1) } }
+    }
+
+    @Test
+    fun getSizeF_whenSet_differentType_throws() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+        assertThrows<IllegalStateException> { underTest.read { getSizeF(KEY_1) } }
+    }
+
+    @Test
+    fun getSizeFOrElse_whenSet_returns() {
+        val underTest = savedState { putSizeF(KEY_1, SIZE_IN_FLOAT_VALUE_1) }
+        val actual = underTest.read { getSizeFOrElse(KEY_1) { SIZE_IN_FLOAT_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(SIZE_IN_FLOAT_VALUE_1)
+    }
+
+    @Test
+    fun getSizeFOrElse_whenNotSet_returnsElse() {
+        val actual = savedState().read { getSizeFOrElse(KEY_1) { SIZE_IN_FLOAT_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(SIZE_IN_FLOAT_VALUE_2)
+    }
+
+    @Test
+    fun getSizeFOrElse_whenSet_differentType_returnsDefault() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+        val actual = underTest.read { getSizeFOrElse(KEY_1) { SIZE_IN_FLOAT_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(SIZE_IN_FLOAT_VALUE_2)
+    }
+
+    @Test
+    fun getParcelable_whenSet_returns() {
+        val underTest = savedState { putParcelable(KEY_1, PARCELABLE_VALUE_1) }
+        val actual = underTest.read { getParcelable<TestParcelable>(KEY_1) }
+
+        assertThat(actual).isEqualTo(PARCELABLE_VALUE_1)
+    }
+
+    @Test
+    fun getParcelable_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> {
+            savedState().read { getParcelable<TestParcelable>(KEY_1) }
+        }
+    }
+
+    @Test
+    fun getParcelable_whenSet_differentType_throws() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+        assertThrows<IllegalStateException> {
+            underTest.read { getParcelable<TestParcelable>(KEY_1) }
+        }
+    }
+
+    @Test
+    fun getParcelableOrElse_whenSet_returns() {
+        val underTest = savedState { putParcelable(KEY_1, PARCELABLE_VALUE_1) }
+        val actual = underTest.read { getParcelableOrElse(KEY_1) { PARCELABLE_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(PARCELABLE_VALUE_1)
+    }
+
+    @Test
+    fun getParcelableOrElse_whenNotSet_returnsElse() {
+        val actual = savedState().read { getParcelableOrElse(KEY_1) { PARCELABLE_VALUE_1 } }
+
+        assertThat(actual).isEqualTo(PARCELABLE_VALUE_1)
+    }
+
+    @Test
+    fun getParcelableOrElse_whenSet_differentType_returnsDefault() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+        val actual = underTest.read { getParcelableOrElse(KEY_1) { PARCELABLE_VALUE_1 } }
+
+        assertThat(actual).isEqualTo(PARCELABLE_VALUE_1)
+    }
+
+    @Test
+    fun getParcelableList_whenSet_returns() {
+        val expected = List(size = 5) { idx -> TestParcelable(idx) }
+
+        val underTest = savedState { putParcelableList(KEY_1, expected) }
+        val actual = underTest.read { getParcelableList<TestParcelable>(KEY_1) }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getParcelableList_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> {
+            savedState().read { getParcelableList<TestParcelable>(KEY_1) }
+        }
+    }
+
+    @Test
+    fun getParcelableList_whenSet_differentType_throws() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+        assertThrows<IllegalStateException> {
+            underTest.read { getParcelableList<TestParcelable>(KEY_1) }
+        }
+    }
+
+    @Test
+    fun getParcelableListOrElse_whenSet_returns() {
+        val expected = List(size = 5) { idx -> TestParcelable(idx) }
+
+        val underTest = savedState { putParcelableList(KEY_1, expected) }
+        val actual =
+            underTest.read { getParcelableListOrElse<TestParcelable>(KEY_1) { emptyList() } }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getParcelableListOrElse_whenNotSet_returnsElse() {
+        val actual =
+            savedState().read { getParcelableListOrElse<TestParcelable>(KEY_1) { emptyList() } }
+
+        assertThat(actual).isEqualTo(emptyList<TestParcelable>())
+    }
+
+    @Test
+    fun getParcelableListOrElse_whenSet_differentType_throws() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+        val actual = underTest.read { getParcelableListOrElse(KEY_1) { emptyList() } }
+
+        assertThat(actual).isEqualTo(emptyList<Parcelable>())
+    }
+
+    @Test
+    fun getParcelableArray_whenSet_returns() {
+        val expected = Array(size = 5) { idx -> TestParcelable(idx) }
+
+        val underTest = savedState { putParcelableArray(KEY_1, expected) }
+        val actual = underTest.read { getParcelableArray<TestParcelable>(KEY_1) }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getParcelableArray_ofParcelable_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> {
+            savedState().read { getParcelableArray<TestParcelable>(KEY_1) }
+        }
+    }
+
+    @Test
+    fun getParcelableArray_whenSet_differentType_throws() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+        assertThrows<IllegalStateException> {
+            underTest.read { getParcelableArray<TestParcelable>(KEY_1) }
+        }
+    }
+
+    @Test
+    fun getParcelableArrayOrElse_whenSet_returns() {
+        val expected = Array(size = 5) { idx -> TestParcelable(idx) }
+
+        val underTest = savedState { putParcelableArray(KEY_1, expected) }
+        val actual =
+            underTest.read { getParcelableArrayOrElse<TestParcelable>(KEY_1) { emptyArray() } }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getParcelableArrayOrElse_ofParcelable_whenNotSet_returnsElse() {
+        val actual =
+            savedState().read { getParcelableArrayOrElse<TestParcelable>(KEY_1) { emptyArray() } }
+
+        assertThat(actual).isEqualTo(emptyArray<TestParcelable>())
+    }
+
+    @Test
+    fun getParcelableArrayOrElse_whenSet_differentType_throws() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+        val actual = underTest.read { getParcelableArrayOrElse(KEY_1) { emptyArray() } }
+
+        assertThat(actual).isEqualTo(emptyArray<Parcelable>())
+    }
+
+    @Test
+    fun getSparseParcelableArray_whenSet_returns() {
+        val expected = SPARSE_PARCELABLE_ARRAY
+
+        val underTest = savedState { putSparseParcelableArray(KEY_1, expected) }
+        val actual = underTest.read { getSparseParcelableArray<TestParcelable>(KEY_1) }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getSparseParcelableArray_ofParcelable_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> {
+            savedState().read { getSparseParcelableArray<TestParcelable>(KEY_1) }
+        }
+    }
+
+    @Test
+    fun getSparseParcelableArray_whenSet_differentType_throws() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+        assertThrows<IllegalStateException> {
+            underTest.read { getSparseParcelableArray<TestParcelable>(KEY_1) }
+        }
+    }
+
+    @Test
+    fun getSparseParcelableArrayOrElse_whenSet_returns() {
+        val expected = SPARSE_PARCELABLE_ARRAY
+
+        val underTest = savedState { putSparseParcelableArray(KEY_1, expected) }
+        val actual =
+            underTest.read {
+                getSparseParcelableArrayOrElse<TestParcelable>(KEY_1) { SparseArray() }
+            }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getSparseParcelableArrayOrElse_ofParcelable_whenNotSet_returnsElse() {
+        val expected = SPARSE_PARCELABLE_ARRAY
+
+        val actual =
+            savedState().read { getSparseParcelableArrayOrElse<TestParcelable>(KEY_1) { expected } }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getSparseParcelableArrayOrElse_whenSet_differentType_throws() {
+        val expected = SPARSE_PARCELABLE_ARRAY
+
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+        val actual = underTest.read { getSparseParcelableArrayOrElse(KEY_1) { expected } }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getSerializable_whenSet_returns() {
+        val underTest = savedState { putSerializable(KEY_1, SERIALIZABLE_VALUE_1) }
+        val actual = underTest.read { getSerializable<TestSerializable>(KEY_1) }
+
+        assertThat(actual).isEqualTo(SERIALIZABLE_VALUE_1)
+    }
+
+    @Test
+    fun getSerializable_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> {
+            savedState().read { getSerializable<TestSerializable>(KEY_1) }
+        }
+    }
+
+    @Test
+    fun getSerializable_whenSet_differentType_throws() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+        assertThrows<IllegalStateException> {
+            underTest.read { getSerializable<TestSerializable>(KEY_1) }
+        }
+    }
+
+    @Test
+    fun getSerializableOrElse_whenSet_returns() {
+        val underTest = savedState { putSerializable(KEY_1, SERIALIZABLE_VALUE_1) }
+        val actual = underTest.read { getSerializableOrElse(KEY_1) { SERIALIZABLE_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(SERIALIZABLE_VALUE_1)
+    }
+
+    @Test
+    fun getSerializableOrElse_whenNotSet_returnsElse() {
+        val actual = savedState().read { getSerializableOrElse(KEY_1) { SERIALIZABLE_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(SERIALIZABLE_VALUE_2)
+    }
+
+    @Test
+    fun getSerializableOrElse_whenSet_differentType_returnsDefault() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+        val actual = underTest.read { getSerializableOrElse(KEY_1) { SERIALIZABLE_VALUE_1 } }
+
+        assertThat(actual).isEqualTo(SERIALIZABLE_VALUE_1)
+    }
+
+    private companion object {
+        const val KEY_1 = "KEY_1"
+        val SIZE_IN_PIXEL_VALUE_1 = Size(/* width= */ Int.MIN_VALUE, /* height */ Int.MIN_VALUE)
+        val SIZE_IN_PIXEL_VALUE_2 = Size(/* width= */ Int.MAX_VALUE, /* height */ Int.MAX_VALUE)
+        val SIZE_IN_FLOAT_VALUE_1 =
+            SizeF(/* width= */ Float.MIN_VALUE, /* height */ Float.MIN_VALUE)
+        val SIZE_IN_FLOAT_VALUE_2 =
+            SizeF(/* width= */ Float.MAX_VALUE, /* height */ Float.MAX_VALUE)
+        val BINDER_VALUE_1 = TestBinder(value = Int.MIN_VALUE)
+        val BINDER_VALUE_2 = TestBinder(value = Int.MAX_VALUE)
+        val PARCELABLE_VALUE_1 = TestParcelable(value = Int.MIN_VALUE)
+        val PARCELABLE_VALUE_2 = TestParcelable(value = Int.MAX_VALUE)
+        val SERIALIZABLE_VALUE_1 = TestSerializable(value = Int.MIN_VALUE)
+        val SERIALIZABLE_VALUE_2 = TestSerializable(value = Int.MAX_VALUE)
+        val SPARSE_PARCELABLE_ARRAY =
+            SparseArray<TestParcelable>(/* initialCapacity= */ 5).apply {
+                repeat(times = 5) { idx -> put(idx, TestParcelable(idx)) }
+            }
+    }
+
+    internal data class TestBinder(val value: Int) : IBinder {
+        override fun getInterfaceDescriptor() = error("")
+
+        override fun pingBinder() = error("")
+
+        override fun isBinderAlive() = error("")
+
+        override fun queryLocalInterface(descriptor: String) = error("")
+
+        override fun dump(fd: FileDescriptor, args: Array<out String>?) = error("")
+
+        override fun dumpAsync(fd: FileDescriptor, args: Array<out String>?) = error("")
+
+        override fun transact(code: Int, data: Parcel, reply: Parcel?, flags: Int) = error("")
+
+        override fun linkToDeath(recipient: IBinder.DeathRecipient, flags: Int) = error("")
+
+        override fun unlinkToDeath(recipient: IBinder.DeathRecipient, flags: Int) = error("")
+    }
+
+    internal data class TestParcelable(val value: Int) : Parcelable {
+
+        override fun describeContents(): Int = 0
+
+        override fun writeToParcel(dest: Parcel, flags: Int) {
+            dest.writeInt(value)
+        }
+
+        companion object {
+            @Suppress("unused")
+            @JvmField
+            val CREATOR =
+                object : Parcelable.Creator<TestParcelable> {
+                    override fun createFromParcel(source: Parcel) =
+                        TestParcelable(value = source.readInt())
+
+                    override fun newArray(size: Int) = arrayOfNulls<TestParcelable>(size)
+                }
+        }
+    }
+
+    internal data class TestSerializable(val value: Int) : Serializable
+}
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt
index 5249fa0..a18e93e 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt
@@ -25,10 +25,15 @@
 import kotlin.jvm.JvmName
 
 @PublishedApi internal const val DEFAULT_BOOLEAN: Boolean = false
+
 @PublishedApi internal const val DEFAULT_CHAR: Char = 0.toChar()
+
 @PublishedApi internal const val DEFAULT_FLOAT: Float = 0F
+
 @PublishedApi internal const val DEFAULT_DOUBLE: Double = 0.0
+
 @PublishedApi internal const val DEFAULT_INT: Int = 0
+
 @PublishedApi internal const val DEFAULT_LONG: Long = 0L
 
 /**
@@ -87,6 +92,30 @@
     public inline fun getCharOrElse(key: String, defaultValue: () -> Char): Char
 
     /**
+     * Retrieves a [CharSequence] value associated with the specified [key]. Throws an
+     * [IllegalStateException] if the [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @return The [CharSequence] value associated with the [key].
+     * @throws IllegalStateException If the [key] is not found.
+     */
+    public inline fun getCharSequence(key: String): CharSequence
+
+    /**
+     * Retrieves a [CharSequence] value associated with the specified [key], or a default value if
+     * the [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @param defaultValue A function providing the default value if the [key] is not found.
+     * @return The [CharSequence] value associated with the [key], or the default value if the [key]
+     *   is not found.
+     */
+    public inline fun getCharSequenceOrElse(
+        key: String,
+        defaultValue: () -> CharSequence
+    ): CharSequence
+
+    /**
      * Retrieves a [Double] value associated with the specified [key]. Throws an
      * [IllegalStateException] if the [key] doesn't exist.
      *
@@ -239,6 +268,31 @@
     ): List<String>
 
     /**
+     * Retrieves a [List] of elements of [CharArray] associated with the specified [key]. Throws an
+     * [IllegalStateException] if the [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @return The [List] of elements of [CharArray] associated with the [key].
+     * @throws IllegalStateException If the [key] is not found.
+     */
+    public inline fun getCharSequenceList(key: String): List<CharSequence>
+
+    /**
+     * Retrieves a [List] of elements of [CharSequence] associated with the specified [key], or a
+     * default value if the [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @param defaultValue A function providing the default value if the [key] is not found or the
+     *   retrieved value is not a list of [CharSequence].
+     * @return The list of elements of [CharSequence] associated with the [key], or the default
+     *   value if the [key] is not found.
+     */
+    public inline fun getCharSequenceListOrElse(
+        key: String,
+        defaultValue: () -> List<CharSequence>
+    ): List<CharSequence>
+
+    /**
      * Retrieves a [BooleanArray] value associated with the specified [key]. Throws an
      * [IllegalStateException] if the [key] doesn't exist.
      *
@@ -284,6 +338,30 @@
     public inline fun getCharArrayOrElse(key: String, defaultValue: () -> CharArray): CharArray
 
     /**
+     * Retrieves a [CharArray] value associated with the specified [key]. Throws an
+     * [IllegalStateException] if the [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @return The [CharArray] value associated with the [key].
+     * @throws IllegalStateException If the [key] is not found.
+     */
+    public inline fun getCharSequenceArray(key: String): Array<CharSequence>
+
+    /**
+     * Retrieves a [CharArray] value associated with the specified [key], or a default value if the
+     * [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @param defaultValue A function providing the default value if the [key] is not found.
+     * @return The [CharArray] value associated with the [key], or the default value if the [key] is
+     *   not found.
+     */
+    public inline fun getCharSequenceArrayOrElse(
+        key: String,
+        defaultValue: () -> Array<CharSequence>
+    ): Array<CharSequence>
+
+    /**
      * Retrieves a [DoubleArray] value associated with the specified [key]. Throws an
      * [IllegalStateException] if the [key] doesn't exist.
      *
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateWriter.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateWriter.kt
index 13b5c8f..11cd15c04 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateWriter.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateWriter.kt
@@ -45,9 +45,23 @@
      */
     public inline fun putBoolean(key: String, value: Boolean)
 
+    /**
+     * Stores a char value associated with the specified key in the [SavedState].
+     *
+     * @param key The key to associate the value with.
+     * @param value The char value to store.
+     */
     public inline fun putChar(key: String, value: Char)
 
     /**
+     * Stores a char sequence value associated with the specified key in the [SavedState].
+     *
+     * @param key The key to associate the value with.
+     * @param value The char sequence value to store.
+     */
+    public inline fun putCharSequence(key: String, value: CharSequence)
+
+    /**
      * Stores a double value associated with the specified key in the [SavedState].
      *
      * @param key The key to associate the value with.
@@ -103,6 +117,15 @@
     public inline fun putIntList(key: String, values: List<Int>)
 
     /**
+     * Stores a list of elements of [CharSequence] associated with the specified key in the
+     * [SavedState].
+     *
+     * @param key The key to associate the value with.
+     * @param values The list of elements to store.
+     */
+    public inline fun putCharSequenceList(key: String, values: List<CharSequence>)
+
+    /**
      * Stores a list of elements of [String] associated with the specified key in the [SavedState].
      *
      * @param key The key to associate the value with.
@@ -129,6 +152,15 @@
     public inline fun putCharArray(key: String, values: CharArray)
 
     /**
+     * Stores an [Array] of elements of [CharSequence] associated with the specified key in the
+     * [SavedState].
+     *
+     * @param key The key to associate the value with.
+     * @param values The array of elements to store.
+     */
+    public inline fun putCharSequenceArray(key: String, values: Array<CharSequence>)
+
+    /**
      * Stores an [Array] of elements of [Double] associated with the specified key in the
      * [SavedState].
      *
diff --git a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt
index fc57a73..d19506c 100644
--- a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt
+++ b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt
@@ -332,6 +332,49 @@
     }
 
     @Test
+    fun getCharSequence_whenSet_returns() {
+        val underTest = savedState { putCharSequence(KEY_1, CHAR_SEQUENCE_VALUE_1) }
+        val actual = underTest.read { getCharSequence(KEY_1) }
+
+        assertThat(actual).isEqualTo(CHAR_SEQUENCE_VALUE_1)
+    }
+
+    @Test
+    fun getCharSequence_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> { savedState().read { getCharSequence(KEY_1) } }
+    }
+
+    @Test
+    fun getCharSequence_whenSet_differentType_throws() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+        assertThrows<IllegalStateException> { underTest.read { getString(KEY_1) } }
+    }
+
+    @Test
+    fun getCharSequenceOrElse_whenSet_returns() {
+        val underTest = savedState { putCharSequence(KEY_1, CHAR_SEQUENCE_VALUE_1) }
+        val actual = underTest.read { getCharSequenceOrElse(KEY_1) { CHAR_SEQUENCE_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(CHAR_SEQUENCE_VALUE_1)
+    }
+
+    @Test
+    fun getCharSequenceOrElse_whenNotSet_returnsElse() {
+        val actual = savedState().read { getCharSequenceOrElse(KEY_1) { CHAR_SEQUENCE_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(CHAR_SEQUENCE_VALUE_2)
+    }
+
+    @Test
+    fun getCharSequenceOrElse_whenSet_differentType_returnsElse() {
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+        val actual = underTest.read { getCharSequenceOrElse(KEY_1) { CHAR_SEQUENCE_VALUE_2 } }
+
+        assertThat(actual).isEqualTo(CHAR_SEQUENCE_VALUE_2)
+    }
+
+    @Test
     fun getDouble_whenSet_returns() {
         val underTest = savedState { putDouble(KEY_1, Double.MAX_VALUE) }
         val actual = underTest.read { getDouble(KEY_1) }
@@ -625,6 +668,53 @@
     }
 
     @Test
+    fun getCharSequenceList_whenSet_returns() {
+        val underTest = savedState { putCharSequenceList(KEY_1, CHAR_SEQUENCE_LIST) }
+        val actual = underTest.read { getCharSequenceList(KEY_1) }
+
+        assertThat(actual).isEqualTo(CHAR_SEQUENCE_LIST)
+    }
+
+    @Test
+    fun getCharSequenceList_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> { savedState().read { getCharSequenceList(KEY_1) } }
+    }
+
+    @Test
+    fun getCharSequenceList_whenSet_differentType_throws() {
+        val expected = Int.MAX_VALUE
+
+        val underTest = savedState { putInt(KEY_1, expected) }
+
+        assertThrows<IllegalStateException> { underTest.read { getCharSequenceList(KEY_1) } }
+    }
+
+    @Test
+    fun getCharSequenceListOrElse_whenSet_returns() {
+        val underTest = savedState { putCharSequenceList(KEY_1, CHAR_SEQUENCE_LIST) }
+        val actual = underTest.read { getCharSequenceListOrElse(KEY_1) { emptyList() } }
+
+        assertThat(actual).isEqualTo(CHAR_SEQUENCE_LIST)
+    }
+
+    @Test
+    fun getCharSequenceListOrElse_whenNotSet_returnsElse() {
+        val actual = savedState().read { getCharSequenceListOrElse(KEY_1) { emptyList() } }
+
+        assertThat(actual).isEqualTo(emptyList<CharSequence>())
+    }
+
+    @Test
+    fun getCharSequenceListOrElse_whenSet_differentType_returnsElse() {
+        val expected = Int.MAX_VALUE
+
+        val underTest = savedState { putInt(KEY_1, expected) }
+        val actual = underTest.read { getCharSequenceListOrElse(KEY_1) { emptyList() } }
+
+        assertThat(actual).isEqualTo(emptyList<CharSequence>())
+    }
+
+    @Test
     fun getStringList_whenSet_returns() {
         val underTest = savedState { putStringList(KEY_1, LIST_STRING_VALUE) }
         val actual = underTest.read { getStringList(KEY_1) }
@@ -778,6 +868,59 @@
     }
 
     @Test
+    fun getCharSequenceArray_whenSet_returns() {
+        val expected = Array<CharSequence>(size = 5) { idx -> idx.toString() }
+
+        val underTest = savedState { putCharSequenceArray(KEY_1, expected) }
+        val actual = underTest.read { getCharSequenceArray(KEY_1) }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getCharSequenceArray_whenNotSet_throws() {
+        assertThrows<IllegalArgumentException> { savedState().read { getCharSequenceArray(KEY_1) } }
+    }
+
+    @Test
+    fun getCharSequenceArray_whenSet_differentType_throws() {
+        val expected = Int.MAX_VALUE
+
+        val underTest = savedState { putInt(KEY_1, expected) }
+
+        assertThrows<IllegalStateException> { underTest.read { getCharSequenceArray(KEY_1) } }
+    }
+
+    @Test
+    fun getCharSequenceArrayOrElse_whenSet_returns() {
+        val expected = CHAR_SEQUENCE_ARRAY
+
+        val underTest = savedState { putCharSequenceArray(KEY_1, expected) }
+        val actual = underTest.read { getCharSequenceArrayOrElse(KEY_1) { emptyArray() } }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getCharSequenceArrayOrElse_whenNotSet_returnsElse() {
+        val expected = CHAR_SEQUENCE_ARRAY
+
+        val actual = savedState().read { getCharSequenceArrayOrElse(KEY_1) { expected } }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
+    fun getCharSequenceArrayOrElse_whenSet_differentType_returnsElse() {
+        val expected = CHAR_SEQUENCE_ARRAY
+
+        val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+        val actual = underTest.read { getCharSequenceArrayOrElse(KEY_1) { expected } }
+
+        assertThat(actual).isEqualTo(expected)
+    }
+
+    @Test
     fun getDoubleArray_whenSet_returns() {
         val expected = DoubleArray(size = 5) { idx -> idx.toDouble() }
 
@@ -1109,6 +1252,10 @@
         val LIST_INT_VALUE = List(size = 5) { idx -> idx }
         val LIST_STRING_VALUE = List(size = 5) { idx -> "index=$idx" }
         val SAVED_STATE_VALUE = savedState()
+        val CHAR_SEQUENCE_VALUE_1: CharSequence = Int.MIN_VALUE.toString()
+        val CHAR_SEQUENCE_VALUE_2: CharSequence = Int.MAX_VALUE.toString()
+        val CHAR_SEQUENCE_ARRAY = Array<CharSequence>(size = 5) { idx -> "index=$idx" }
+        val CHAR_SEQUENCE_LIST = List<CharSequence>(size = 5) { idx -> "index=$idx" }
 
         private fun createDefaultSavedState(): SavedState {
             var key = 0
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
index 5a36f36..b18561a 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
@@ -51,6 +51,19 @@
         return source.map[key] as? Char ?: defaultValue()
     }
 
+    actual inline fun getCharSequence(key: String): CharSequence {
+        if (key !in this) keyNotFoundError(key)
+        return source.map[key] as? CharSequence ?: valueNotFoundError(key)
+    }
+
+    actual inline fun getCharSequenceOrElse(
+        key: String,
+        defaultValue: () -> CharSequence
+    ): CharSequence {
+        if (key !in this) defaultValue()
+        return source.map[key] as? CharSequence ?: defaultValue()
+    }
+
     actual inline fun getDouble(key: String): Double {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? Double ?: DEFAULT_DOUBLE
@@ -101,31 +114,42 @@
         return source.map[key] as? String ?: defaultValue()
     }
 
-    @Suppress("UNCHECKED_CAST")
+    actual inline fun getCharSequenceList(key: String): List<CharSequence> {
+        if (key !in this) keyNotFoundError(key)
+        @Suppress("UNCHECKED_CAST")
+        return source.map[key] as? List<CharSequence> ?: valueNotFoundError(key)
+    }
+
+    actual inline fun getCharSequenceListOrElse(
+        key: String,
+        defaultValue: () -> List<CharSequence>
+    ): List<CharSequence> {
+        if (key !in this) defaultValue()
+        @Suppress("UNCHECKED_CAST") return source.map[key] as? List<CharSequence> ?: defaultValue()
+    }
+
     actual inline fun getIntList(key: String): List<Int> {
         if (key !in this) keyNotFoundError(key)
-        return source.map[key] as? List<Int> ?: valueNotFoundError(key)
+        @Suppress("UNCHECKED_CAST") return source.map[key] as? List<Int> ?: valueNotFoundError(key)
     }
 
-    @Suppress("UNCHECKED_CAST")
     actual inline fun getIntListOrElse(key: String, defaultValue: () -> List<Int>): List<Int> {
         if (key !in this) defaultValue()
-        return source.map[key] as? List<Int> ?: defaultValue()
+        @Suppress("UNCHECKED_CAST") return source.map[key] as? List<Int> ?: defaultValue()
     }
 
-    @Suppress("UNCHECKED_CAST")
     actual inline fun getStringList(key: String): List<String> {
         if (key !in this) keyNotFoundError(key)
+        @Suppress("UNCHECKED_CAST")
         return source.map[key] as? List<String> ?: valueNotFoundError(key)
     }
 
-    @Suppress("UNCHECKED_CAST")
     actual inline fun getStringListOrElse(
         key: String,
         defaultValue: () -> List<String>
     ): List<String> {
         if (key !in this) defaultValue()
-        return source.map[key] as? List<String> ?: defaultValue()
+        @Suppress("UNCHECKED_CAST") return source.map[key] as? List<String> ?: defaultValue()
     }
 
     actual inline fun getCharArray(key: String): CharArray {
@@ -138,6 +162,20 @@
         return source.map[key] as? CharArray ?: defaultValue()
     }
 
+    actual inline fun getCharSequenceArray(key: String): Array<CharSequence> {
+        if (key !in this) keyNotFoundError(key)
+        @Suppress("UNCHECKED_CAST")
+        return source.map[key] as? Array<CharSequence> ?: valueNotFoundError(key)
+    }
+
+    actual inline fun getCharSequenceArrayOrElse(
+        key: String,
+        defaultValue: () -> Array<CharSequence>
+    ): Array<CharSequence> {
+        if (key !in this) defaultValue()
+        @Suppress("UNCHECKED_CAST") return source.map[key] as? Array<CharSequence> ?: defaultValue()
+    }
+
     actual inline fun getBooleanArray(key: String): BooleanArray {
         if (key !in this) keyNotFoundError(key)
         return source.map[key] as? BooleanArray ?: valueNotFoundError(key)
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt
index 95cecc4..88fac06 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt
@@ -39,6 +39,10 @@
         source.map[key] = value
     }
 
+    actual inline fun putCharSequence(key: String, value: CharSequence) {
+        source.map[key] = value
+    }
+
     actual inline fun putDouble(key: String, value: Double) {
         source.map[key] = value
     }
@@ -63,6 +67,10 @@
         source.map[key] = value
     }
 
+    actual inline fun putCharSequenceList(key: String, values: List<CharSequence>) {
+        source.map[key] = values
+    }
+
     actual inline fun putIntList(key: String, values: List<Int>) {
         source.map[key] = values
     }
@@ -79,6 +87,10 @@
         source.map[key] = values
     }
 
+    actual inline fun putCharSequenceArray(key: String, values: Array<CharSequence>) {
+        source.map[key] = values
+    }
+
     actual inline fun putDoubleArray(key: String, values: DoubleArray) {
         source.map[key] = values
     }
diff --git a/slidingpanelayout/slidingpanelayout/api/current.txt b/slidingpanelayout/slidingpanelayout/api/current.txt
index e49a46f..b591f76 100644
--- a/slidingpanelayout/slidingpanelayout/api/current.txt
+++ b/slidingpanelayout/slidingpanelayout/api/current.txt
@@ -46,6 +46,7 @@
     method public final void setUserResizeBehavior(androidx.slidingpanelayout.widget.SlidingPaneLayout.UserResizeBehavior userResizeBehavior);
     method public final void setUserResizingDividerDrawable(android.graphics.drawable.Drawable? drawable);
     method public final void setUserResizingDividerDrawable(@DrawableRes int resId);
+    method public final void setUserResizingDividerTint(android.content.res.ColorStateList? colorStateList);
     method public final void setUserResizingEnabled(boolean);
     method @Deprecated public void smoothSlideClosed();
     method @Deprecated public void smoothSlideOpen();
diff --git a/slidingpanelayout/slidingpanelayout/api/res-current.txt b/slidingpanelayout/slidingpanelayout/api/res-current.txt
index 0d72e80..88c866a 100644
--- a/slidingpanelayout/slidingpanelayout/api/res-current.txt
+++ b/slidingpanelayout/slidingpanelayout/api/res-current.txt
@@ -3,3 +3,4 @@
 attr isUserResizingEnabled
 attr userResizeBehavior
 attr userResizingDividerDrawable
+attr userResizingDividerTint
diff --git a/slidingpanelayout/slidingpanelayout/api/restricted_current.txt b/slidingpanelayout/slidingpanelayout/api/restricted_current.txt
index e49a46f..b591f76 100644
--- a/slidingpanelayout/slidingpanelayout/api/restricted_current.txt
+++ b/slidingpanelayout/slidingpanelayout/api/restricted_current.txt
@@ -46,6 +46,7 @@
     method public final void setUserResizeBehavior(androidx.slidingpanelayout.widget.SlidingPaneLayout.UserResizeBehavior userResizeBehavior);
     method public final void setUserResizingDividerDrawable(android.graphics.drawable.Drawable? drawable);
     method public final void setUserResizingDividerDrawable(@DrawableRes int resId);
+    method public final void setUserResizingDividerTint(android.content.res.ColorStateList? colorStateList);
     method public final void setUserResizingEnabled(boolean);
     method @Deprecated public void smoothSlideClosed();
     method @Deprecated public void smoothSlideOpen();
diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeDividerTintTest.kt b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeDividerTintTest.kt
new file mode 100644
index 0000000..fe06388
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeDividerTintTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.slidingpanelayout.widget
+
+import android.content.res.ColorStateList
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class UserResizeDividerTintTest {
+    @Test
+    fun userResizingDividerTint_tintIsSetToDrawable() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val view = SlidingPaneLayout(context)
+        val drawable = TestDrawable()
+
+        view.setUserResizingDividerDrawable(drawable)
+        val tint = ColorStateList.valueOf(Color.RED)
+        view.setUserResizingDividerTint(tint)
+
+        assertWithMessage("userResizingDividerTint is set to drawable")
+            .that(drawable.tint)
+            .isEqualTo(tint)
+    }
+
+    @Test
+    fun userResizingDividerTint_setDrawableAfterTint_tintIsNotSetToDrawable() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val view = SlidingPaneLayout(context)
+        val drawable = TestDrawable()
+
+        val tint = ColorStateList.valueOf(Color.RED)
+        view.setUserResizingDividerTint(tint)
+
+        view.setUserResizingDividerDrawable(drawable)
+        assertWithMessage("userResizingDividerTint is not set to drawable")
+            .that(drawable.tint)
+            .isNull()
+    }
+
+    @Test
+    fun userResizingDividerTint_setTintToNull() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val view = SlidingPaneLayout(context)
+        val drawable = TestDrawable().apply { setTintList(ColorStateList.valueOf(Color.RED)) }
+        view.setUserResizingDividerDrawable(drawable)
+
+        view.setUserResizingDividerTint(null)
+        assertWithMessage("userResizingDividerTint is set to null").that(drawable.tint).isNull()
+    }
+}
+
+private class TestDrawable : Drawable() {
+    var tint: ColorStateList? = null
+        private set
+
+    override fun draw(canvas: Canvas) {}
+
+    override fun setAlpha(alpha: Int) {}
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {}
+
+    @Suppress("DeprecatedCallableAddReplaceWith")
+    @Deprecated("Deprecated in Java")
+    override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
+
+    override fun setTintList(tint: ColorStateList?) {
+        this.tint = tint
+    }
+}
diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/res/layout/user_resize_divider_tint.xml b/slidingpanelayout/slidingpanelayout/src/androidTest/res/layout/user_resize_divider_tint.xml
new file mode 100644
index 0000000..4d7a361
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout/src/androidTest/res/layout/user_resize_divider_tint.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<androidx.slidingpanelayout.widget.SlidingPaneLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:isUserResizingEnabled="true"
+    app:userResizingDividerDrawable="@android:drawable/ic_menu_add"
+    app:userResizingDividerTint="@android:color/primary_text_dark">
+
+</androidx.slidingpanelayout.widget.SlidingPaneLayout>
\ No newline at end of file
diff --git a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
index 5b023fe..729b2b9 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
+++ b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
@@ -17,6 +17,7 @@
 package androidx.slidingpanelayout.widget
 
 import android.content.Context
+import android.content.res.ColorStateList
 import android.graphics.Canvas
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
@@ -427,6 +428,16 @@
         setUserResizingDividerDrawable(ContextCompat.getDrawable(context, resId))
     }
 
+    /**
+     * The tint color for the resizing divider [Drawable] which is set by
+     * [setUserResizingDividerDrawable]. This may also be set from `userResizingDividerTint` XML
+     * attribute during the view inflation. Note: the tint is not retained after calling
+     * [setUserResizingDividerDrawable].
+     */
+    fun setUserResizingDividerTint(colorStateList: ColorStateList?) {
+        userResizingDividerDrawable?.apply { setTintList(colorStateList) }
+    }
+
     /** `true` if the user is currently dragging the [user resizing divider][isUserResizable] */
     val isDividerDragging: Boolean
         get() = draggableDividerHandler.isDragging
@@ -581,6 +592,11 @@
                 getBoolean(R.styleable.SlidingPaneLayout_isUserResizingEnabled, false)
             userResizingDividerDrawable =
                 getDrawable(R.styleable.SlidingPaneLayout_userResizingDividerDrawable)
+            // It won't override the tint on drawable if userResizingDividerTint is not specified.
+            getColorStateList(R.styleable.SlidingPaneLayout_userResizingDividerTint)?.apply {
+                setUserResizingDividerTint(this)
+            }
+
             isChildClippingToResizeDividerEnabled =
                 getBoolean(
                     R.styleable.SlidingPaneLayout_isChildClippingToResizeDividerEnabled,
diff --git a/slidingpanelayout/slidingpanelayout/src/main/res/drawable/slidingpanelayout_divider.xml b/slidingpanelayout/slidingpanelayout/src/main/res/drawable/slidingpanelayout_divider.xml
index c5fd33b..f50ac30 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/res/drawable/slidingpanelayout_divider.xml
+++ b/slidingpanelayout/slidingpanelayout/src/main/res/drawable/slidingpanelayout_divider.xml
@@ -15,10 +15,12 @@
   limitations under the License.
   -->
 
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="#c0555555"/>
-    <size android:width="8dp" android:height="80dp"/>
-    <corners android:radius="4dp"/>
-</shape>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Pressed state -->
+    <item
+        android:state_pressed="true"
+        android:drawable="@drawable/slidingpanelayout_divider_pressed" />
+    <!-- Default state -->
+    <item
+        android:drawable="@drawable/slidingpanelayout_divider_default"/>
+</selector>
\ No newline at end of file
diff --git a/slidingpanelayout/slidingpanelayout/src/main/res/drawable/slidingpanelayout_divider_default.xml b/slidingpanelayout/slidingpanelayout/src/main/res/drawable/slidingpanelayout_divider_default.xml
new file mode 100644
index 0000000..cdcc39b1
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout/src/main/res/drawable/slidingpanelayout_divider_default.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <size android:height="48dp" android:width="4dp" />
+    <corners android:radius="2dp" />
+    <solid android:color="#ff444746" />
+</shape>
\ No newline at end of file
diff --git a/slidingpanelayout/slidingpanelayout/src/main/res/drawable/slidingpanelayout_divider_pressed.xml b/slidingpanelayout/slidingpanelayout/src/main/res/drawable/slidingpanelayout_divider_pressed.xml
new file mode 100644
index 0000000..4612c86
--- /dev/null
+++ b/slidingpanelayout/slidingpanelayout/src/main/res/drawable/slidingpanelayout_divider_pressed.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <size android:height="52dp" android:width="12dp" />
+    <corners android:radius="6dp" />
+    <solid android:color="#ff1f1f1f" />
+</shape>
\ No newline at end of file
diff --git a/slidingpanelayout/slidingpanelayout/src/main/res/values/attrs.xml b/slidingpanelayout/slidingpanelayout/src/main/res/values/attrs.xml
index 59f02be..14c032e 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/res/values/attrs.xml
+++ b/slidingpanelayout/slidingpanelayout/src/main/res/values/attrs.xml
@@ -19,6 +19,7 @@
         <attr name="isOverlappingEnabled" format="boolean"/>
         <attr name="isUserResizingEnabled" format="boolean"/>
         <attr name="userResizingDividerDrawable" format="reference"/>
+        <attr name="userResizingDividerTint" format="color"/>
         <attr name="isChildClippingToResizeDividerEnabled" format="boolean"/>
         <attr name="userResizeBehavior" format="enum">
             <enum name="relayoutWhenComplete" value="0"/>
diff --git a/slidingpanelayout/slidingpanelayout/src/main/res/values/public.xml b/slidingpanelayout/slidingpanelayout/src/main/res/values/public.xml
index 497b058..c8e7a8f 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/res/values/public.xml
+++ b/slidingpanelayout/slidingpanelayout/src/main/res/values/public.xml
@@ -18,6 +18,7 @@
     <public type="attr" name="isOverlappingEnabled"/>
     <public type="attr" name="isUserResizingEnabled"/>
     <public type="attr" name="userResizingDividerDrawable"/>
+    <public type="attr" name="userResizingDividerTint"/>
     <public type="attr" name="isChildClippingToResizeDividerEnabled"/>
     <public type="attr" name="userResizeBehavior"/>
 </resources>