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>