Merge "SwiftKey multiline for when ImeAction is unspecified" into androidx-main
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 1173c37..273ff17 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -477,7 +477,15 @@
setOf(
":compose:integration-tests:macrobenchmark",
":compose:integration-tests:macrobenchmark-target"
- ), // link compose's macrobenchmark and its target
+ ),
+ setOf(
+ ":emoji2:integration-tests:init-disabled-macrobenchmark",
+ ":emoji2:integration-tests:init-disabled-macrobenchmark-target",
+ ),
+ setOf(
+ ":emoji2:integration-tests:init-enabled-macrobenchmark",
+ ":emoji2:integration-tests:init-enabled-macrobenchmark-target",
+ ),
)
}
}
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index 2c57aeb..e14d471 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -38,6 +38,7 @@
)
dependencies {
+ implementation("androidx.core:core:1.1.0")
implementation("androidx.concurrent:concurrent-listenablefuture-callback:1.0.0-beta01")
bundleInside(project(path: ":camera:camera-camera2-pipe", configuration: "exportRelease"))
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt
new file mode 100644
index 0000000..d038a85
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.impl
+
+import android.graphics.ImageFormat
+import android.graphics.SurfaceTexture
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraDevice
+import android.os.Build
+import android.util.Size
+import android.view.Surface
+import androidx.camera.camera2.pipe.core.Log.error
+import androidx.camera.camera2.pipe.integration.adapter.CameraUseCaseAdapter
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.impl.Config
+import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.ImageFormatConstants
+import androidx.camera.core.impl.ImageInputConfig
+import androidx.camera.core.impl.ImmediateSurface
+import androidx.camera.core.impl.MutableOptionsBundle
+import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.camera.core.impl.UseCaseConfig.OPTION_SESSION_CONFIG_UNPACKER
+import androidx.camera.core.impl.UseCaseConfigFactory
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.core.util.Consumer
+
+private val DEFAULT_PREVIEW_SIZE = Size(0, 0)
+
+/**
+ * A [UseCase] used to issue repeating requests when only [androidx.camera.core.ImageCapture] is
+ * enabled, since taking a picture may require a repeating surface to perform pre-capture checks,
+ * mainly around 3A.
+ */
+class MeteringRepeating(
+ private val cameraProperties: CameraProperties,
+ config: MeteringRepeatingConfig,
+) : UseCase(config) {
+
+ private val meteringSurfaceSize = cameraProperties.getMinimumPreviewSize()
+ private var deferrableSurface: DeferrableSurface? = null
+
+ override fun getDefaultConfig(applyDefaultConfig: Boolean, factory: UseCaseConfigFactory) =
+ Builder(cameraProperties).useCaseConfig
+
+ override fun getUseCaseConfigBuilder(config: Config) = Builder(cameraProperties)
+
+ override fun onSuggestedResolutionUpdated(suggestedResolution: Size): Size {
+ updateSessionConfig(createPipeline().build())
+ notifyActive()
+ return meteringSurfaceSize
+ }
+
+ override fun onDetached() {
+ deferrableSurface?.close()
+ deferrableSurface = null
+ }
+
+ private fun createPipeline(): SessionConfig.Builder {
+ val surfaceTexture = SurfaceTexture(0).apply {
+ setDefaultBufferSize(meteringSurfaceSize.width, meteringSurfaceSize.height)
+ }
+ val surface = Surface(surfaceTexture)
+
+ deferrableSurface?.close()
+ deferrableSurface = ImmediateSurface(surface)
+ deferrableSurface!!.terminationFuture
+ .addListener(
+ {
+ surface.release()
+ surfaceTexture.release()
+ },
+ CameraXExecutors.directExecutor()
+ )
+
+ return SessionConfig.Builder
+ .createFrom(MeteringRepeatingConfig())
+ .apply {
+ setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
+ addSurface(deferrableSurface!!)
+ addErrorListener { _, _ ->
+ updateSessionConfig(createPipeline().build())
+ notifyReset()
+ }
+ }
+ }
+
+ private fun CameraProperties.getMinimumPreviewSize(): Size {
+ val map = metadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
+ if (map == null) {
+ error { "Can not retrieve SCALER_STREAM_CONFIGURATION_MAP." }
+ return DEFAULT_PREVIEW_SIZE
+ }
+
+ val outputSizes = if (Build.VERSION.SDK_INT < 23) {
+ // ImageFormat.PRIVATE is only public after Android level 23. Therefore, use
+ // SurfaceTexture.class to get the supported output sizes before Android level 23.
+ map.getOutputSizes(SurfaceTexture::class.java)
+ } else {
+ map.getOutputSizes(ImageFormat.PRIVATE)
+ }
+
+ if (outputSizes == null) {
+ error { "Can not get output size list." }
+ return DEFAULT_PREVIEW_SIZE
+ }
+
+ check(outputSizes.isNotEmpty()) { "Output sizes empty" }
+ return outputSizes.minWithOrNull { size1, size2 -> size1.area().compareTo(size2.area()) }!!
+ }
+
+ class MeteringRepeatingConfig : UseCaseConfig<MeteringRepeating>, ImageInputConfig {
+ private val config = MutableOptionsBundle.create().apply {
+ insertOption(
+ OPTION_SESSION_CONFIG_UNPACKER,
+ CameraUseCaseAdapter.DefaultSessionOptionsUnpacker
+ )
+ }
+
+ override fun getConfig() = config
+
+ override fun getInputFormat() = ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+ }
+
+ class Builder(private val cameraProperties: CameraProperties) :
+ UseCaseConfig.Builder<MeteringRepeating, MeteringRepeatingConfig, Builder> {
+
+ override fun getMutableConfig() = MutableOptionsBundle.create()
+
+ override fun getUseCaseConfig() = MeteringRepeatingConfig()
+
+ override fun setTargetClass(targetClass: Class<MeteringRepeating>) = this
+
+ override fun setTargetName(targetName: String) = this
+
+ override fun setUseCaseEventCallback(eventCallback: EventCallback) = this
+
+ override fun setDefaultSessionConfig(sessionConfig: SessionConfig) = this
+
+ override fun setDefaultCaptureConfig(captureConfig: CaptureConfig) = this
+
+ override fun setSessionOptionUnpacker(optionUnpacker: SessionConfig.OptionUnpacker) = this
+
+ override fun setCaptureOptionUnpacker(optionUnpacker: CaptureConfig.OptionUnpacker) = this
+
+ override fun setSurfaceOccupancyPriority(priority: Int) = this
+
+ override fun setCameraSelector(cameraSelector: CameraSelector) = this
+
+ override fun setAttachedUseCasesUpdateListener(
+ attachedUseCasesUpdateListener: Consumer<MutableCollection<UseCase>>
+ ): Builder = this
+
+ override fun build(): MeteringRepeating {
+ return MeteringRepeating(cameraProperties, useCaseConfig)
+ }
+ }
+}
diff --git a/camera/camera-view/api/current.txt b/camera/camera-view/api/current.txt
index 645b302..160857f 100644
--- a/camera/camera-view/api/current.txt
+++ b/camera/camera-view/api/current.txt
@@ -33,40 +33,6 @@
field public static final int IMAGE_CAPTURE = 1; // 0x1
}
- @Deprecated public final class CameraView extends android.widget.FrameLayout {
- ctor @Deprecated public CameraView(android.content.Context);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?, int);
- ctor @Deprecated @RequiresApi(21) public CameraView(android.content.Context, android.util.AttributeSet?, int, int);
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
- method @Deprecated public void enableTorch(boolean);
- method @Deprecated public Integer? getCameraLensFacing();
- method @Deprecated public androidx.camera.view.CameraView.CaptureMode getCaptureMode();
- method @Deprecated public int getFlash();
- method @Deprecated public float getMaxZoomRatio();
- method @Deprecated public float getMinZoomRatio();
- method @Deprecated public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
- method @Deprecated public androidx.camera.view.PreviewView.ScaleType getScaleType();
- method @Deprecated public float getZoomRatio();
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public boolean hasCameraWithLensFacing(int);
- method @Deprecated public boolean isPinchToZoomEnabled();
- method @Deprecated public boolean isTorchOn();
- method @Deprecated public boolean isZoomSupported();
- method @Deprecated public void setCameraLensFacing(Integer?);
- method @Deprecated public void setCaptureMode(androidx.camera.view.CameraView.CaptureMode);
- method @Deprecated public void setFlash(int);
- method @Deprecated public void setPinchToZoomEnabled(boolean);
- method @Deprecated public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
- method @Deprecated public void setZoomRatio(float);
- method @Deprecated public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
- method @Deprecated public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
- method @Deprecated public void toggleCamera();
- }
-
- @Deprecated public enum CameraView.CaptureMode {
- enum_constant @Deprecated public static final androidx.camera.view.CameraView.CaptureMode IMAGE;
- }
-
public final class LifecycleCameraController extends androidx.camera.view.CameraController {
ctor public LifecycleCameraController(android.content.Context);
method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
diff --git a/camera/camera-view/api/public_plus_experimental_current.txt b/camera/camera-view/api/public_plus_experimental_current.txt
index 6dbb420..4190144 100644
--- a/camera/camera-view/api/public_plus_experimental_current.txt
+++ b/camera/camera-view/api/public_plus_experimental_current.txt
@@ -38,47 +38,6 @@
field @androidx.camera.view.video.ExperimentalVideo public static final int VIDEO_CAPTURE = 4; // 0x4
}
- @Deprecated public final class CameraView extends android.widget.FrameLayout {
- ctor @Deprecated public CameraView(android.content.Context);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?, int);
- ctor @Deprecated @RequiresApi(21) public CameraView(android.content.Context, android.util.AttributeSet?, int, int);
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
- method @Deprecated public void enableTorch(boolean);
- method @Deprecated public Integer? getCameraLensFacing();
- method @Deprecated public androidx.camera.view.CameraView.CaptureMode getCaptureMode();
- method @Deprecated @androidx.camera.core.ImageCapture.FlashMode public int getFlash();
- method @Deprecated public float getMaxZoomRatio();
- method @Deprecated public float getMinZoomRatio();
- method @Deprecated public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
- method @Deprecated public androidx.camera.view.PreviewView.ScaleType getScaleType();
- method @Deprecated public float getZoomRatio();
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public boolean hasCameraWithLensFacing(@androidx.camera.core.CameraSelector.LensFacing int);
- method @Deprecated public boolean isPinchToZoomEnabled();
- method @Deprecated @androidx.camera.view.video.ExperimentalVideo public boolean isRecording();
- method @Deprecated public boolean isTorchOn();
- method @Deprecated public boolean isZoomSupported();
- method @Deprecated public void setCameraLensFacing(Integer?);
- method @Deprecated public void setCaptureMode(androidx.camera.view.CameraView.CaptureMode);
- method @Deprecated public void setFlash(@androidx.camera.core.ImageCapture.FlashMode int);
- method @Deprecated public void setPinchToZoomEnabled(boolean);
- method @Deprecated public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
- method @Deprecated public void setZoomRatio(float);
- method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void startRecording(java.io.File, java.util.concurrent.Executor, androidx.camera.view.video.OnVideoSavedCallback);
- method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void startRecording(android.os.ParcelFileDescriptor, java.util.concurrent.Executor, androidx.camera.view.video.OnVideoSavedCallback);
- method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void startRecording(androidx.camera.view.video.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.view.video.OnVideoSavedCallback);
- method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void stopRecording();
- method @Deprecated public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
- method @Deprecated public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
- method @Deprecated public void toggleCamera();
- }
-
- @Deprecated public enum CameraView.CaptureMode {
- enum_constant @Deprecated public static final androidx.camera.view.CameraView.CaptureMode IMAGE;
- enum_constant @Deprecated @androidx.camera.view.video.ExperimentalVideo public static final androidx.camera.view.CameraView.CaptureMode MIXED;
- enum_constant @Deprecated @androidx.camera.view.video.ExperimentalVideo public static final androidx.camera.view.CameraView.CaptureMode VIDEO;
- }
-
public final class LifecycleCameraController extends androidx.camera.view.CameraController {
ctor public LifecycleCameraController(android.content.Context);
method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
diff --git a/camera/camera-view/api/restricted_current.txt b/camera/camera-view/api/restricted_current.txt
index d0ba609..56cdeaa 100644
--- a/camera/camera-view/api/restricted_current.txt
+++ b/camera/camera-view/api/restricted_current.txt
@@ -33,40 +33,6 @@
field public static final int IMAGE_CAPTURE = 1; // 0x1
}
- @Deprecated public final class CameraView extends android.widget.FrameLayout {
- ctor @Deprecated public CameraView(android.content.Context);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?);
- ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?, int);
- ctor @Deprecated @RequiresApi(21) public CameraView(android.content.Context, android.util.AttributeSet?, int, int);
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
- method @Deprecated public void enableTorch(boolean);
- method @Deprecated public Integer? getCameraLensFacing();
- method @Deprecated public androidx.camera.view.CameraView.CaptureMode getCaptureMode();
- method @Deprecated @androidx.camera.core.ImageCapture.FlashMode public int getFlash();
- method @Deprecated public float getMaxZoomRatio();
- method @Deprecated public float getMinZoomRatio();
- method @Deprecated public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
- method @Deprecated public androidx.camera.view.PreviewView.ScaleType getScaleType();
- method @Deprecated public float getZoomRatio();
- method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public boolean hasCameraWithLensFacing(@androidx.camera.core.CameraSelector.LensFacing int);
- method @Deprecated public boolean isPinchToZoomEnabled();
- method @Deprecated public boolean isTorchOn();
- method @Deprecated public boolean isZoomSupported();
- method @Deprecated public void setCameraLensFacing(Integer?);
- method @Deprecated public void setCaptureMode(androidx.camera.view.CameraView.CaptureMode);
- method @Deprecated public void setFlash(@androidx.camera.core.ImageCapture.FlashMode int);
- method @Deprecated public void setPinchToZoomEnabled(boolean);
- method @Deprecated public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
- method @Deprecated public void setZoomRatio(float);
- method @Deprecated public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
- method @Deprecated public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
- method @Deprecated public void toggleCamera();
- }
-
- @Deprecated public enum CameraView.CaptureMode {
- enum_constant @Deprecated public static final androidx.camera.view.CameraView.CaptureMode IMAGE;
- }
-
public final class LifecycleCameraController extends androidx.camera.view.CameraController {
ctor public LifecycleCameraController(android.content.Context);
method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
diff --git a/camera/camera-view/build.gradle b/camera/camera-view/build.gradle
index 06c00c8..2b2420f 100644
--- a/camera/camera-view/build.gradle
+++ b/camera/camera-view/build.gradle
@@ -36,7 +36,7 @@
implementation("androidx.camera:camera-lifecycle:${VIEW_ATOMIC_GROUP_PINNED_VER}")
implementation("androidx.annotation:annotation-experimental:1.1.0-rc01")
implementation(GUAVA_LISTENABLE_FUTURE)
- implementation("androidx.core:core:1.1.0")
+ implementation("androidx.core:core:1.3.2")
implementation("androidx.concurrent:concurrent-futures:1.0.0")
implementation(AUTO_VALUE_ANNOTATIONS)
implementation("androidx.appcompat:appcompat:1.1.0")
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java
deleted file mode 100644
index b6106fc..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java
+++ /dev/null
@@ -1,873 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.view;
-
-import android.Manifest.permission;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.Display;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RequiresPermission;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.RestrictTo.Scope;
-import androidx.camera.core.Camera;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.core.FocusMeteringAction;
-import androidx.camera.core.FocusMeteringResult;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.Logger;
-import androidx.camera.core.MeteringPoint;
-import androidx.camera.core.MeteringPointFactory;
-import androidx.camera.core.VideoCapture;
-import androidx.camera.core.impl.LensFacingConverter;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureCallback;
-import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.camera.view.video.OnVideoSavedCallback;
-import androidx.camera.view.video.OutputFileOptions;
-import androidx.camera.view.video.OutputFileResults;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LiveData;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.io.File;
-import java.util.concurrent.Executor;
-
-/**
- * A {@link View} that displays a preview of the camera with methods {@link
- * #takePicture(Executor, OnImageCapturedCallback)},
- * {@link #takePicture(ImageCapture.OutputFileOptions, Executor, OnImageSavedCallback)},
- * {@link #startRecording(File, Executor, OnVideoSavedCallback callback)}
- * and {@link #stopRecording()}.
- *
- * <p>Because the Camera is a limited resource and consumes a high amount of power, CameraView must
- * be opened/closed. CameraView will handle opening/closing automatically through use of a {@link
- * LifecycleOwner}. Use {@link #bindToLifecycle(LifecycleOwner)} to start the camera.
- *
- * @deprecated Use {@link LifecycleCameraController}. See
- * <a href="https://medium.com/androiddevelopers/camerax-learn-how-to-use-cameracontroller
- * -e3ed10fffecf">migration guide</a>.
- */
-@Deprecated
-public final class CameraView extends FrameLayout {
- static final String TAG = CameraView.class.getSimpleName();
-
- static final int INDEFINITE_VIDEO_DURATION = -1;
- static final int INDEFINITE_VIDEO_SIZE = -1;
-
- private static final String EXTRA_SUPER = "super";
- private static final String EXTRA_ZOOM_RATIO = "zoom_ratio";
- private static final String EXTRA_PINCH_TO_ZOOM_ENABLED = "pinch_to_zoom_enabled";
- private static final String EXTRA_FLASH = "flash";
- private static final String EXTRA_MAX_VIDEO_DURATION = "max_video_duration";
- private static final String EXTRA_MAX_VIDEO_SIZE = "max_video_size";
- private static final String EXTRA_SCALE_TYPE = "scale_type";
- private static final String EXTRA_CAMERA_DIRECTION = "camera_direction";
- private static final String EXTRA_CAPTURE_MODE = "captureMode";
-
- private static final int LENS_FACING_NONE = 0;
- private static final int LENS_FACING_FRONT = 1;
- private static final int LENS_FACING_BACK = 2;
- private static final int FLASH_MODE_AUTO = 1;
- private static final int FLASH_MODE_ON = 2;
- private static final int FLASH_MODE_OFF = 4;
- // For tap-to-focus
- private long mDownEventTimestamp;
- // For pinch-to-zoom
- private PinchToZoomGestureDetector mPinchToZoomGestureDetector;
- private boolean mIsPinchToZoomEnabled = true;
- CameraXModule mCameraModule;
- private final DisplayManager.DisplayListener mDisplayListener =
- new DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) {
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- mCameraModule.invalidateView();
- }
- };
- private PreviewView mPreviewView;
- // For accessibility event
- private MotionEvent mUpEvent;
-
- public CameraView(@NonNull Context context) {
- this(context, null);
- }
-
- public CameraView(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public CameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs);
- }
-
- @RequiresApi(21)
- public CameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- init(context, attrs);
- }
-
- /**
- * Binds control of the camera used by this view to the given lifecycle.
- *
- * <p>This links opening/closing the camera to the given lifecycle. The camera will not operate
- * unless this method is called with a valid {@link LifecycleOwner} that is not in the {@link
- * androidx.lifecycle.Lifecycle.State#DESTROYED} state. Call this method only once camera
- * permissions have been obtained.
- *
- * <p>Once the provided lifecycle has transitioned to a {@link
- * androidx.lifecycle.Lifecycle.State#DESTROYED} state, CameraView must be bound to a new
- * lifecycle through this method in order to operate the camera.
- *
- * @param lifecycleOwner The lifecycle that will control this view's camera
- * @throws IllegalArgumentException if provided lifecycle is in a {@link
- * androidx.lifecycle.Lifecycle.State#DESTROYED} state.
- * @throws IllegalStateException if camera permissions are not granted.
- */
- @RequiresPermission(permission.CAMERA)
- public void bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner) {
- mCameraModule.bindToLifecycle(lifecycleOwner);
- }
-
- private void init(Context context, @Nullable AttributeSet attrs) {
- addView(mPreviewView = new PreviewView(getContext()), 0 /* view position */);
- mCameraModule = new CameraXModule(this);
-
- if (attrs != null) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView);
- setScaleType(
- PreviewView.ScaleType.fromId(
- a.getInteger(R.styleable.CameraView_scaleType,
- getScaleType().getId())));
- setPinchToZoomEnabled(
- a.getBoolean(
- R.styleable.CameraView_pinchToZoomEnabled, isPinchToZoomEnabled()));
- setCaptureMode(
- CaptureMode.fromId(
- a.getInteger(R.styleable.CameraView_captureMode,
- getCaptureMode().getId())));
-
- int lensFacing = a.getInt(R.styleable.CameraView_lensFacing, LENS_FACING_BACK);
- switch (lensFacing) {
- case LENS_FACING_NONE:
- setCameraLensFacing(null);
- break;
- case LENS_FACING_FRONT:
- setCameraLensFacing(CameraSelector.LENS_FACING_FRONT);
- break;
- case LENS_FACING_BACK:
- setCameraLensFacing(CameraSelector.LENS_FACING_BACK);
- break;
- default:
- // Unhandled event.
- }
-
- int flashMode = a.getInt(R.styleable.CameraView_flash, 0);
- switch (flashMode) {
- case FLASH_MODE_AUTO:
- setFlash(ImageCapture.FLASH_MODE_AUTO);
- break;
- case FLASH_MODE_ON:
- setFlash(ImageCapture.FLASH_MODE_ON);
- break;
- case FLASH_MODE_OFF:
- setFlash(ImageCapture.FLASH_MODE_OFF);
- break;
- default:
- // Unhandled event.
- }
-
- a.recycle();
- }
-
- if (getBackground() == null) {
- setBackgroundColor(0xFF111111);
- }
-
- mPinchToZoomGestureDetector = new PinchToZoomGestureDetector(context);
- }
-
- @Override
- @NonNull
- protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- }
-
- @Override
- @NonNull
- protected Parcelable onSaveInstanceState() {
- // TODO(b/113884082): Decide what belongs here or what should be invalidated on
- // configuration
- // change
- Bundle state = new Bundle();
- state.putParcelable(EXTRA_SUPER, super.onSaveInstanceState());
- state.putInt(EXTRA_SCALE_TYPE, getScaleType().getId());
- state.putFloat(EXTRA_ZOOM_RATIO, getZoomRatio());
- state.putBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED, isPinchToZoomEnabled());
- state.putString(EXTRA_FLASH, FlashModeConverter.nameOf(getFlash()));
- state.putLong(EXTRA_MAX_VIDEO_DURATION, getMaxVideoDuration());
- state.putLong(EXTRA_MAX_VIDEO_SIZE, getMaxVideoSize());
- if (getCameraLensFacing() != null) {
- state.putString(EXTRA_CAMERA_DIRECTION,
- LensFacingConverter.nameOf(getCameraLensFacing()));
- }
- state.putInt(EXTRA_CAPTURE_MODE, getCaptureMode().getId());
- return state;
- }
-
- @Override
- protected void onRestoreInstanceState(@Nullable Parcelable savedState) {
- // TODO(b/113884082): Decide what belongs here or what should be invalidated on
- // configuration
- // change
- if (savedState instanceof Bundle) {
- Bundle state = (Bundle) savedState;
- super.onRestoreInstanceState(state.getParcelable(EXTRA_SUPER));
- setScaleType(PreviewView.ScaleType.fromId(state.getInt(EXTRA_SCALE_TYPE)));
- setZoomRatio(state.getFloat(EXTRA_ZOOM_RATIO));
- setPinchToZoomEnabled(state.getBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED));
- setFlash(FlashModeConverter.valueOf(state.getString(EXTRA_FLASH)));
- setMaxVideoDuration(state.getLong(EXTRA_MAX_VIDEO_DURATION));
- setMaxVideoSize(state.getLong(EXTRA_MAX_VIDEO_SIZE));
- String lensFacingString = state.getString(EXTRA_CAMERA_DIRECTION);
- setCameraLensFacing(
- TextUtils.isEmpty(lensFacingString)
- ? null
- : LensFacingConverter.valueOf(lensFacingString));
- setCaptureMode(CaptureMode.fromId(state.getInt(EXTRA_CAPTURE_MODE)));
- } else {
- super.onRestoreInstanceState(savedState);
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- DisplayManager dpyMgr =
- (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
- dpyMgr.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- DisplayManager dpyMgr =
- (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
- dpyMgr.unregisterDisplayListener(mDisplayListener);
- }
-
- /**
- * Gets the {@link LiveData} of the underlying {@link PreviewView}'s
- * {@link PreviewView.StreamState}.
- *
- * @return A {@link LiveData} containing the {@link PreviewView.StreamState}. Apps can either
- * get current value by {@link LiveData#getValue()} or register a observer by
- * {@link LiveData#observe}.
- * @see PreviewView#getPreviewStreamState()
- */
- @NonNull
- public LiveData<PreviewView.StreamState> getPreviewStreamState() {
- return mPreviewView.getPreviewStreamState();
- }
-
- @NonNull
- PreviewView getPreviewView() {
- return mPreviewView;
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Since bindToLifecycle will depend on the measured dimension, only call it when measured
- // dimension is not 0x0
- if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
- mCameraModule.bindToLifecycleAfterViewMeasured();
- }
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- // In case that the CameraView size is always set as 0x0, we still need to trigger to force
- // binding to lifecycle
- mCameraModule.bindToLifecycleAfterViewMeasured();
-
- mCameraModule.invalidateView();
- super.onLayout(changed, left, top, right, bottom);
- }
-
- /**
- * @return One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, {@link
- * Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
- */
- int getDisplaySurfaceRotation() {
- Display display = getDisplay();
-
- // Null when the View is detached. If we were in the middle of a background operation,
- // better to not NPE. When the background operation finishes, it'll realize that the camera
- // was closed.
- if (display == null) {
- return 0;
- }
-
- return display.getRotation();
- }
-
- /**
- * Returns the scale type used to scale the preview.
- *
- * @return The current {@link PreviewView.ScaleType}.
- */
- @NonNull
- public PreviewView.ScaleType getScaleType() {
- return mPreviewView.getScaleType();
- }
-
- /**
- * Sets the view finder scale type.
- *
- * <p>This controls how the view finder should be scaled and positioned within the view.
- *
- * @param scaleType The desired {@link PreviewView.ScaleType}.
- */
- public void setScaleType(@NonNull PreviewView.ScaleType scaleType) {
- mPreviewView.setScaleType(scaleType);
- }
-
- /**
- * Returns the scale type used to scale the preview.
- *
- * @return The current {@link CaptureMode}.
- */
- @NonNull
- public CaptureMode getCaptureMode() {
- return mCameraModule.getCaptureMode();
- }
-
- /**
- * Sets the CameraView capture mode
- *
- * <p>This controls only image or video capture function is enabled or both are enabled.
- *
- * @param captureMode The desired {@link CaptureMode}.
- */
- public void setCaptureMode(@NonNull CaptureMode captureMode) {
- mCameraModule.setCaptureMode(captureMode);
- }
-
- /**
- * Returns the maximum duration of videos, or {@link #INDEFINITE_VIDEO_DURATION} if there is no
- * timeout.
- *
- * @hide Not currently implemented.
- */
- @RestrictTo(Scope.LIBRARY_GROUP)
- public long getMaxVideoDuration() {
- return mCameraModule.getMaxVideoDuration();
- }
-
- /**
- * Sets the maximum video duration before
- * {@link OnVideoSavedCallback#onVideoSaved(OutputFileResults)} is called
- * automatically.
- * Use {@link #INDEFINITE_VIDEO_DURATION} to disable the timeout.
- */
- private void setMaxVideoDuration(long duration) {
- mCameraModule.setMaxVideoDuration(duration);
- }
-
- /**
- * Returns the maximum size of videos in bytes, or {@link #INDEFINITE_VIDEO_SIZE} if there is no
- * timeout.
- */
- private long getMaxVideoSize() {
- return mCameraModule.getMaxVideoSize();
- }
-
- /**
- * Sets the maximum video size in bytes before
- * {@link OnVideoSavedCallback#onVideoSaved(OutputFileResults)}
- * is called automatically. Use {@link #INDEFINITE_VIDEO_SIZE} to disable the size restriction.
- */
- private void setMaxVideoSize(long size) {
- mCameraModule.setMaxVideoSize(size);
- }
-
- /**
- * Takes a picture, and calls {@link OnImageCapturedCallback#onCaptureSuccess(ImageProxy)}
- * once when done.
- *
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure callbacks.
- */
- public void takePicture(@NonNull Executor executor, @NonNull OnImageCapturedCallback callback) {
- mCameraModule.takePicture(executor, callback);
- }
-
- /**
- * Takes a picture and calls
- * {@link OnImageSavedCallback#onImageSaved(ImageCapture.OutputFileResults)} when done.
- *
- * <p> The value of {@link ImageCapture.Metadata#isReversedHorizontal()} in the
- * {@link ImageCapture.OutputFileOptions} will be overwritten based on camera direction. For
- * front camera, it will be set to true; for back camera, it will be set to false.
- *
- * @param outputFileOptions Options to store the newly captured image.
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure.
- */
- public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
- @NonNull Executor executor,
- @NonNull OnImageSavedCallback callback) {
- mCameraModule.takePicture(outputFileOptions, executor, callback);
- }
-
- /**
- * Takes a video and calls the OnVideoSavedCallback when done.
- *
- * @param file The destination.
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure.
- */
- @ExperimentalVideo
- public void startRecording(@NonNull File file, @NonNull Executor executor,
- @NonNull OnVideoSavedCallback callback) {
- OutputFileOptions options = OutputFileOptions.builder(file).build();
- startRecording(options, executor, callback);
- }
-
- /**
- * Takes a video and calls the OnVideoSavedCallback when done.
- *
- * @param fd The destination {@link ParcelFileDescriptor}.
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure.
- */
- @ExperimentalVideo
- public void startRecording(@NonNull ParcelFileDescriptor fd, @NonNull Executor executor,
- @NonNull OnVideoSavedCallback callback) {
- OutputFileOptions options = OutputFileOptions.builder(fd).build();
- startRecording(options, executor, callback);
- }
-
- /**
- * Takes a video and calls the OnVideoSavedCallback when done.
- *
- * @param outputFileOptions Options to store the newly captured video.
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure.
- */
- @ExperimentalVideo
- public void startRecording(@NonNull OutputFileOptions outputFileOptions,
- @NonNull Executor executor,
- @NonNull OnVideoSavedCallback callback) {
- VideoCapture.OnVideoSavedCallback callbackWrapper =
- new VideoCapture.OnVideoSavedCallback() {
- @Override
- public void onVideoSaved(
- @NonNull VideoCapture.OutputFileResults outputFileResults) {
- callback.onVideoSaved(
- OutputFileResults.create(outputFileResults.getSavedUri()));
- }
-
- @Override
- public void onError(int videoCaptureError, @NonNull String message,
- @Nullable Throwable cause) {
- callback.onError(videoCaptureError, message, cause);
- }
- };
-
- mCameraModule.startRecording(outputFileOptions.toVideoCaptureOutputFileOptions(), executor,
- callbackWrapper);
- }
-
- /** Stops an in progress video. */
- @ExperimentalVideo
- public void stopRecording() {
- mCameraModule.stopRecording();
- }
-
- /** @return True if currently recording. */
- @ExperimentalVideo
- public boolean isRecording() {
- return mCameraModule.isRecording();
- }
-
- /**
- * Queries whether the current device has a camera with the specified direction.
- *
- * @return True if the device supports the direction.
- * @throws IllegalStateException if the CAMERA permission is not currently granted.
- */
- @RequiresPermission(permission.CAMERA)
- public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
- return mCameraModule.hasCameraWithLensFacing(lensFacing);
- }
-
- /**
- * Toggles between the primary front facing camera and the primary back facing camera.
- *
- * <p>This will have no effect if not already bound to a lifecycle via {@link
- * #bindToLifecycle(LifecycleOwner)}.
- */
- public void toggleCamera() {
- mCameraModule.toggleCamera();
- }
-
- /**
- * Sets the desired camera by specifying desired lensFacing.
- *
- * <p>This will choose the primary camera with the specified camera lensFacing.
- *
- * <p>If called before {@link #bindToLifecycle(LifecycleOwner)}, this will set the camera to be
- * used when first bound to the lifecycle. If the specified lensFacing is not supported by the
- * device, as determined by {@link #hasCameraWithLensFacing(int)}, the first supported
- * lensFacing will be chosen when {@link #bindToLifecycle(LifecycleOwner)} is called.
- *
- * <p>If called with {@code null} AFTER binding to the lifecycle, the behavior would be
- * equivalent to unbind the use cases without the lifecycle having to be destroyed.
- *
- * @param lensFacing The desired camera lensFacing.
- */
- public void setCameraLensFacing(@Nullable Integer lensFacing) {
- mCameraModule.setCameraLensFacing(lensFacing);
- }
-
- /** Returns the currently selected lensFacing. */
- @Nullable
- public Integer getCameraLensFacing() {
- return mCameraModule.getLensFacing();
- }
-
- /** Gets the active flash strategy. */
- @ImageCapture.FlashMode
- public int getFlash() {
- return mCameraModule.getFlash();
- }
-
- /** Sets the active flash strategy. */
- public void setFlash(@ImageCapture.FlashMode int flashMode) {
- mCameraModule.setFlash(flashMode);
- }
-
- private long delta() {
- return System.currentTimeMillis() - mDownEventTimestamp;
- }
-
- @Override
- public boolean onTouchEvent(@NonNull MotionEvent event) {
- // Disable pinch-to-zoom and tap-to-focus while the camera module is paused.
- if (mCameraModule.isPaused()) {
- return false;
- }
- // Only forward the event to the pinch-to-zoom gesture detector when pinch-to-zoom is
- // enabled.
- if (isPinchToZoomEnabled()) {
- mPinchToZoomGestureDetector.onTouchEvent(event);
- }
- if (event.getPointerCount() == 2 && isPinchToZoomEnabled() && isZoomSupported()) {
- return true;
- }
-
- // Camera focus
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mDownEventTimestamp = System.currentTimeMillis();
- break;
- case MotionEvent.ACTION_UP:
- if (delta() < ViewConfiguration.getLongPressTimeout()
- && mCameraModule.isBoundToLifecycle()) {
- mUpEvent = event;
- performClick();
- }
- break;
- default:
- // Unhandled event.
- return false;
- }
- return true;
- }
-
- /**
- * Focus the position of the touch event, or focus the center of the preview for
- * accessibility events
- */
- @Override
- public boolean performClick() {
- super.performClick();
-
- final float x = (mUpEvent != null) ? mUpEvent.getX() : getX() + getWidth() / 2f;
- final float y = (mUpEvent != null) ? mUpEvent.getY() : getY() + getHeight() / 2f;
- mUpEvent = null;
-
- Camera camera = mCameraModule.getCamera();
- if (camera != null) {
- MeteringPointFactory pointFactory = mPreviewView.getMeteringPointFactory();
- float afPointWidth = 1.0f / 6.0f; // 1/6 total area
- float aePointWidth = afPointWidth * 1.5f;
- MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
- MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
-
- ListenableFuture<FocusMeteringResult> future =
- camera.getCameraControl().startFocusAndMetering(
- new FocusMeteringAction.Builder(afPoint,
- FocusMeteringAction.FLAG_AF).addPoint(aePoint,
- FocusMeteringAction.FLAG_AE).build());
- Futures.addCallback(future, new FutureCallback<FocusMeteringResult>() {
- @Override
- public void onSuccess(@Nullable FocusMeteringResult result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Throw the unexpected error.
- throw new RuntimeException(t);
- }
- }, CameraXExecutors.directExecutor());
-
- } else {
- Logger.d(TAG, "cannot access camera");
- }
-
- return true;
- }
-
- float rangeLimit(float val, float max, float min) {
- return Math.min(Math.max(val, min), max);
- }
-
- /**
- * Returns whether the view allows pinch-to-zoom.
- *
- * @return True if pinch to zoom is enabled.
- */
- public boolean isPinchToZoomEnabled() {
- return mIsPinchToZoomEnabled;
- }
-
- /**
- * Sets whether the view should allow pinch-to-zoom.
- *
- * <p>When enabled, the user can pinch the camera to zoom in/out. This only has an effect if the
- * bound camera supports zoom.
- *
- * @param enabled True to enable pinch-to-zoom.
- */
- public void setPinchToZoomEnabled(boolean enabled) {
- mIsPinchToZoomEnabled = enabled;
- }
-
- /**
- * Returns the current zoom ratio.
- *
- * @return The current zoom ratio.
- */
- public float getZoomRatio() {
- return mCameraModule.getZoomRatio();
- }
-
- /**
- * Sets the current zoom ratio.
- *
- * <p>Valid zoom values range from {@link #getMinZoomRatio()} to {@link #getMaxZoomRatio()}.
- *
- * @param zoomRatio The requested zoom ratio.
- */
- public void setZoomRatio(float zoomRatio) {
- mCameraModule.setZoomRatio(zoomRatio);
- }
-
- /**
- * Returns the minimum zoom ratio.
- *
- * <p>For most cameras this should return a zoom ratio of 1. A zoom ratio of 1 corresponds to a
- * non-zoomed image.
- *
- * @return The minimum zoom ratio.
- */
- public float getMinZoomRatio() {
- return mCameraModule.getMinZoomRatio();
- }
-
- /**
- * Returns the maximum zoom ratio.
- *
- * <p>The zoom ratio corresponds to the ratio between both the widths and heights of a
- * non-zoomed image and a maximally zoomed image for the selected camera.
- *
- * @return The maximum zoom ratio.
- */
- public float getMaxZoomRatio() {
- return mCameraModule.getMaxZoomRatio();
- }
-
- /**
- * Returns whether the bound camera supports zooming.
- *
- * @return True if the camera supports zooming.
- */
- public boolean isZoomSupported() {
- return mCameraModule.isZoomSupported();
- }
-
- /**
- * Turns on/off torch.
- *
- * @param torch True to turn on torch, false to turn off torch.
- */
- public void enableTorch(boolean torch) {
- mCameraModule.enableTorch(torch);
- }
-
- /**
- * Returns current torch status.
- *
- * @return true if torch is on , otherwise false
- */
- public boolean isTorchOn() {
- return mCameraModule.isTorchOn();
- }
-
- /**
- * The capture mode used by CameraView.
- *
- * <p>This enum can be used to determine which capture mode will be enabled for {@link
- * CameraView}.
- */
- public enum CaptureMode {
- /** A mode where image capture is enabled. */
- IMAGE(0),
- /** A mode where video capture is enabled. */
- @ExperimentalVideo
- VIDEO(1),
- /**
- * A mode where both image capture and video capture are simultaneously enabled. Note that
- * this mode may not be available on every device.
- */
- @ExperimentalVideo
- MIXED(2);
-
- private final int mId;
-
- int getId() {
- return mId;
- }
-
- CaptureMode(int id) {
- mId = id;
- }
-
- static CaptureMode fromId(int id) {
- for (CaptureMode f : values()) {
- if (f.mId == id) {
- return f;
- }
- }
- throw new IllegalArgumentException();
- }
- }
-
- static class S extends ScaleGestureDetector.SimpleOnScaleGestureListener {
- private ScaleGestureDetector.OnScaleGestureListener mListener;
-
- void setRealGestureDetector(ScaleGestureDetector.OnScaleGestureListener l) {
- mListener = l;
- }
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- return mListener.onScale(detector);
- }
- }
-
- private class PinchToZoomGestureDetector extends ScaleGestureDetector
- implements ScaleGestureDetector.OnScaleGestureListener {
- PinchToZoomGestureDetector(Context context) {
- this(context, new S());
- }
-
- PinchToZoomGestureDetector(Context context, S s) {
- super(context, s);
- s.setRealGestureDetector(this);
- }
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- float scale = detector.getScaleFactor();
-
- // Speeding up the zoom by 2X.
- if (scale > 1f) {
- scale = 1.0f + (scale - 1.0f) * 2;
- } else {
- scale = 1.0f - (1.0f - scale) * 2;
- }
-
- float newRatio = getZoomRatio() * scale;
- newRatio = rangeLimit(newRatio, getMaxZoomRatio(), getMinZoomRatio());
- setZoomRatio(newRatio);
- return true;
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- }
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java
deleted file mode 100644
index c1ad936..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java
+++ /dev/null
@@ -1,659 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.view;
-
-import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
-
-import android.Manifest.permission;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.util.Rational;
-import android.util.Size;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
-import androidx.annotation.RequiresPermission;
-import androidx.camera.core.AspectRatio;
-import androidx.camera.core.Camera;
-import androidx.camera.core.CameraInfoUnavailableException;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.Logger;
-import androidx.camera.core.Preview;
-import androidx.camera.core.TorchState;
-import androidx.camera.core.UseCase;
-import androidx.camera.core.VideoCapture;
-import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
-import androidx.camera.core.impl.LensFacingConverter;
-import androidx.camera.core.impl.utils.CameraOrientationUtil;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureCallback;
-import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.camera.lifecycle.ProcessCameraProvider;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.core.util.Preconditions;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.OnLifecycleEvent;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * CameraX use case operation built on @{link androidx.camera.core}.
- *
- * @deprecated Use {@link LifecycleCameraController}. See
- * <a href="https://medium.com/androiddevelopers/camerax-learn-how-to-use-cameracontroller
- * -e3ed10fffecf">migration guide</a>.
- */
-@Deprecated
-final class CameraXModule {
- public static final String TAG = "CameraXModule";
-
- private static final float UNITY_ZOOM_SCALE = 1f;
- private static final float ZOOM_NOT_SUPPORTED = UNITY_ZOOM_SCALE;
- private static final Rational ASPECT_RATIO_16_9 = new Rational(16, 9);
- private static final Rational ASPECT_RATIO_4_3 = new Rational(4, 3);
- private static final Rational ASPECT_RATIO_9_16 = new Rational(9, 16);
- private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
-
- private final Preview.Builder mPreviewBuilder;
- private final VideoCapture.Builder mVideoCaptureBuilder;
- private final ImageCapture.Builder mImageCaptureBuilder;
- private final CameraView mCameraView;
- final AtomicBoolean mVideoIsRecording = new AtomicBoolean(false);
- private CameraView.CaptureMode mCaptureMode = CameraView.CaptureMode.IMAGE;
- private long mMaxVideoDuration = CameraView.INDEFINITE_VIDEO_DURATION;
- private long mMaxVideoSize = CameraView.INDEFINITE_VIDEO_SIZE;
- @ImageCapture.FlashMode
- private int mFlash = FLASH_MODE_OFF;
- @Nullable
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- Camera mCamera;
- @Nullable
- private ImageCapture mImageCapture;
- @Nullable
- private VideoCapture mVideoCapture;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- Preview mPreview;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- LifecycleOwner mCurrentLifecycle;
- private final LifecycleObserver mCurrentLifecycleObserver =
- new LifecycleObserver() {
- @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
- public void onDestroy(LifecycleOwner owner) {
- if (owner == mCurrentLifecycle) {
- clearCurrentLifecycle();
- }
- }
- };
- @Nullable
- private LifecycleOwner mNewLifecycle;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- Integer mCameraLensFacing = CameraSelector.LENS_FACING_BACK;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- ProcessCameraProvider mCameraProvider;
-
- CameraXModule(CameraView view) {
- mCameraView = view;
-
- Futures.addCallback(ProcessCameraProvider.getInstance(view.getContext()),
- new FutureCallback<ProcessCameraProvider>() {
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- @Override
- public void onSuccess(@Nullable ProcessCameraProvider provider) {
- Preconditions.checkNotNull(provider);
- mCameraProvider = provider;
- if (mCurrentLifecycle != null) {
- bindToLifecycle(mCurrentLifecycle);
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- throw new RuntimeException("CameraX failed to initialize.", t);
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- mPreviewBuilder = new Preview.Builder().setTargetName("Preview");
-
- mImageCaptureBuilder = new ImageCapture.Builder().setTargetName("ImageCapture");
-
- mVideoCaptureBuilder = new VideoCapture.Builder().setTargetName("VideoCapture");
- }
-
- @RequiresPermission(permission.CAMERA)
- void bindToLifecycle(LifecycleOwner lifecycleOwner) {
- mNewLifecycle = lifecycleOwner;
-
- if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
- bindToLifecycleAfterViewMeasured();
- }
- }
-
- @OptIn(markerClass = ExperimentalVideo.class)
- @RequiresPermission(permission.CAMERA)
- void bindToLifecycleAfterViewMeasured() {
- if (mNewLifecycle == null) {
- return;
- }
-
- clearCurrentLifecycle();
- if (mNewLifecycle.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
- // Lifecycle is already in a destroyed state. Since it may have been a valid
- // lifecycle when bound, but became destroyed while waiting for layout, treat this as
- // a no-op now that we have cleared the previous lifecycle.
- mNewLifecycle = null;
- return;
- }
- mCurrentLifecycle = mNewLifecycle;
- mNewLifecycle = null;
-
- if (mCameraProvider == null) {
- // try again once the camera provider is no longer null
- return;
- }
-
- Set<Integer> available = getAvailableCameraLensFacing();
-
- if (available.isEmpty()) {
- Logger.w(TAG, "Unable to bindToLifeCycle since no cameras available");
- mCameraLensFacing = null;
- }
-
- // Ensure the current camera exists, or default to another camera
- if (mCameraLensFacing != null && !available.contains(mCameraLensFacing)) {
- Logger.w(TAG, "Camera does not exist with direction " + mCameraLensFacing);
-
- // Default to the first available camera direction
- mCameraLensFacing = available.iterator().next();
-
- Logger.w(TAG, "Defaulting to primary camera with direction " + mCameraLensFacing);
- }
-
- // Do not attempt to create use cases for a null cameraLensFacing. This could occur if
- // the user explicitly sets the LensFacing to null, or if we determined there
- // were no available cameras, which should be logged in the logic above.
- if (mCameraLensFacing == null) {
- return;
- }
-
- // Set the preferred aspect ratio as 4:3 if it is IMAGE only mode. Set the preferred aspect
- // ratio as 16:9 if it is VIDEO or MIXED mode. Then, it will be WYSIWYG when the view finder
- // is in CENTER_INSIDE mode.
-
- boolean isDisplayPortrait = getDisplayRotationDegrees() == 0
- || getDisplayRotationDegrees() == 180;
-
- Rational targetAspectRatio;
- if (getCaptureMode() == CameraView.CaptureMode.IMAGE) {
- targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_3_4 : ASPECT_RATIO_4_3;
- } else {
- mImageCaptureBuilder.setTargetAspectRatio(AspectRatio.RATIO_16_9);
- mVideoCaptureBuilder.setTargetAspectRatio(AspectRatio.RATIO_16_9);
- targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_9_16 : ASPECT_RATIO_16_9;
- }
-
- mImageCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
- mImageCapture = mImageCaptureBuilder.build();
-
- mVideoCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
- mVideoCapture = mVideoCaptureBuilder.build();
-
- // Adjusts the preview resolution according to the view size and the target aspect ratio.
- int height = (int) (getMeasuredWidth() / targetAspectRatio.floatValue());
- mPreviewBuilder.setTargetResolution(new Size(getMeasuredWidth(), height));
-
- mPreview = mPreviewBuilder.build();
- mPreview.setSurfaceProvider(mCameraView.getPreviewView().getSurfaceProvider());
-
- CameraSelector cameraSelector =
- new CameraSelector.Builder().requireLensFacing(mCameraLensFacing).build();
- if (getCaptureMode() == CameraView.CaptureMode.IMAGE) {
- mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
- mImageCapture,
- mPreview);
- } else if (getCaptureMode() == CameraView.CaptureMode.VIDEO) {
- mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
- mVideoCapture,
- mPreview);
- } else {
- mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
- mImageCapture,
- mVideoCapture, mPreview);
- }
-
- setZoomRatio(UNITY_ZOOM_SCALE);
- mCurrentLifecycle.getLifecycle().addObserver(mCurrentLifecycleObserver);
- // Enable flash setting in ImageCapture after use cases are created and binded.
- setFlash(getFlash());
- }
-
- public void open() {
- throw new UnsupportedOperationException(
- "Explicit open/close of camera not yet supported. Use bindtoLifecycle() instead.");
- }
-
- public void close() {
- throw new UnsupportedOperationException(
- "Explicit open/close of camera not yet supported. Use bindtoLifecycle() instead.");
- }
-
- @OptIn(markerClass = ExperimentalVideo.class)
- public void takePicture(Executor executor, OnImageCapturedCallback callback) {
- if (mImageCapture == null) {
- return;
- }
-
- if (getCaptureMode() == CameraView.CaptureMode.VIDEO) {
- throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
- }
-
- if (callback == null) {
- throw new IllegalArgumentException("OnImageCapturedCallback should not be empty");
- }
-
- mImageCapture.takePicture(executor, callback);
- }
-
- @OptIn(markerClass = ExperimentalVideo.class)
- public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
- @NonNull Executor executor, OnImageSavedCallback callback) {
- if (mImageCapture == null) {
- return;
- }
-
- if (getCaptureMode() == CameraView.CaptureMode.VIDEO) {
- throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
- }
-
- if (callback == null) {
- throw new IllegalArgumentException("OnImageSavedCallback should not be empty");
- }
-
- outputFileOptions.getMetadata().setReversedHorizontal(mCameraLensFacing != null
- && mCameraLensFacing == CameraSelector.LENS_FACING_FRONT);
- mImageCapture.takePicture(outputFileOptions, executor, callback);
- }
-
- public void startRecording(VideoCapture.OutputFileOptions outputFileOptions,
- Executor executor, final OnVideoSavedCallback callback) {
- if (mVideoCapture == null) {
- return;
- }
-
- if (getCaptureMode() == CameraView.CaptureMode.IMAGE) {
- throw new IllegalStateException("Can not record video under IMAGE capture mode.");
- }
-
- if (callback == null) {
- throw new IllegalArgumentException("OnVideoSavedCallback should not be empty");
- }
-
- mVideoIsRecording.set(true);
- mVideoCapture.startRecording(
- outputFileOptions,
- executor,
- new VideoCapture.OnVideoSavedCallback() {
- @Override
- public void onVideoSaved(
- @NonNull VideoCapture.OutputFileResults outputFileResults) {
- mVideoIsRecording.set(false);
- callback.onVideoSaved(outputFileResults);
- }
-
- @Override
- public void onError(
- @VideoCapture.VideoCaptureError int videoCaptureError,
- @NonNull String message,
- @Nullable Throwable cause) {
- mVideoIsRecording.set(false);
- Logger.e(TAG, message, cause);
- callback.onError(videoCaptureError, message, cause);
- }
- });
- }
-
- public void stopRecording() {
- if (mVideoCapture == null) {
- return;
- }
-
- mVideoCapture.stopRecording();
- }
-
- public boolean isRecording() {
- return mVideoIsRecording.get();
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- public void setCameraLensFacing(@Nullable Integer lensFacing) {
- // Setting same lens facing is a no-op, so check for that first
- if (!Objects.equals(mCameraLensFacing, lensFacing)) {
- // If we're not bound to a lifecycle, just update the camera that will be opened when we
- // attach to a lifecycle.
- mCameraLensFacing = lensFacing;
-
- if (mCurrentLifecycle != null) {
- // Re-bind to lifecycle with new camera
- bindToLifecycle(mCurrentLifecycle);
- }
- }
- }
-
- @RequiresPermission(permission.CAMERA)
- public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
- if (mCameraProvider == null) {
- return false;
- }
- try {
- return mCameraProvider.hasCamera(
- new CameraSelector.Builder().requireLensFacing(lensFacing).build());
- } catch (CameraInfoUnavailableException e) {
- return false;
- }
- }
-
- @Nullable
- public Integer getLensFacing() {
- return mCameraLensFacing;
- }
-
- public void toggleCamera() {
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- Set<Integer> availableCameraLensFacing = getAvailableCameraLensFacing();
-
- if (availableCameraLensFacing.isEmpty()) {
- return;
- }
-
- if (mCameraLensFacing == null) {
- setCameraLensFacing(availableCameraLensFacing.iterator().next());
- return;
- }
-
- if (mCameraLensFacing == CameraSelector.LENS_FACING_BACK
- && availableCameraLensFacing.contains(CameraSelector.LENS_FACING_FRONT)) {
- setCameraLensFacing(CameraSelector.LENS_FACING_FRONT);
- return;
- }
-
- if (mCameraLensFacing == CameraSelector.LENS_FACING_FRONT
- && availableCameraLensFacing.contains(CameraSelector.LENS_FACING_BACK)) {
- setCameraLensFacing(CameraSelector.LENS_FACING_BACK);
- return;
- }
- }
-
- public float getZoomRatio() {
- if (mCamera != null) {
- return mCamera.getCameraInfo().getZoomState().getValue().getZoomRatio();
- } else {
- return UNITY_ZOOM_SCALE;
- }
- }
-
- public void setZoomRatio(float zoomRatio) {
- if (mCamera != null) {
- ListenableFuture<Void> future = mCamera.getCameraControl().setZoomRatio(
- zoomRatio);
- Futures.addCallback(future, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Throw the unexpected error.
- throw new RuntimeException(t);
- }
- }, CameraXExecutors.directExecutor());
- } else {
- Logger.e(TAG, "Failed to set zoom ratio");
- }
- }
-
- public float getMinZoomRatio() {
- if (mCamera != null) {
- return mCamera.getCameraInfo().getZoomState().getValue().getMinZoomRatio();
- } else {
- return UNITY_ZOOM_SCALE;
- }
- }
-
- public float getMaxZoomRatio() {
- if (mCamera != null) {
- return mCamera.getCameraInfo().getZoomState().getValue().getMaxZoomRatio();
- } else {
- return ZOOM_NOT_SUPPORTED;
- }
- }
-
- public boolean isZoomSupported() {
- return getMaxZoomRatio() != ZOOM_NOT_SUPPORTED;
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- private void rebindToLifecycle() {
- if (mCurrentLifecycle != null) {
- bindToLifecycle(mCurrentLifecycle);
- }
- }
-
- boolean isBoundToLifecycle() {
- return mCamera != null;
- }
-
- int getRelativeCameraOrientation(boolean compensateForMirroring) {
- int rotationDegrees = 0;
- if (mCamera != null) {
- rotationDegrees =
- mCamera.getCameraInfo().getSensorRotationDegrees(getDisplaySurfaceRotation());
- if (compensateForMirroring) {
- rotationDegrees = (360 - rotationDegrees) % 360;
- }
- }
-
- return rotationDegrees;
- }
-
- public void invalidateView() {
- updateViewInfo();
- }
-
- void clearCurrentLifecycle() {
- if (mCurrentLifecycle != null && mCameraProvider != null) {
- // Remove previous use cases
- List<UseCase> toUnbind = new ArrayList<>();
- if (mImageCapture != null && mCameraProvider.isBound(mImageCapture)) {
- toUnbind.add(mImageCapture);
- }
- if (mVideoCapture != null && mCameraProvider.isBound(mVideoCapture)) {
- toUnbind.add(mVideoCapture);
- }
- if (mPreview != null && mCameraProvider.isBound(mPreview)) {
- toUnbind.add(mPreview);
- }
-
- if (!toUnbind.isEmpty()) {
- mCameraProvider.unbind(toUnbind.toArray((new UseCase[0])));
- }
-
- // Remove surface provider once unbound.
- if (mPreview != null) {
- mPreview.setSurfaceProvider(null);
- }
- }
- mCamera = null;
- mCurrentLifecycle = null;
- }
-
- // Update view related information used in use cases
- private void updateViewInfo() {
- if (mImageCapture != null) {
- mImageCapture.setCropAspectRatio(new Rational(getWidth(), getHeight()));
- mImageCapture.setTargetRotation(getDisplaySurfaceRotation());
- }
-
- if (mVideoCapture != null) {
- mVideoCapture.setTargetRotation(getDisplaySurfaceRotation());
- }
- }
-
- @RequiresPermission(permission.CAMERA)
- private Set<Integer> getAvailableCameraLensFacing() {
- // Start with all camera directions
- Set<Integer> available = new LinkedHashSet<>(Arrays.asList(LensFacingConverter.values()));
-
- // If we're bound to a lifecycle, remove unavailable cameras
- if (mCurrentLifecycle != null) {
- if (!hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK)) {
- available.remove(CameraSelector.LENS_FACING_BACK);
- }
-
- if (!hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT)) {
- available.remove(CameraSelector.LENS_FACING_FRONT);
- }
- }
-
- return available;
- }
-
- @ImageCapture.FlashMode
- public int getFlash() {
- return mFlash;
- }
-
- public void setFlash(@ImageCapture.FlashMode int flash) {
- this.mFlash = flash;
-
- if (mImageCapture == null) {
- // Do nothing if there is no imageCapture
- return;
- }
-
- mImageCapture.setFlashMode(flash);
- }
-
- public void enableTorch(boolean torch) {
- if (mCamera == null) {
- return;
- }
- ListenableFuture<Void> future = mCamera.getCameraControl().enableTorch(torch);
- Futures.addCallback(future, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Throw the unexpected error.
- throw new RuntimeException(t);
- }
- }, CameraXExecutors.directExecutor());
- }
-
- public boolean isTorchOn() {
- if (mCamera == null) {
- return false;
- }
- return mCamera.getCameraInfo().getTorchState().getValue() == TorchState.ON;
- }
-
- public Context getContext() {
- return mCameraView.getContext();
- }
-
- public int getWidth() {
- return mCameraView.getWidth();
- }
-
- public int getHeight() {
- return mCameraView.getHeight();
- }
-
- public int getDisplayRotationDegrees() {
- return CameraOrientationUtil.surfaceRotationToDegrees(getDisplaySurfaceRotation());
- }
-
- protected int getDisplaySurfaceRotation() {
- return mCameraView.getDisplaySurfaceRotation();
- }
-
- private int getMeasuredWidth() {
- return mCameraView.getMeasuredWidth();
- }
-
- private int getMeasuredHeight() {
- return mCameraView.getMeasuredHeight();
- }
-
- @Nullable
- public Camera getCamera() {
- return mCamera;
- }
-
- @NonNull
- public CameraView.CaptureMode getCaptureMode() {
- return mCaptureMode;
- }
-
- public void setCaptureMode(@NonNull CameraView.CaptureMode captureMode) {
- this.mCaptureMode = captureMode;
- rebindToLifecycle();
- }
-
- public long getMaxVideoDuration() {
- return mMaxVideoDuration;
- }
-
- public void setMaxVideoDuration(long duration) {
- mMaxVideoDuration = duration;
- }
-
- public long getMaxVideoSize() {
- return mMaxVideoSize;
- }
-
- public void setMaxVideoSize(long size) {
- mMaxVideoSize = size;
- }
-
- public boolean isPaused() {
- return false;
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
index 9459be0..89775f6 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
@@ -69,6 +69,7 @@
import androidx.camera.view.transform.CoordinateTransform;
import androidx.camera.view.transform.OutputTransform;
import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -156,6 +157,7 @@
@SuppressWarnings("WeakerAccess")
final Preview.SurfaceProvider mSurfaceProvider = new Preview.SurfaceProvider() {
+ @SuppressLint("NewApi")
@OptIn(markerClass = ExperimentalUseCaseGroup.class)
@Override
@AnyThread
@@ -230,10 +232,8 @@
Threads.checkMainThread();
final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.PreviewView, defStyleAttr, defStyleRes);
- if (Build.VERSION.SDK_INT >= 29) {
- saveAttributeDataForStyleable(context, R.styleable.PreviewView, attrs, attributes,
- defStyleAttr, defStyleRes);
- }
+ ViewCompat.saveAttributeDataForStyleable(this, context, R.styleable.PreviewView, attrs,
+ attributes, defStyleAttr, defStyleRes);
try {
final int scaleTypeId = attributes.getInteger(
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java b/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
index 4110046..0d9a473 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
@@ -16,7 +16,6 @@
package androidx.camera.view;
-import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.util.Size;
import android.view.PixelCopy;
@@ -28,6 +27,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.annotation.UiThread;
import androidx.camera.core.Logger;
import androidx.camera.core.SurfaceRequest;
@@ -114,7 +114,7 @@
* would introduced in API level 24. PreviewView doesn't currently use a SurfaceView on API
* levels below 24.
*/
- @TargetApi(24)
+ @RequiresApi(24)
@Nullable
@Override
Bitmap getPreviewBitmap() {
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt
deleted file mode 100644
index 65a5116..0000000
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.integration.view
-
-import androidx.camera.testing.CameraUtil
-import androidx.camera.testing.CoreAppTestUtil
-import androidx.camera.view.PreviewView
-import androidx.core.os.bundleOf
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.testing.FragmentScenario
-import androidx.fragment.app.testing.launchFragmentInContainer
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.asFlow
-import androidx.lifecycle.testing.TestLifecycleOwner
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.GrantPermissionRule
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.runBlocking
-import org.junit.Assume
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TestRule
-import org.junit.runner.RunWith
-
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-class CameraViewFragmentTest {
-
- @get:Rule
- val useCamera: TestRule = CameraUtil.grantCameraPermissionAndPreTest()
-
- @get:Rule
- val permissionRule: GrantPermissionRule =
- GrantPermissionRule.grant(
- android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
- android.Manifest.permission.RECORD_AUDIO
- )
-
- @Before
- fun setup() {
- Assume.assumeTrue(CameraUtil.deviceHasCamera())
- CoreAppTestUtil.assumeCompatibleDevice()
- // Clear the device UI and check if there is no dialog or lock screen on the top of the
- // window before start the test.
- CoreAppTestUtil.prepareDeviceUI(InstrumentationRegistry.getInstrumentation())
- }
-
- @Test
- fun cameraView_canStream_defaultLifecycle() = runBlocking {
- with(launchFragmentInContainer<CameraViewFragment>()) { assertStreaming() }
- }
-
- @Test
- fun cameraView_canStream_withActivityLifecycle() = runBlocking {
- with(
- launchFragmentInContainer<CameraViewFragment>(
- fragmentArgs = bundleOf(
- CameraViewFragment.ARG_LIFECYCLE_TYPE to
- CameraViewFragment.LIFECYCLE_TYPE_ACTIVITY
- )
- )
- ) { assertStreaming() }
- }
-
- @Test
- fun cameraView_canStream_withFragmentLifecycle() = runBlocking {
- with(
- launchFragmentInContainer<CameraViewFragment>(
- fragmentArgs = bundleOf(
- CameraViewFragment.ARG_LIFECYCLE_TYPE to
- CameraViewFragment.LIFECYCLE_TYPE_FRAGMENT
- )
- )
- ) { assertStreaming() }
- }
-
- @Test
- fun cameraView_canStream_withFragmentViewLifecycle() = runBlocking {
- with(
- launchFragmentInContainer<CameraViewFragment>(
- fragmentArgs = bundleOf(
- CameraViewFragment.ARG_LIFECYCLE_TYPE to
- CameraViewFragment.LIFECYCLE_TYPE_FRAGMENT_VIEW
- )
- )
- ) { assertStreaming() }
- }
-
- @Test
- fun cameraView_ignoresLifecycleInDestroyedState() {
- // Since launchFragmentInContainer waits for onResume() to complete, CameraView should
- // have measured its view and bound to the lifecycle by this time. This would crash with
- // an IllegalArgumentException prior to applying fix for b/157949175
- launchFragmentInContainer(
- fragmentArgs = bundleOf(
- CameraViewFragment.ARG_LIFECYCLE_TYPE to CameraViewFragment.LIFECYCLE_TYPE_DEBUG
- ),
- instantiate = {
- CameraViewFragment().apply {
- setDebugLifecycleOwner(TestLifecycleOwner(Lifecycle.State.DESTROYED))
- }
- }
- )
- }
-}
-
-@Suppress("DEPRECATION")
-private suspend inline fun FragmentScenario<CameraViewFragment>.assertStreaming() {
- val streamState = withFragment {
- (view?.findViewById<androidx.camera.view.CameraView>(R.id.camera)?.previewStreamState)!!
- }.asFlow().first {
- it == PreviewView.StreamState.STREAMING
- }
-
- assertThat(streamState).isEqualTo(PreviewView.StreamState.STREAMING)
-}
-
-// Adapted from ActivityScenario.withActivity extension function
-private inline fun <reified F : Fragment, T : Any> FragmentScenario<F>.withFragment(
- crossinline block: F.() -> T
-): T {
- lateinit var value: T
- var err: Throwable? = null
- onFragment { fragment ->
- try {
- value = block(fragment)
- } catch (t: Throwable) {
- err = t
- }
- }
- err?.let { throw it }
- return value
-}
\ No newline at end of file
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraViewFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraViewFragment.java
deleted file mode 100644
index 6a5e574..0000000
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraViewFragment.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.integration.view;
-
-import android.app.Activity;
-import android.content.pm.PackageManager;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.CompoundButton;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.view.CameraView;
-import androidx.camera.view.CameraView.CaptureMode;
-import androidx.camera.view.PreviewView;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.Fragment;
-import androidx.lifecycle.LifecycleOwner;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.Locale;
-import java.util.Objects;
-
-/** The main camera fragment. */
-public class CameraViewFragment extends Fragment {
- private static final String TAG = "CameraViewFragment";
-
- // Possible values for this intent key are the name values of LensFacing encoded as
- // strings (case-insensitive): "back", "front".
- private static final String INTENT_EXTRA_CAMERA_DIRECTION = "camera_direction";
-
- // Possible values for this intent key are the name values of CameraView.CaptureMode encoded as
- // strings (case-insensitive): "image", "video", "mixed"
- private static final String INTENT_EXTRA_CAPTURE_MODE = "captureMode";
-
- // The time-out to wait for the ready of CameraProver in the CameraXModule of CameraView.
- private static final int CAMERA_PROVIDER_READY_TIMEOUT = 2000;
-
- // Argument key which determines the lifecycle used to control the camera of CameraView.
- // Possible values for this argument key are LIFECYCLE_TYPE_ACTIVITY, LIFECYCLE_TYPE_FRAGMENT,
- // LIFECYCLE_TYPE_FRAGMENT_VIEW, LIFECYCLE_TYPE_CUSTOM. If using LIFECYCLE_TYPE_DEBUG, then
- // a lifecycle must be provided via setDebugLifecycleOwner().
- static final String ARG_LIFECYCLE_TYPE = "lifecycle_type";
-
- // Fragment's Activity lifecycle
- static final String LIFECYCLE_TYPE_ACTIVITY = "activity";
- // Fragment lifecycle (this). This is the default lifecycle used by this fragment
- static final String LIFECYCLE_TYPE_FRAGMENT = "fragment";
- // Fragment's View lifecycle (getViewLifecycleOwner())
- static final String LIFECYCLE_TYPE_FRAGMENT_VIEW = "fragment_view";
- // Lifecycle provided by setDebugLifecycleOwner
- static final String LIFECYCLE_TYPE_DEBUG = "debug";
-
-
- private View mCameraHolder;
- CameraView mCameraView;
- private View mCaptureView;
- private CompoundButton mModeButton;
- @Nullable
- private CompoundButton mToggleCameraButton;
- private CompoundButton mToggleCropButton;
- @Nullable
- private LifecycleOwner mDebugLifecycleOwner;
-
- /**
- * Sets the debug lifecycle used by this fragment IF the fragment has argument
- * {@link #ARG_LIFECYCLE_TYPE} set to {@link #LIFECYCLE_TYPE_DEBUG}.
- *
- * <p>This lifecycle must be set before the fragment lifecycle reaches
- * {@link #onViewStateRestored(Bundle)}, or it will be ignored.
- *
- * <p>This value set here is not retained across fragment recreation, so it is only safe to
- * use for debugging/testing purposes.
- */
- public void setDebugLifecycleOwner(@NonNull LifecycleOwner owner) {
- mDebugLifecycleOwner = owner;
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- mCameraHolder = view.findViewById(R.id.layout_camera);
- mCameraView = view.findViewById(R.id.camera);
- mToggleCameraButton = view.findViewById(R.id.toggle);
- mToggleCropButton = view.findViewById(R.id.toggle_crop);
- mCaptureView = mCameraHolder.findViewById(R.id.capture);
- if (mCameraHolder == null) {
- throw new IllegalStateException("No View found with id R.id.layout_camera");
- }
- if (mCameraView == null) {
- throw new IllegalStateException("No CameraView found with id R.id.camera");
- }
- if (mCaptureView == null) {
- throw new IllegalStateException("No CameraView found with id R.id.capture");
- }
-
- mModeButton = mCameraHolder.findViewById(R.id.mode);
-
- if (mModeButton == null) {
- throw new IllegalStateException("No View found with id R.id.mode");
- }
-
- // Log the location of some views, so their locations can be used to perform some automated
- // clicks in tests.
- logCenterCoordinates(mCameraView, "camera_view");
- logCenterCoordinates(mCaptureView, "capture");
- logCenterCoordinates(mToggleCameraButton, "toggle_camera");
- logCenterCoordinates(mToggleCropButton, "toggle_crop");
- logCenterCoordinates(mModeButton, "mode");
-
- // Get extra option for setting initial camera direction
- Bundle bundle = getActivity().getIntent().getExtras();
- if (bundle != null) {
- final String cameraDirectionString = bundle.getString(INTENT_EXTRA_CAMERA_DIRECTION);
- final boolean isCameraDirectionValid =
- cameraDirectionString != null && (cameraDirectionString.equalsIgnoreCase("BACK")
- || cameraDirectionString.equalsIgnoreCase("FRONT"));
- if (isCameraDirectionValid) {
- @CameraSelector.LensFacing int lensFacing = cameraDirectionString.equalsIgnoreCase(
- "BACK")
- ? CameraSelector.LENS_FACING_BACK : CameraSelector.LENS_FACING_FRONT;
- mCameraView.setCameraLensFacing(lensFacing);
- }
-
- String captureModeString = bundle.getString(INTENT_EXTRA_CAPTURE_MODE);
- if (captureModeString != null) {
- CaptureMode captureMode = CaptureMode.valueOf(captureModeString.toUpperCase());
- mCameraView.setCaptureMode(captureMode);
- }
- }
- }
-
- @Override
- @OptIn(markerClass = ExperimentalVideo.class)
- public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
- super.onViewStateRestored(savedInstanceState);
-
- if (ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.CAMERA)
- != PackageManager.PERMISSION_GRANTED) {
- throw new IllegalStateException("App has not been granted CAMERA permission");
- }
-
- // Set the lifecycle that will be used to control the camera
- Bundle args = getArguments();
- String lifecycleType = args == null ? LIFECYCLE_TYPE_FRAGMENT :
- args.getString(ARG_LIFECYCLE_TYPE, LIFECYCLE_TYPE_FRAGMENT);
- Log.d(TAG, "Attempting bindToLifecycle with lifecycle type: " + lifecycleType);
- switch (lifecycleType) {
- case LIFECYCLE_TYPE_ACTIVITY:
- mCameraView.bindToLifecycle(requireActivity());
- break;
- case LIFECYCLE_TYPE_FRAGMENT:
- mCameraView.bindToLifecycle(CameraViewFragment.this);
- break;
- case LIFECYCLE_TYPE_FRAGMENT_VIEW:
- mCameraView.bindToLifecycle(Objects.requireNonNull(getViewLifecycleOwner()));
- break;
- case LIFECYCLE_TYPE_DEBUG:
- if (mDebugLifecycleOwner == null) {
- throw new IllegalStateException("Lifecycle type set to DEBUG, but no debug "
- + "lifecycle exists. setDebugLifecycleOwner() must be called before "
- + "onViewStateRestored()");
- }
- mCameraView.bindToLifecycle(mDebugLifecycleOwner);
- break;
- default:
- throw new IllegalArgumentException(String.format(Locale.US, "Invalid lifecycle "
- + "type: %s. Valid options are %s, %s, %s, and %s.", lifecycleType,
- LIFECYCLE_TYPE_ACTIVITY, LIFECYCLE_TYPE_FRAGMENT,
- LIFECYCLE_TYPE_FRAGMENT_VIEW, LIFECYCLE_TYPE_DEBUG));
- }
-
- mCameraView.setPinchToZoomEnabled(true);
- mCaptureView.setOnTouchListener(new CaptureViewOnTouchListener(mCameraView));
-
- // Set clickable, Let the cameraView can be interacted by Voice Access
- mCameraView.setClickable(true);
-
- // CameraView.hasCameraWithLensFacing need to wait for the ready of CameraProvider. Check
- // the b/183916771 for the workaround.
- Handler handler = new Handler(Looper.getMainLooper());
- handler.postDelayed(() -> {
- if (mToggleCameraButton != null) {
- if (mToggleCameraButton != null) {
- mToggleCameraButton.setVisibility(
- (mCameraView.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK)
- && mCameraView.hasCameraWithLensFacing(
- CameraSelector.LENS_FACING_FRONT))
- ? View.VISIBLE
- : View.INVISIBLE);
- mToggleCameraButton.setChecked(
- mCameraView.getCameraLensFacing() == CameraSelector.LENS_FACING_FRONT);
- }
- }
- }, CAMERA_PROVIDER_READY_TIMEOUT);
-
- // Set listeners here, or else restoring state will trigger them.
- if (mToggleCameraButton != null) {
- mToggleCameraButton.setOnCheckedChangeListener(
- new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton b, boolean checked) {
- mCameraView.setCameraLensFacing(
- checked ? CameraSelector.LENS_FACING_FRONT
- : CameraSelector.LENS_FACING_BACK);
- }
- });
- }
-
- mToggleCropButton.setChecked(
- mCameraView.getScaleType() == PreviewView.ScaleType.FILL_CENTER);
- mToggleCropButton.setOnCheckedChangeListener((b, checked) -> {
- if (checked) {
- mCameraView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
- } else {
- mCameraView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
- }
- });
-
- if (mModeButton != null) {
- updateModeButtonIcon();
-
- mModeButton.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (mCameraView.isRecording()) {
- Toast.makeText(
- CameraViewFragment.this.getContext(),
- "Can not switch mode during video recording.",
- Toast.LENGTH_SHORT)
- .show();
- return;
- }
-
- if (mCameraView.getCaptureMode() == CaptureMode.MIXED) {
- mCameraView.setCaptureMode(CaptureMode.IMAGE);
- } else if (mCameraView.getCaptureMode() == CaptureMode.IMAGE) {
- mCameraView.setCaptureMode(CaptureMode.VIDEO);
- } else {
- mCameraView.setCaptureMode(CaptureMode.MIXED);
- }
-
- CameraViewFragment.this.updateModeButtonIcon();
- }
- });
- }
- }
-
- @OptIn(markerClass = ExperimentalVideo.class)
- void updateModeButtonIcon() {
- if (mCameraView.getCaptureMode() == CaptureMode.MIXED) {
- mModeButton.setButtonDrawable(R.drawable.ic_photo_camera);
- } else if (mCameraView.getCaptureMode() == CaptureMode.IMAGE) {
- mModeButton.setButtonDrawable(R.drawable.ic_camera);
- } else {
- mModeButton.setButtonDrawable(R.drawable.ic_videocam);
- }
- }
-
- @Override
- @NonNull
- public View onCreateView(
- @NonNull LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment_main, container, false);
- }
-
- private void logCenterCoordinates(View view, String name) {
- view.getViewTreeObserver()
- .addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- Activity activity = getActivity();
- if (activity == null) {
- // The fragment has been detached from the parent Activity.
- return;
- }
- Rect rect = new Rect();
- view.getGlobalVisibleRect(rect);
- Log.d(
- TAG,
- "View "
- + name
- + " Center "
- + rect.centerX()
- + " "
- + rect.centerY());
- File externalDir = activity.getExternalFilesDir(null);
- File logFile =
- new File(externalDir, name + "_button_coordinates.txt");
- try (PrintStream stream = new PrintStream(logFile)) {
- stream.print(rect.centerX() + " " + rect.centerY());
- } catch (IOException e) {
- Log.e(TAG, "Could not save to " + logFile, e);
- }
- }
- });
- }
-}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java
deleted file mode 100644
index 0f08848..0000000
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.integration.view;
-
-import android.content.ContentValues;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.ImageCaptureException;
-import androidx.camera.view.CameraView;
-import androidx.camera.view.CameraView.CaptureMode;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.camera.view.video.OnVideoSavedCallback;
-import androidx.camera.view.video.OutputFileResults;
-import androidx.core.content.ContextCompat;
-
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.Locale;
-
-/**
- * A {@link View.OnTouchListener} which converts a view's touches into camera actions.
- *
- * <p>The listener converts touches on a {@link View}, such as a button, into appropriate photo
- * taking or video recording actions through a {@link CameraView}. A click is interpreted as a
- * take-photo signal, while a long-press is interpreted as a record-video signal.
- */
-class CaptureViewOnTouchListener implements View.OnTouchListener {
- private static final String TAG = "ViewOnTouchListener";
-
- private static final String FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS";
- private static final String PHOTO_EXTENSION = ".jpg";
- private static final String VIDEO_EXTENSION = ".mp4";
-
- private static final int TAP = 1;
- private static final int HOLD = 2;
- private static final int RELEASE = 3;
-
- private final long mLongPress = ViewConfiguration.getLongPressTimeout();
- private final CameraView mCameraView;
-
- // TODO: Use a Handler for a background thread, rather than running on the current (main)
- // thread.
- private final Handler mHandler =
- new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case TAP:
- onTap();
- break;
- case HOLD:
- onHold();
- if (mCameraView.getMaxVideoDuration() > 0) {
- sendEmptyMessageDelayed(RELEASE, mCameraView.getMaxVideoDuration());
- }
- break;
- case RELEASE:
- onRelease();
- break;
- default:
- // No op
- }
- }
- };
-
- private long mDownEventTimestamp;
- private Rect mViewBoundsRect;
-
- /** Creates a new listener which links to the given {@link CameraView}. */
- CaptureViewOnTouchListener(CameraView cameraView) {
- mCameraView = cameraView;
- }
-
- /** Called when the user taps. */
- @OptIn(markerClass = ExperimentalVideo.class)
- private void onTap() {
- if (mCameraView.getCaptureMode() == CaptureMode.IMAGE
- || mCameraView.getCaptureMode() == CaptureMode.MIXED) {
-
- File saveFile = createNewFile(PHOTO_EXTENSION);
- ImageCapture.OutputFileOptions outputFileOptions;
- ContentValues contentValues = new ContentValues();
- contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, saveFile.getName());
- contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH,
- Environment.DIRECTORY_PICTURES);
- } else {
- contentValues.put(MediaStore.MediaColumns.DATA, saveFile.getAbsolutePath());
- }
- outputFileOptions = new ImageCapture.OutputFileOptions.Builder(
- mCameraView.getContext().getContentResolver(),
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- contentValues).build();
-
- mCameraView.takePicture(outputFileOptions,
- ContextCompat.getMainExecutor(mCameraView.getContext()),
- new OnImageSavedCallback() {
- @Override
- public void onImageSaved(
- @NonNull ImageCapture.OutputFileResults outputFileResults) {
- report("Picture saved to " + saveFile.getAbsolutePath());
- // Print out metadata about the picture
- // TODO: Print out metadata to log once metadata is implemented
- }
-
- @Override
- public void onError(@NonNull ImageCaptureException exception) {
- report("Failure: " + exception.getMessage(), exception.getCause());
- }
- });
- }
- }
-
- /** Called when the user holds (long presses). */
- @OptIn(markerClass = ExperimentalVideo.class)
- private void onHold() {
- if (mCameraView.getCaptureMode() == CaptureMode.VIDEO
- || mCameraView.getCaptureMode() == CaptureMode.MIXED) {
-
- final File saveFile = createNewFile(VIDEO_EXTENSION);
- mCameraView.startRecording(saveFile,
- ContextCompat.getMainExecutor(mCameraView.getContext()),
- new OnVideoSavedCallback() {
- @Override
- public void onVideoSaved(
- @NonNull OutputFileResults outputFileResults) {
- report("Video saved to " + outputFileResults.getSavedUri());
- }
-
- @Override
- public void onError(int videoCaptureError, @NonNull String message,
- @Nullable Throwable cause) {
- report("Failure: " + message, cause);
- }
- });
- }
- }
-
- /** Called when the user releases. */
- @OptIn(markerClass = ExperimentalVideo.class)
- private void onRelease() {
- if (mCameraView.getCaptureMode() == CaptureMode.VIDEO
- || mCameraView.getCaptureMode() == CaptureMode.MIXED) {
- mCameraView.stopRecording();
- }
- }
-
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mDownEventTimestamp = System.currentTimeMillis();
- mViewBoundsRect =
- new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
- mHandler.sendEmptyMessageDelayed(HOLD, mLongPress);
- view.setPressed(true);
- break;
- case MotionEvent.ACTION_MOVE:
- // If the user moves their finger off the button, trigger RELEASE
- if (mViewBoundsRect.contains(
- view.getLeft() + (int) event.getX(), view.getTop() + (int) event.getY())) {
- break;
- }
- // Fall-through
- case MotionEvent.ACTION_CANCEL:
- clearHandler();
- if (deltaSinceDownEvent() > mLongPress
- && (mCameraView.getMaxVideoDuration() <= 0
- || deltaSinceDownEvent() < mCameraView.getMaxVideoDuration())) {
- mHandler.sendEmptyMessage(RELEASE);
- }
- view.setPressed(false);
- break;
- case MotionEvent.ACTION_UP:
- clearHandler();
- if (deltaSinceDownEvent() < mLongPress) {
- mHandler.sendEmptyMessage(TAP);
- } else if ((mCameraView.getMaxVideoDuration() <= 0
- || deltaSinceDownEvent() < mCameraView.getMaxVideoDuration())) {
- mHandler.sendEmptyMessage(RELEASE);
- }
- view.setPressed(false);
- break;
- default:
- // No op
- }
- return true;
- }
-
- private long deltaSinceDownEvent() {
- return System.currentTimeMillis() - mDownEventTimestamp;
- }
-
- private void clearHandler() {
- mHandler.removeMessages(TAP);
- mHandler.removeMessages(HOLD);
- mHandler.removeMessages(RELEASE);
- }
-
- private File createNewFile(String extension) {
- File dirFile =
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
- if (dirFile != null && !dirFile.exists()) {
- dirFile.mkdirs();
- }
- // Use Locale.US to ensure we get ASCII digits
- return new File(dirFile,
- new SimpleDateFormat(FILENAME, Locale.US).format(System.currentTimeMillis())
- + extension);
- }
-
- @SuppressWarnings("WeakerAccess")
- void report(@NonNull String msg) {
- report(msg, null);
- }
-
- @SuppressWarnings("WeakerAccess")
- void report(@NonNull String msg, @Nullable Throwable cause) {
- Log.d(TAG, msg, cause);
- Toast.makeText(mCameraView.getContext(), msg, Toast.LENGTH_SHORT).show();
- }
-}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java
index 504277c..411ec1f 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java
@@ -54,7 +54,7 @@
private static final int REQUEST_CODE_PERMISSIONS = 10;
private boolean mCheckedPermissions = false;
- private Mode mMode = Mode.CAMERA_VIEW;
+ private Mode mMode = Mode.CAMERA_CONTROLLER;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -114,9 +114,6 @@
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
- case R.id.camera_view:
- mMode = Mode.CAMERA_VIEW;
- break;
case R.id.preview_view:
mMode = Mode.PREVIEW_VIEW;
break;
@@ -143,9 +140,6 @@
private void startFragment() {
switch (mMode) {
- case CAMERA_VIEW:
- startFragment(R.string.camera_view, new CameraViewFragment());
- break;
case PREVIEW_VIEW:
startFragment(R.string.preview_view, new PreviewViewFragment());
break;
@@ -175,6 +169,6 @@
}
private enum Mode {
- CAMERA_VIEW, PREVIEW_VIEW, CAMERA_CONTROLLER, TRANSFORM
+ PREVIEW_VIEW, CAMERA_CONTROLLER, TRANSFORM
}
}
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/fullscreen_selector.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/fullscreen_selector.xml
deleted file mode 100644
index ff39715..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/fullscreen_selector.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 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.
--->
-<selector
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:constantSize="true">
- <item android:drawable="@drawable/ic_fullscreen_exit" android:state_checked="true" android:state_enabled="true" />
- <item android:drawable="@drawable/ic_fullscreen" android:state_checked="false" android:state_enabled="true" />
-</selector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_camera.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_camera.xml
deleted file mode 100644
index a35682e..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_camera.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0">
- <path
-
- android:fillColor="?android:attr/colorControlNormal"
- android:pathData="M9.4,10.5l4.77,-8.26C13.47,2.09 12.75,2 12,2c-2.4,0 -4.6,0.85 -6.32,2.25l3.66,6.35 0.06,-0.1zM21.54,9c-0.92,-2.92 -3.15,-5.26 -6,-6.34L11.88,9h9.66zM21.8,10h-7.49l0.29,0.5 4.76,8.25C21,16.97 22,14.61 22,12c0,-0.69 -0.07,-1.35 -0.2,-2zM8.54,12l-3.9,-6.75C3.01,7.03 2,9.39 2,12c0,0.69 0.07,1.35 0.2,2h7.49l-1.15,-2zM2.46,15c0.92,2.92 3.15,5.26 6,6.34L12.12,15L2.46,15zM13.73,15l-3.9,6.76c0.7,0.15 1.42,0.24 2.17,0.24 2.4,0 4.6,-0.85 6.32,-2.25l-3.66,-6.35 -0.93,1.6z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen.xml
deleted file mode 100644
index 467ce90..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0">
- <path
- android:fillColor="?android:attr/colorControlNormal"
- android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen_exit.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen_exit.xml
deleted file mode 100644
index 8ee9dfc..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0">
- <path
- android:fillColor="?android:attr/colorControlNormal"
- android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_photo_camera.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_photo_camera.xml
deleted file mode 100644
index 625e155..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_photo_camera.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:tint="?attr/colorControlNormal"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_videocam.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_videocam.xml
deleted file mode 100644
index f009891..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_videocam.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:tint="?attr/colorControlNormal"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M18,10.48V6c0,-1.1 -0.9,-2 -2,-2H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11L18,10.48zM16,9.69V18H4V6h12V9.69zM11.67,11l-2.5,3.72L7.5,12L5,16h10L11.67,11z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/layout-land/fragment_main.xml b/camera/integration-tests/viewtestapp/src/main/res/layout-land/fragment_main.xml
deleted file mode 100644
index 7938a58..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/layout-land/fragment_main.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 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.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/layout_camera"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
-
- <androidx.camera.view.CameraView
- android:id="@+id/camera"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:layout_weight="1" />
-
- <LinearLayout
- android:layout_width="125dp"
- android:layout_height="match_parent"
- android:layout_marginBottom="10dp"
- android:layout_marginTop="10dp"
- android:gravity="center"
- android:orientation="vertical">
-
- <CheckBox
- android:id="@+id/toggle_crop"
- android:layout_width="wrap_content"
- android:layout_height="50dp"
- android:button="@drawable/fullscreen_selector" />
-
- <CheckBox
- android:id="@+id/mode"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:button="@drawable/ic_photo_camera" />
-
- <Button
- android:id="@+id/capture"
- style="?android:buttonBarButtonStyle"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_marginBottom="10dp"
- android:layout_marginTop="10dp"
- android:layout_weight="1"
- android:text="@string/btn_capture" />
-
- <CheckBox
- android:id="@+id/toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- </LinearLayout>
-
- </LinearLayout>
-
-</FrameLayout>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/layout/fragment_main.xml b/camera/integration-tests/viewtestapp/src/main/res/layout/fragment_main.xml
deleted file mode 100644
index 72f8ecb..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/layout/fragment_main.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 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.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/layout_camera"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <androidx.camera.view.CameraView
- android:id="@+id/camera"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_gravity="center_horizontal"
- android:layout_weight="1" />
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="58dp"
- android:layout_marginLeft="10dp"
- android:layout_marginRight="10dp"
- android:gravity="center">
-
- <CheckBox
- android:id="@+id/toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <Button
- android:id="@+id/capture"
- style="?android:buttonBarButtonStyle"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_marginLeft="50dp"
- android:layout_marginRight="50dp"
- android:layout_weight="1"
- android:text="@string/btn_capture" />
-
- <CheckBox
- android:id="@+id/mode"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="15dp"
- android:button="@drawable/ic_photo_camera" />
-
- <CheckBox
- android:id="@+id/toggle_crop"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:button="@drawable/fullscreen_selector" />
-
- </LinearLayout>
-
- </LinearLayout>
-
-</FrameLayout>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml b/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml
index 8babd1e..4868f69 100644
--- a/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml
+++ b/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml
@@ -17,10 +17,6 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
- android:id="@+id/camera_view"
- android:title="@string/camera_view"
- app:showAsAction="never" />
- <item
android:id="@+id/preview_view"
android:title="@string/preview_view"
app:showAsAction="never" />
diff --git a/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml b/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml
index 0eb3bd9..e257662 100644
--- a/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml
+++ b/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml
@@ -41,7 +41,6 @@
<string name="btn_switch">Switch</string>
<string name="btn_confirm">Confirm</string>
<string name="btn_cancel">Cancel</string>
- <string name="camera_view">Camera View</string>
<string name="preview_view">Preview View</string>
<string name="camera_controller">Camera Controller</string>
<string name="transform">Transform</string>
diff --git a/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt b/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt
index 67580ac..459b61a 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt
@@ -272,6 +272,9 @@
method public static androidx.compose.animation.core.Easing getLinearOutSlowInEasing();
}
+ @kotlin.RequiresOptIn(message="This is an experimental animation API for Transition. It may change in the future.") public @interface ExperimentalTransitionApi {
+ }
+
public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
}
@@ -404,8 +407,10 @@
ctor public MutableTransitionState(S? initialState);
method public S! getCurrentState();
method public S! getTargetState();
+ method @androidx.compose.animation.core.ExperimentalTransitionApi public boolean isIdle();
method public void setTargetState(S! p);
property public final S! currentState;
+ property @androidx.compose.animation.core.ExperimentalTransitionApi public final boolean isIdle;
property public final S! targetState;
}
@@ -518,6 +523,7 @@
method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateRect(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Rect>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Rect> targetValueByState);
method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateSize(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Size>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Size> targetValueByState);
method @androidx.compose.runtime.Composable public static inline <S, T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateValue(androidx.compose.animation.core.Transition<S>, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<T>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> targetValueByState);
+ method @androidx.compose.animation.core.ExperimentalTransitionApi @androidx.compose.runtime.Composable public static inline <S, T> androidx.compose.animation.core.Transition<T> createChildTransition(androidx.compose.animation.core.Transition<S>, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> transformToChildState);
method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(T? targetState, optional String? label);
method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(androidx.compose.animation.core.MutableTransitionState<T> transitionState, optional String? label);
}
diff --git a/compose/animation/animation-core/api/public_plus_experimental_current.txt b/compose/animation/animation-core/api/public_plus_experimental_current.txt
index 67580ac..459b61a 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_current.txt
@@ -272,6 +272,9 @@
method public static androidx.compose.animation.core.Easing getLinearOutSlowInEasing();
}
+ @kotlin.RequiresOptIn(message="This is an experimental animation API for Transition. It may change in the future.") public @interface ExperimentalTransitionApi {
+ }
+
public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
}
@@ -404,8 +407,10 @@
ctor public MutableTransitionState(S? initialState);
method public S! getCurrentState();
method public S! getTargetState();
+ method @androidx.compose.animation.core.ExperimentalTransitionApi public boolean isIdle();
method public void setTargetState(S! p);
property public final S! currentState;
+ property @androidx.compose.animation.core.ExperimentalTransitionApi public final boolean isIdle;
property public final S! targetState;
}
@@ -518,6 +523,7 @@
method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateRect(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Rect>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Rect> targetValueByState);
method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateSize(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Size>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Size> targetValueByState);
method @androidx.compose.runtime.Composable public static inline <S, T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateValue(androidx.compose.animation.core.Transition<S>, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<T>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> targetValueByState);
+ method @androidx.compose.animation.core.ExperimentalTransitionApi @androidx.compose.runtime.Composable public static inline <S, T> androidx.compose.animation.core.Transition<T> createChildTransition(androidx.compose.animation.core.Transition<S>, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> transformToChildState);
method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(T? targetState, optional String? label);
method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(androidx.compose.animation.core.MutableTransitionState<T> transitionState, optional String? label);
}
diff --git a/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt b/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt
index 3062208..4d76908 100644
--- a/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt
+++ b/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt
@@ -484,13 +484,17 @@
}
public final class Transition<S> {
+ ctor @kotlin.PublishedApi internal Transition(androidx.compose.animation.core.MutableTransitionState<S> transitionState, optional String? label);
method @kotlin.PublishedApi internal boolean addAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+ method @kotlin.PublishedApi internal boolean addTransition(androidx.compose.animation.core.Transition<?> transition);
method public S! getCurrentState();
method public String? getLabel();
method public androidx.compose.animation.core.Transition.Segment<S> getSegment();
method public S! getTargetState();
method public boolean isRunning();
method @kotlin.PublishedApi internal void removeAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+ method @kotlin.PublishedApi internal boolean removeTransition(androidx.compose.animation.core.Transition<?> transition);
+ method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal void updateTarget(S? targetState);
property public final S! currentState;
property public final boolean isRunning;
property public final String? label;
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 3062208..4d76908 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -484,13 +484,17 @@
}
public final class Transition<S> {
+ ctor @kotlin.PublishedApi internal Transition(androidx.compose.animation.core.MutableTransitionState<S> transitionState, optional String? label);
method @kotlin.PublishedApi internal boolean addAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+ method @kotlin.PublishedApi internal boolean addTransition(androidx.compose.animation.core.Transition<?> transition);
method public S! getCurrentState();
method public String? getLabel();
method public androidx.compose.animation.core.Transition.Segment<S> getSegment();
method public S! getTargetState();
method public boolean isRunning();
method @kotlin.PublishedApi internal void removeAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+ method @kotlin.PublishedApi internal boolean removeTransition(androidx.compose.animation.core.Transition<?> transition);
+ method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal void updateTarget(S? targetState);
property public final S! currentState;
property public final boolean isRunning;
property public final String? label;
diff --git a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt
index 7d19409..7172064 100644
--- a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt
+++ b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt
@@ -18,11 +18,13 @@
import androidx.annotation.Sampled
import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.ExperimentalTransitionApi
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.createChildTransition
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.spring
@@ -34,9 +36,12 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.Icon
@@ -51,6 +56,8 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
@@ -324,3 +331,118 @@
}
}
}
+
+@Composable
+@Suppress("UNUSED_PARAMETER")
+@Sampled
+fun CreateChildTransitionSample() {
+ // enum class DialerState { DialerMinimized, NumberPad }
+ @OptIn(ExperimentalTransitionApi::class)
+ @Composable
+ fun DialerButton(visibilityTransition: Transition<Boolean>, modifier: Modifier) {
+ val scale by visibilityTransition.animateFloat { visible ->
+ if (visible) 1f else 2f
+ }
+ Box(modifier.scale(scale).background(Color.Black)) {
+ // Content goes here
+ }
+ }
+
+ @Composable
+ fun NumberPad(visibilityTransition: Transition<Boolean>) {
+ // Create animations using the provided Transition for visibility change here...
+ }
+
+ @OptIn(ExperimentalTransitionApi::class)
+ @Composable
+ fun childTransitionSample() {
+ var dialerState by remember { mutableStateOf(DialerState.NumberPad) }
+ Box(Modifier.fillMaxSize()) {
+ val parentTransition = updateTransition(dialerState)
+
+ // Animate to different corner radius based on target state
+ val cornerRadius by parentTransition.animateDp {
+ if (it == DialerState.NumberPad) 0.dp else 20.dp
+ }
+
+ Box(
+ Modifier.align(Alignment.BottomCenter).widthIn(50.dp).heightIn(50.dp)
+ .clip(RoundedCornerShape(cornerRadius))
+ ) {
+ NumberPad(
+ // Creates a child transition that derives its target state from the parent
+ // transition, and the mapping from parent state to child state.
+ // This will allow:
+ // 1) Parent transition to account for additional animations in the child
+ // Transitions before it considers itself finished. This is useful when you
+ // have a subsequent action after all animations triggered by a state change
+ // have finished.
+ // 2) Separation of concerns. This allows the child composable (i.e.
+ // NumberPad) to only care about its own visibility, rather than knowing about
+ // DialerState.
+ visibilityTransition = parentTransition.createChildTransition {
+ // This is the lambda that defines how the parent target state maps to
+ // child target state.
+ it == DialerState.NumberPad
+ }
+ // Note: If it's not important for the animations within the child composable to
+ // be observable, it's perfectly valid to not hoist the animations through
+ // a Transition object and instead use animate*AsState.
+ )
+ DialerButton(
+ visibilityTransition = parentTransition.createChildTransition {
+ it == DialerState.DialerMinimized
+ },
+ modifier = Modifier.matchParentSize()
+ )
+ }
+ }
+ }
+}
+
+enum class DialerState {
+ DialerMinimized,
+ NumberPad
+}
+
+@OptIn(ExperimentalTransitionApi::class)
+@Composable
+fun TransitionStateIsIdleSample() {
+ @Composable
+ fun SelectableItem(selectedState: MutableTransitionState<Boolean>) {
+ val transition = updateTransition(selectedState)
+ val cornerRadius by transition.animateDp { selected -> if (selected) 10.dp else 0.dp }
+ val backgroundColor by transition.animateColor { selected ->
+ if (selected) Color.Red else Color.White
+ }
+ Box(Modifier.background(backgroundColor, RoundedCornerShape(cornerRadius))) {
+ // Item content goes here
+ }
+ }
+
+ @OptIn(ExperimentalTransitionApi::class)
+ @Composable
+ fun ItemsSample(selectedId: Int) {
+ Column {
+ repeat(3) { id ->
+ Box {
+ // Initialize the selected state as false to produce a transition going from
+ // false to true if `selected` parameter is true when entering composition.
+ val selectedState = remember { MutableTransitionState(false) }
+ // Mutate target state as needed.
+ selectedState.targetState = id == selectedId
+ // Now we pass the `MutableTransitionState` to the `Selectable` item and
+ // observe state change.
+ SelectableItem(selectedState)
+ if (selectedState.isIdle && selectedState.targetState) {
+ // If isIdle == true, it means the transition has arrived at its target state
+ // and there is no pending animation.
+ // Now we can do something after the selection transition is
+ // finished:
+ Text("Nice choice")
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
index 157183c9..8135f03 100644
--- a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
+++ b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
@@ -20,16 +20,17 @@
import androidx.compose.animation.animateColor
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
-import androidx.compose.runtime.withFrameNanos
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.delay
import org.junit.Rule
@@ -48,6 +49,7 @@
To
}
+ @OptIn(InternalAnimationApi::class)
@Test
fun transitionTest() {
val target = mutableStateOf(AnimStates.From)
@@ -239,6 +241,7 @@
assertTrue(playTime > 200 * MillisToNanos)
}
+ @OptIn(InternalAnimationApi::class)
@Test
fun addNewAnimationInFlightTest() {
val target = mutableStateOf(AnimStates.From)
@@ -339,4 +342,108 @@
assertTrue(targetRecreated)
assertTrue(playTime >= 800 * MillisToNanos)
}
+
+ @OptIn(ExperimentalTransitionApi::class)
+ @Test
+ fun testMutableTransitionStateIsIdle() {
+ val mutableTransitionState = MutableTransitionState(false)
+ var transition: Transition<Boolean>? = null
+ rule.setContent {
+ transition = updateTransition(mutableTransitionState).apply {
+ animateFloat {
+ if (it) 1f else 0f
+ }
+ }
+ }
+ rule.mainClock.autoAdvance = false
+ rule.runOnIdle {
+ assertTrue(mutableTransitionState.isIdle)
+ mutableTransitionState.targetState = true
+ assertFalse(mutableTransitionState.isIdle)
+ }
+
+ while (transition?.currentState != true) {
+ // Animation has not finished or even started from Transition's perspective
+ assertFalse(mutableTransitionState.isIdle)
+ rule.mainClock.advanceTimeByFrame()
+ }
+ assertTrue(mutableTransitionState.isIdle)
+
+ // Now that transition false -> true finished, go back to false
+ rule.runOnIdle {
+ assertTrue(mutableTransitionState.isIdle)
+ mutableTransitionState.targetState = false
+ assertFalse(mutableTransitionState.isIdle)
+ }
+
+ while (transition?.currentState == true) {
+ // Animation has not finished or even started from Transition's perspective
+ assertFalse(mutableTransitionState.isIdle)
+ rule.mainClock.advanceTimeByFrame()
+ }
+ assertTrue(mutableTransitionState.isIdle)
+ }
+
+ @OptIn(ExperimentalTransitionApi::class, InternalAnimationApi::class)
+ @Test
+ fun testCreateChildTransition() {
+ val intState = mutableStateOf(1)
+ val parentTransitionFloat = mutableStateOf(1f)
+ val childTransitionFloat = mutableStateOf(1f)
+ rule.setContent {
+ val transition = updateTransition(intState.value)
+ parentTransitionFloat.value = transition.animateFloat({ tween(100) }) {
+ when (it) {
+ 0 -> 0f
+ 1 -> 1f
+ else -> 2f
+ }
+ }.value
+ val booleanTransition = transition.createChildTransition {
+ it == 1
+ }
+ childTransitionFloat.value = booleanTransition.animateFloat({ tween(500) }) {
+ if (it) 1f else 0f
+ }.value
+ LaunchedEffect(intState.value) {
+ while (true) {
+ if (transition.targetState == transition.currentState) {
+ break
+ }
+ withFrameNanos { it }
+ if (intState.value == 0) {
+ // 1 -> 0
+ if (
+ transition.playTimeNanos > 0 && transition.playTimeNanos <= 500_000_000L
+ ) {
+ assertTrue(transition.isRunning)
+ assertTrue(booleanTransition.isRunning)
+ }
+ } else if (intState.value == 2) {
+ // 0 -> 2
+ assertFalse(booleanTransition.isRunning)
+ if (transition.playTimeNanos > 120_000_000L) {
+ assertFalse(transition.isRunning)
+ } else if (transition.playTimeNanos > 0) {
+ assertTrue(transition.isRunning)
+ }
+ }
+ }
+ }
+ }
+ rule.runOnIdle {
+ assertEquals(1f, parentTransitionFloat.value)
+ assertEquals(1f, childTransitionFloat.value)
+ intState.value = 0
+ }
+ rule.runOnIdle {
+ assertEquals(0f, parentTransitionFloat.value)
+ assertEquals(0f, childTransitionFloat.value)
+ intState.value = 2
+ }
+ rule.runOnIdle {
+ assertEquals(2f, parentTransitionFloat.value)
+ assertEquals(0f, childTransitionFloat.value)
+ }
+ }
}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ExperimentalTransitionApi.kt
similarity index 68%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ExperimentalTransitionApi.kt
index 2aa2c9c..9972083 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ExperimentalTransitionApi.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2021 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.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.compose.animation.core
+@RequiresOptIn(
+ message = "This is an experimental animation API for Transition. It may change in the future."
+)
+annotation class ExperimentalTransitionApi
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
index 1251a0c..d0cce40 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
@@ -22,7 +22,6 @@
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.runtime.getValue
@@ -66,7 +65,14 @@
label: String? = null
): Transition<T> {
val transition = remember { Transition(targetState, label = label) }
- transition.updateTarget(targetState)
+ transition.animateTo(targetState)
+ DisposableEffect(transition) {
+ onDispose {
+ // Clean up on the way out, to ensure the observers are not stuck in an in-between
+ // state.
+ transition.onTransitionEnd()
+ }
+ }
return transition
}
@@ -101,6 +107,19 @@
* animate from the current values to the new target.
*/
var targetState: S by mutableStateOf(initialState)
+
+ /**
+ * [isIdle] returns whether the transition has finished running. This will return false once
+ * the [targetState] has been set to a different value than [currentState].
+ *
+ * @sample androidx.compose.animation.core.samples.TransitionStateIsIdleSample
+ */
+ @get:ExperimentalTransitionApi
+ val isIdle: Boolean
+ get() = (currentState == targetState) && !isRunning
+
+ // Updated from Transition
+ internal var isRunning: Boolean by mutableStateOf(false)
}
/**
@@ -130,7 +149,14 @@
val transition = remember(transitionState) {
Transition(transitionState = transitionState, label)
}
- transition.updateTarget(transitionState.targetState)
+ transition.animateTo(transitionState.targetState)
+ DisposableEffect(transition) {
+ onDispose {
+ // Clean up on the way out, to ensure the observers are not stuck in an in-between
+ // state.
+ transition.onTransitionEnd()
+ }
+ }
return transition
}
@@ -153,7 +179,7 @@
* @see androidx.compose.animation.animateColor
*/
// TODO: Support creating Transition outside of composition and support imperative use of Transition
-class Transition<S> internal constructor(
+class Transition<S> @PublishedApi internal constructor(
private val transitionState: MutableTransitionState<S>,
val label: String? = null
) {
@@ -196,15 +222,20 @@
/**
* Play time in nano-seconds. [playTimeNanos] is always non-negative. It starts from 0L at the
* beginning of the transition and increment until all child animations have finished.
+ * @suppress
*/
- internal var playTimeNanos by mutableStateOf(0L)
+ @InternalAnimationApi
+ var playTimeNanos by mutableStateOf(0L)
+ private var startTimeNanos by mutableStateOf(AnimationConstants.UnspecifiedTime)
// This gets calculated every time child is updated/added
internal var updateChildrenNeeded: Boolean by mutableStateOf(true)
- private var startTimeNanos = AnimationConstants.UnspecifiedTime
private val _animations = mutableVectorOf<TransitionAnimationState<*, *>>()
+ // TODO: Support this in animation tooling
+ private val _transitions = mutableVectorOf<Transition<*>>()
+
/** @suppress **/
@InternalAnimationApi
val animations: List<TransitionAnimationState<*, *>> = _animations.asMutableList()
@@ -219,12 +250,9 @@
var totalDurationNanos: Long by mutableStateOf(0L)
private set
- // Target state that is currently being animated to
- private var currentTargetState: S = currentState
-
internal fun onFrame(frameTimeNanos: Long) {
if (startTimeNanos == AnimationConstants.UnspecifiedTime) {
- startTimeNanos = frameTimeNanos
+ onTransitionStart(frameTimeNanos)
}
updateChildrenNeeded = false
@@ -241,11 +269,31 @@
allFinished = false
}
}
- if (allFinished) {
- startTimeNanos = AnimationConstants.UnspecifiedTime
- currentState = targetState
- playTimeNanos = 0
+ _transitions.forEach {
+ if (it.targetState != it.currentState) {
+ it.onFrame(playTimeNanos)
+ }
+ if (it.targetState != it.currentState) {
+ allFinished = false
+ }
}
+ if (allFinished) {
+ onTransitionEnd()
+ }
+ }
+
+ // onTransitionStart and onTransitionEnd are symmetric. Both are called from onFrame
+ internal fun onTransitionStart(frameTimeNanos: Long) {
+ startTimeNanos = frameTimeNanos
+ transitionState.isRunning = true
+ }
+
+ // onTransitionStart and onTransitionEnd are symmetric. Both are called from onFrame
+ internal fun onTransitionEnd() {
+ startTimeNanos = AnimationConstants.UnspecifiedTime
+ currentState = targetState
+ playTimeNanos = 0
+ transitionState.isRunning = false
}
/**
@@ -256,6 +304,7 @@
fun seek(initialState: S, targetState: S, playTimeNanos: Long) {
// Reset running state
startTimeNanos = AnimationConstants.UnspecifiedTime
+ transitionState.isRunning = false
if (!isSeeking || this.currentState != initialState || this.targetState != targetState) {
// Reset all child animations
this.currentState = initialState
@@ -274,6 +323,12 @@
}
@PublishedApi
+ internal fun addTransition(transition: Transition<*>) = _transitions.add(transition)
+
+ @PublishedApi
+ internal fun removeTransition(transition: Transition<*>) = _transitions.remove(transition)
+
+ @PublishedApi
internal fun addAnimation(
@Suppress("HiddenTypeParameter")
animation: TransitionAnimationState<*, *>
@@ -290,6 +345,7 @@
// This target state should only be used to modify "mutableState"s, as it could potentially
// roll back. The
@Suppress("ComposableNaming")
+ @PublishedApi
@Composable
internal fun updateTarget(targetState: S) {
if (!isSeeking) {
@@ -300,11 +356,25 @@
segment = Segment(this.targetState, targetState)
currentState = this.targetState
this.targetState = targetState
- }
- SideEffect {
- animateTo(targetState)
- }
+ if (!isRunning) {
+ updateChildrenNeeded = true
+ }
+ // If target state is changed, reset all the animations to be re-created in the
+ // next frame w/ their new target value. Child animations target values are updated in
+ // the side effect that may not have happened when this function in invoked.
+ _animations.forEach { it.resetAnimation() }
+ }
+ }
+ }
+
+ // This should only be called if PlayTime comes from clock directly, instead of from a parent
+ // Transition.
+ @Suppress("ComposableNaming")
+ @Composable
+ internal fun animateTo(targetState: S) {
+ if (!isSeeking) {
+ updateTarget(targetState)
// target != currentState adds LaunchedEffect into the tree in the same frame as
// target change.
if (targetState != currentState || isRunning || updateChildrenNeeded) {
@@ -319,22 +389,6 @@
}
}
- internal fun animateTo(targetState: S) {
- if (targetState != currentTargetState) {
- if (isRunning) {
- startTimeNanos += playTimeNanos
- playTimeNanos = 0
- } else {
- updateChildrenNeeded = true
- }
- currentTargetState = targetState
- // If target state is changed, reset all the animations to be re-created in the
- // next frame w/ their new target value. Child animations target values are updated in
- // the side effect that may not have happened when this function in invoked.
- _animations.forEach { it.resetAnimation() }
- }
- }
-
private fun onChildAnimationUpdated() {
updateChildrenNeeded = true
if (isSeeking) {
@@ -363,14 +417,9 @@
) : State<T> {
// Changed during composition, may rollback
- @Suppress("ShowingMemberInHiddenClass")
- @PublishedApi
- internal var targetValue: T by mutableStateOf(initialValue)
- internal set
+ private var targetValue: T by mutableStateOf(initialValue)
+ private var animationSpec: FiniteAnimationSpec<T> by mutableStateOf(spring())
- @Suppress("ShowingMemberInHiddenClass")
- @PublishedApi
- internal var animationSpec: FiniteAnimationSpec<T> by mutableStateOf(spring())
private var animation: TargetBasedAnimation<T, V> by mutableStateOf(
TargetBasedAnimation(
animationSpec, typeConverter, initialValue, targetValue,
@@ -379,11 +428,12 @@
)
internal var isFinished: Boolean by mutableStateOf(true)
private var offsetTimeNanos by mutableStateOf(0L)
+ private var needsReset by mutableStateOf(false)
// Changed during animation, no concerns of rolling back
override var value by mutableStateOf(initialValue)
internal set
- internal var velocityVector: V = initialVelocityVector
+ private var velocityVector: V = initialVelocityVector
internal val durationNanos
get() = animation.durationNanos
@@ -404,9 +454,28 @@
velocityVector = animation.getVelocityVectorFromNanos(playTimeNanos)
}
- private fun updateAnimation(initialValue: T = value) {
+ private val interruptionSpec: FiniteAnimationSpec<T>
+
+ init {
+ val visibilityThreshold: T? = visibilityThresholdMap.get(typeConverter)?.let {
+ val vector = typeConverter.convertToVector(initialValue)
+ for (id in 0 until vector.size) {
+ vector[id] = it
+ }
+ typeConverter.convertFromVector(vector)
+ }
+ interruptionSpec = spring(visibilityThreshold = visibilityThreshold)
+ }
+
+ private fun updateAnimation(initialValue: T = value, isInterrupted: Boolean = false) {
+ val spec = if (isInterrupted) {
+ // When interrupted, use the default spring, unless the spec is also a spring.
+ if (animationSpec is SpringSpec<*>) animationSpec else interruptionSpec
+ } else {
+ animationSpec
+ }
animation = TargetBasedAnimation(
- animationSpec,
+ spec,
typeConverter,
initialValue,
targetValue,
@@ -416,29 +485,34 @@
}
internal fun resetAnimation() {
- offsetTimeNanos = 0
- isFinished = false
- updateAnimation()
+ needsReset = true
}
@PublishedApi
@Suppress("ShowingMemberInHiddenClass")
// This gets called *during* composition
- internal fun updateTargetValue(targetValue: T) {
- if (this.targetValue != targetValue) {
+ internal fun updateTargetValue(targetValue: T, animationSpec: FiniteAnimationSpec<T>) {
+ if (this.targetValue != targetValue || needsReset) {
this.targetValue = targetValue
+ this.animationSpec = animationSpec
+ updateAnimation(isInterrupted = !isFinished)
isFinished = false
- updateAnimation()
// This is needed because the target change could happen during a transition
offsetTimeNanos = playTimeNanos
+ needsReset = false
}
}
@PublishedApi
@Suppress("ControlFlowWithEmptyBody", "ShowingMemberInHiddenClass")
// This gets called *during* composition
- internal fun updateInitialAndTargetValue(initialValue: T, targetValue: T) {
+ internal fun updateInitialAndTargetValue(
+ initialValue: T,
+ targetValue: T,
+ animationSpec: FiniteAnimationSpec<T>
+ ) {
this.targetValue = targetValue
+ this.animationSpec = animationSpec
if (animation.initialValue == initialValue && animation.targetValue == targetValue) {
// TODO(b/178811102): we should be able to return early here.
}
@@ -460,6 +534,160 @@
return this == initialState && targetState == [email protected]
}
}
+
+ /**
+ * [DeferredAnimation] can be constructed using [Transition.createDeferredAnimation] during
+ * composition and initialized later. It is useful for animations, the target values for
+ * which are unknown at composition time (e.g. layout size/position, etc).
+ *
+ * Once a [DeferredAnimation] is created, it can be configured and updated as needed using
+ * [DeferredAnimation.animate] method.
+ *
+ * @suppress
+ */
+ @InternalAnimationApi
+ inner class DeferredAnimation<T, V : AnimationVector> internal constructor(
+ val typeConverter: TwoWayConverter<T, V>,
+ val label: String
+ ) {
+ internal var data: DeferredAnimationData<T, V>? = null
+
+ internal inner class DeferredAnimationData<T, V : AnimationVector>(
+ val animation: Transition<S>.TransitionAnimationState<T, V>,
+ var transitionSpec: Segment<S>.() -> FiniteAnimationSpec<T>,
+ var targetValueByState: (state: S) -> T,
+ ) : State<T> {
+ override val value: T
+ get() {
+ animation.updateTargetValue(
+ targetValueByState(targetState),
+ segment.transitionSpec()
+ )
+ return animation.value
+ }
+ }
+
+ /**
+ * [DeferredAnimation] allows the animation setup to be deferred until a later time after
+ * composition. [animate] can be used to set up a [DeferredAnimation]. Like other
+ * Transition animations such as [Transition.animateFloat], [DeferredAnimation] also
+ * expects [transitionSpec] and [targetValueByState] for the mapping from target state
+ * to animation spec and target value, respectively.
+ */
+ fun animate(
+ transitionSpec: Segment<S>.() -> FiniteAnimationSpec<T>,
+ targetValueByState: (state: S) -> T
+ ): State<T> {
+ val animData: DeferredAnimationData<T, V> = data ?: DeferredAnimationData(
+ TransitionAnimationState(
+ targetValueByState(currentState),
+ typeConverter.createZeroVectorFrom(targetValueByState(currentState)),
+ typeConverter,
+ label
+ ),
+ transitionSpec,
+ targetValueByState
+ ).apply {
+ data = this
+ addAnimation(this.animation)
+ }
+ return animData.apply {
+ // Update animtion data with the latest mapping
+ this.targetValueByState = targetValueByState
+ this.transitionSpec = transitionSpec
+
+ animation.updateTargetValue(
+ targetValueByState(targetState),
+ segment.transitionSpec()
+ )
+ }
+ }
+
+ internal fun setupSeeking() {
+ data?.apply {
+ animation.updateInitialAndTargetValue(
+ targetValueByState(segment.initialState),
+ targetValueByState(segment.targetState),
+ segment.transitionSpec()
+ )
+ }
+ }
+ }
+
+ internal fun removeAnimation(deferredAnimation: DeferredAnimation<*, *>) {
+ deferredAnimation.data?.animation?.let {
+ removeAnimation(it)
+ }
+ }
+}
+
+/**
+ * This creates a [DeferredAnimation], which will not animate until it is set up using
+ * [DeferredAnimation.animate]. Once the animation is set up, it will animate from the
+ * [currentState][Transition.currentState] to [targetState][Transition.targetState]. If the
+ * [Transition] has already arrived at its target state at the time when the animation added, there
+ * will be no animation.
+ *
+ * @param typeConverter A converter to convert any value of type [T] from/to an [AnimationVector]
+ * @param label A label for differentiating this animation from others in android studio.
+ *
+ * @suppress
+ */
+@InternalAnimationApi
+@Composable
+fun <S, T, V : AnimationVector> Transition<S>.createDeferredAnimation(
+ typeConverter: TwoWayConverter<T, V>,
+ label: String = "DeferredAnimation"
+): Transition<S>.DeferredAnimation<T, V> {
+ val lazyAnim = remember(this) { DeferredAnimation(typeConverter, label) }
+ DisposableEffect(lazyAnim) {
+ onDispose {
+ removeAnimation(lazyAnim)
+ }
+ }
+ if (isSeeking) {
+ lazyAnim.setupSeeking()
+ }
+ return lazyAnim
+}
+
+/**
+ * [createChildTransition] creates a child Transition based on the mapping between parent state to
+ * child state provided in [transformToChildState]. This serves the following purposes:
+ * 1) Hoist the child transition state into parent transition. Therefore the parent Transition
+ * will be aware of whether there's any on-going animation due to the same target state change.
+ * This will further allow sequential animation to be set up when all animations have finished.
+ * 2) Separation of concerns. The child transition can respresent a much more simplified state
+ * transition when, for example, mapping from an enum parent state to a Boolean visible state for
+ * passing further down the compose tree. The child composables hence can be designed around
+ * handling a more simple and a more relevant state change.
+ *
+ * [label] is used to differentiate from other animations in the same transition in Android Studio.
+ *
+ * @sample androidx.compose.animation.core.samples.CreateChildTransitionSample
+ */
+@ExperimentalTransitionApi
+@Composable
+inline fun <S, T> Transition<S>.createChildTransition(
+ label: String = "ChildTransition",
+ transformToChildState: @Composable (parentState: S) -> T,
+): Transition<T> {
+ val initialParentState = remember(this) { this.currentState }
+ val initialState = transformToChildState(initialParentState)
+ val transition = remember(this) {
+ Transition<T>(MutableTransitionState(initialState), label)
+ }
+
+ DisposableEffect(transition) {
+ addTransition(transition)
+ onDispose {
+ removeTransition(transition)
+ }
+ }
+
+ val targetState = transformToChildState(this.targetState)
+ transition.updateTarget(targetState)
+ return transition
}
/**
@@ -496,36 +724,30 @@
targetValueByState: @Composable (state: S) -> T
): State<T> {
+ val initialValue = targetValueByState(currentState)
val targetValue = targetValueByState(targetState)
- // TODO: need a better way to store initial value.
- val initNeeded = remember(this) { mutableStateOf(true) }
- val initValue = if (initNeeded.value) targetValueByState(currentState) else targetValue
val transitionAnimation = remember(this) {
// Initialize the animation state to initialState value, so if it's added during a
// transition run, it'll participate in the animation.
// This is preferred because it's easy to opt out - Simply adding new animation once
// currentState == targetState would opt out.
TransitionAnimationState(
- initValue,
+ initialValue,
typeConverter.createZeroVectorFrom(targetValue),
typeConverter,
label
)
}
- transitionAnimation.animationSpec = transitionSpec(segment)
-
- if (initNeeded.value) {
- SideEffect {
- initNeeded.value = false
- }
- }
-
+ val animationSpec = transitionSpec(segment)
if (isSeeking) {
// In the case of seeking, we also need to update initial value as needed
- val initialValue = targetValueByState(segment.initialState)
- transitionAnimation.updateInitialAndTargetValue(initialValue, targetValue)
+ transitionAnimation.updateInitialAndTargetValue(
+ initialValue,
+ targetValue,
+ animationSpec
+ )
} else {
- transitionAnimation.updateTargetValue(targetValue)
+ transitionAnimation.updateTargetValue(targetValue, animationSpec)
}
DisposableEffect(transitionAnimation) {
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
index 14f4ce0..5b27b547 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
@@ -99,4 +99,16 @@
val Rect.Companion.VisibilityThreshold: Rect
get() = rectVisibilityThreshold
-// TODO: Add Dp.DefaultAnimation = spring<Dp>(visibilityThreshold = Dp.VisibilityThreshold)
\ No newline at end of file
+// TODO: Add Dp.DefaultAnimation = spring<Dp>(visibilityThreshold = Dp.VisibilityThreshold)
+
+internal val visibilityThresholdMap: Map<TwoWayConverter<*, *>, Float> = mapOf(
+ Int.VectorConverter to 1f,
+ IntSize.VectorConverter to 1f,
+ IntOffset.VectorConverter to 1f,
+ Float.VectorConverter to 0.01f,
+ Rect.VectorConverter to PxVisibilityThreshold,
+ Size.VectorConverter to PxVisibilityThreshold,
+ Offset.VectorConverter to PxVisibilityThreshold,
+ Dp.VectorConverter to DpVisibilityThreshold,
+ DpOffset.VectorConverter to DpVisibilityThreshold
+)
\ No newline at end of file
diff --git a/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt b/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt
index 5c07774..0f00f36 100644
--- a/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt
@@ -6,9 +6,20 @@
}
public final class AnimatedVisibilityKt {
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <T> void AnimatedVisibility(androidx.compose.animation.core.Transition<T>, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @Deprecated @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ @androidx.compose.animation.ExperimentalAnimationApi public final class AnimatedVisibilityScope {
+ method public androidx.compose.ui.Modifier animateEnterExit(androidx.compose.ui.Modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit);
+ method public androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> getTransition();
+ property public final androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> transition;
}
public final class AnimationModifierKt {
@@ -23,6 +34,12 @@
method @androidx.compose.runtime.Composable public static <T> void Crossfade(T? targetState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> content);
}
+ @androidx.compose.animation.ExperimentalAnimationApi public enum EnterExitState {
+ enum_constant public static final androidx.compose.animation.EnterExitState PostExit;
+ enum_constant public static final androidx.compose.animation.EnterExitState PreEnter;
+ enum_constant public static final androidx.compose.animation.EnterExitState Visible;
+ }
+
public final class EnterExitTransitionKt {
method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandHorizontally(optional androidx.compose.ui.Alignment.Horizontal expandFrom, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialWidth, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandIn(optional androidx.compose.ui.Alignment expandFrom, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,androidx.compose.ui.unit.IntSize> initialSize, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
@@ -42,10 +59,22 @@
@androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class EnterTransition {
method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.EnterTransition plus(androidx.compose.animation.EnterTransition enter);
+ field public static final androidx.compose.animation.EnterTransition.Companion Companion;
+ }
+
+ public static final class EnterTransition.Companion {
+ method public androidx.compose.animation.EnterTransition getNone();
+ property public final androidx.compose.animation.EnterTransition None;
}
@androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class ExitTransition {
method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.ExitTransition plus(androidx.compose.animation.ExitTransition exit);
+ field public static final androidx.compose.animation.ExitTransition.Companion Companion;
+ }
+
+ public static final class ExitTransition.Companion {
+ method public androidx.compose.animation.ExitTransition getNone();
+ property public final androidx.compose.animation.ExitTransition None;
}
@kotlin.RequiresOptIn(message="This is an experimental animation API.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalAnimationApi {
diff --git a/compose/animation/animation/api/public_plus_experimental_current.txt b/compose/animation/animation/api/public_plus_experimental_current.txt
index 5c07774..0f00f36 100644
--- a/compose/animation/animation/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation/api/public_plus_experimental_current.txt
@@ -6,9 +6,20 @@
}
public final class AnimatedVisibilityKt {
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <T> void AnimatedVisibility(androidx.compose.animation.core.Transition<T>, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+ method @Deprecated @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
+ @androidx.compose.animation.ExperimentalAnimationApi public final class AnimatedVisibilityScope {
+ method public androidx.compose.ui.Modifier animateEnterExit(androidx.compose.ui.Modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit);
+ method public androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> getTransition();
+ property public final androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> transition;
}
public final class AnimationModifierKt {
@@ -23,6 +34,12 @@
method @androidx.compose.runtime.Composable public static <T> void Crossfade(T? targetState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> content);
}
+ @androidx.compose.animation.ExperimentalAnimationApi public enum EnterExitState {
+ enum_constant public static final androidx.compose.animation.EnterExitState PostExit;
+ enum_constant public static final androidx.compose.animation.EnterExitState PreEnter;
+ enum_constant public static final androidx.compose.animation.EnterExitState Visible;
+ }
+
public final class EnterExitTransitionKt {
method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandHorizontally(optional androidx.compose.ui.Alignment.Horizontal expandFrom, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialWidth, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandIn(optional androidx.compose.ui.Alignment expandFrom, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,androidx.compose.ui.unit.IntSize> initialSize, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
@@ -42,10 +59,22 @@
@androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class EnterTransition {
method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.EnterTransition plus(androidx.compose.animation.EnterTransition enter);
+ field public static final androidx.compose.animation.EnterTransition.Companion Companion;
+ }
+
+ public static final class EnterTransition.Companion {
+ method public androidx.compose.animation.EnterTransition getNone();
+ property public final androidx.compose.animation.EnterTransition None;
}
@androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class ExitTransition {
method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.ExitTransition plus(androidx.compose.animation.ExitTransition exit);
+ field public static final androidx.compose.animation.ExitTransition.Companion Companion;
+ }
+
+ public static final class ExitTransition.Companion {
+ method public androidx.compose.animation.ExitTransition getNone();
+ property public final androidx.compose.animation.ExitTransition None;
}
@kotlin.RequiresOptIn(message="This is an experimental animation API.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalAnimationApi {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt
index 24e6a0f..31d9ea7 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt
@@ -52,7 +52,7 @@
.fillMaxWidth()
.padding(50.dp)
) {
- Text()
+ MyText()
Spacer(Modifier.requiredHeight(20.dp))
Button()
Spacer(Modifier.requiredHeight(20.dp))
@@ -61,7 +61,7 @@
}
@Composable
-private fun Text() {
+private fun MyText() {
val shortText = "Click me"
val longText = "Very long text\nthat spans across\nmultiple lines"
var short by remember { mutableStateOf(true) }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
index d4f9186..f1326e7 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
@@ -34,12 +34,13 @@
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.selection.selectable
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
@@ -54,6 +55,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import kotlin.math.max
+import kotlin.math.min
@Composable
fun AnimatedVisibilityDemo() {
@@ -68,60 +71,67 @@
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimatedItems(animateContentSize: Boolean) {
- var counter by remember { mutableStateOf(0) }
- Box(
- Modifier.padding(bottom = 20.dp)
- ) {
-
- val modifier = if (animateContentSize) Modifier.animateContentSize() else Modifier
- Column(
- Modifier.background(Color.LightGray).fillMaxWidth().then(modifier)
+ var itemNum by remember { mutableStateOf(0) }
+ Column {
+ Row(
+ Modifier.fillMaxWidth().padding(20.dp),
+ horizontalArrangement = Arrangement.SpaceBetween
) {
- val itemNum = if (counter >= 7) 12 - counter else counter
-
- AnimatedVisibility(visible = itemNum > 0) {
- Item(
- pastelColors[0],
- "Expand Vertically + Fade In\nShrink " +
- "Vertically + Fade Out\n(Column Default)"
- )
+ Button(onClick = { itemNum = min((itemNum + 1), 6) }) {
+ Text("Add")
}
- HorizontalTransition(visible = itemNum > 1) {
- Item(pastelColors[1], "Expand Horizontally\nShrink Horizontally")
- }
- SlideTransition(visible = itemNum > 2) {
- Item(
- pastelColors[2],
- "Slide In Horizontally + Fade In\nSlide Out Horizontally + " +
- "Fade Out"
- )
- }
- AnimatedVisibility(
- visible = itemNum > 3,
- enter = expandVertically(),
- exit = shrinkVertically()
- ) {
- Item(pastelColors[3], "Expand Vertically\nShrink Vertically")
- }
- FadeTransition(visible = itemNum > 4) {
- Item(pastelColors[4], "Fade In\nFade Out")
- }
- FullyLoadedTransition(visible = itemNum > 5) {
- Item(
- pastelColors[0],
- "Expand Vertically + Fade In + Slide In Vertically\n" +
- "Shrink Vertically + Fade Out + Slide Out Vertically"
- )
+ Button(onClick = { itemNum = max((itemNum - 1), 0) }) {
+ Text("Remove")
}
}
-
- Button(
- modifier = Modifier.align(Alignment.TopEnd).padding(10.dp),
- onClick = {
- counter = (counter + 1) % 12
- }
+ Box(
+ Modifier.padding(bottom = 20.dp)
) {
- Text("Click Me")
+
+ val modifier = if (animateContentSize) Modifier.animateContentSize() else Modifier
+ Column(
+ Modifier.background(Color.LightGray).fillMaxWidth().then(modifier)
+ ) {
+
+ Column(
+ Modifier.background(Color.LightGray).fillMaxWidth().then(modifier)
+ ) {
+ AnimatedVisibility(visible = itemNum > 0) {
+ Item(
+ pastelColors[0],
+ "Expand Vertically + Fade In\nShrink " +
+ "Vertically + Fade Out\n(Column Default)"
+ )
+ }
+ HorizontalTransition(visible = itemNum > 1) {
+ Item(pastelColors[1], "Expand Horizontally\nShrink Horizontally")
+ }
+ SlideTransition(visible = itemNum > 2) {
+ Item(
+ pastelColors[2],
+ "Slide In Horizontally + Fade In\nSlide Out Horizontally + " +
+ "Fade Out"
+ )
+ }
+ AnimatedVisibility(
+ visible = itemNum > 3,
+ enter = expandVertically(),
+ exit = shrinkVertically()
+ ) {
+ Item(pastelColors[3], "Expand Vertically\nShrink Vertically")
+ }
+ FadeTransition(visible = itemNum > 4) {
+ Item(pastelColors[4], "Fade In\nFade Out")
+ }
+ FullyLoadedTransition(visible = itemNum > 5) {
+ Item(
+ pastelColors[0],
+ "Expand Vertically + Fade In + Slide In Vertically\n" +
+ "Shrink Vertically + Fade Out + Slide Out Vertically"
+ )
+ }
+ }
+ }
}
}
}
@@ -164,9 +174,10 @@
targetWidth = { fullWidth -> fullWidth / 10 },
// Overwrites the default animation with tween for this shrink animation.
animationSpec = tween(durationMillis = 400)
- ) + fadeOut(),
- content = content
- )
+ ) + fadeOut()
+ ) {
+ content()
+ }
}
@OptIn(ExperimentalAnimationApi::class)
@@ -187,9 +198,10 @@
// Overwrites the ending position of the slide-out to 200 (pixels) to the right
targetOffsetX = { 200 },
animationSpec = spring(stiffness = Spring.StiffnessHigh)
- ) + fadeOut(),
- content = content
- )
+ ) + fadeOut()
+ ) {
+ content()
+ }
}
@OptIn(ExperimentalAnimationApi::class)
@@ -204,9 +216,10 @@
exit = fadeOut(
// Overwrites the default animation with tween
animationSpec = tween(durationMillis = 250)
- ),
- content = content
- )
+ )
+ ) {
+ content()
+ }
}
@OptIn(ExperimentalAnimationApi::class)
@@ -221,7 +234,8 @@
) + expandVertically(
expandFrom = Alignment.Top
) + fadeIn(initialAlpha = 0.3f),
- exit = slideOutVertically() + shrinkVertically() + fadeOut(),
- content = content
- )
+ exit = slideOutVertically() + shrinkVertically() + fadeOut()
+ ) {
+ content()
+ }
}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
index 96ed035..ef08514 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
@@ -18,65 +18,77 @@
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.ExperimentalTransitionApi
+import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment.Companion.CenterEnd
import androidx.compose.ui.Alignment.Companion.End
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.flow.collect
-@OptIn(ExperimentalAnimationApi::class)
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
@Composable
fun AnimatedVisibilityLazyColumnDemo() {
- var itemNum by remember { mutableStateOf(0) }
Column {
+ val model = remember { MyModel() }
Row(Modifier.fillMaxWidth()) {
Button(
- { itemNum = itemNum + 1 },
- enabled = itemNum <= turquoiseColors.size - 1,
+ { model.addNewItem() },
modifier = Modifier.padding(15.dp).weight(1f)
) {
Text("Add")
}
+ }
- Button(
- { itemNum = itemNum - 1 },
- enabled = itemNum >= 1,
- modifier = Modifier.padding(15.dp).weight(1f)
- ) {
- Text("Remove")
+ LaunchedEffect(model) {
+ snapshotFlow {
+ model.items.firstOrNull { it.visible.isIdle && !it.visible.targetState }
+ }.collect {
+ if (it != null) {
+ model.pruneItems()
+ }
}
}
LazyColumn {
- itemsIndexed(turquoiseColors) { i, color ->
+ items(model.items, key = { it.itemId }) { item ->
AnimatedVisibility(
- (turquoiseColors.size - itemNum) <= i,
+ item.visible,
enter = expandVertically(),
exit = shrinkVertically()
) {
- Spacer(Modifier.fillMaxWidth().requiredHeight(90.dp).background(color))
+ Box(Modifier.fillMaxWidth().requiredHeight(90.dp).background(item.color)) {
+ Button(
+ { model.removeItem(item) },
+ modifier = Modifier.align(CenterEnd).padding(15.dp)
+ ) {
+ Text("Remove")
+ }
+ }
}
}
}
Button(
- { itemNum = 0 },
+ { model.removeAll() },
modifier = Modifier.align(End).padding(15.dp)
) {
Text("Clear All")
@@ -84,6 +96,44 @@
}
}
+private class MyModel {
+ private val _items: MutableList<ColoredItem> = mutableStateListOf()
+ private var lastItemId = 0
+ val items: List<ColoredItem> = _items
+
+ class ColoredItem(val visible: MutableTransitionState<Boolean>, val itemId: Int) {
+ val color: Color
+ get() = turquoiseColors.let {
+ it[itemId % it.size]
+ }
+ }
+
+ fun addNewItem() {
+ lastItemId++
+ _items.add(
+ ColoredItem(
+ MutableTransitionState(false).apply { targetState = true },
+ lastItemId
+ )
+ )
+ }
+
+ fun removeItem(item: ColoredItem) {
+ item.visible.targetState = false
+ }
+
+ @OptIn(ExperimentalTransitionApi::class)
+ fun pruneItems() {
+ _items.removeAll(items.filter { it.visible.isIdle && !it.visible.targetState })
+ }
+
+ fun removeAll() {
+ _items.forEach {
+ it.visible.targetState = false
+ }
+ }
+}
+
private val turquoiseColors = listOf(
Color(0xff07688C),
Color(0xff1986AF),
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
index a8aff2e..1160261 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
@@ -44,7 +44,10 @@
AnimatedVisibilityContentSizeChangeDemo()
},
ComposableDemo("Cross Fade") { CrossfadeDemo() },
- ComposableDemo("Enter/Exit Transition Demo") { EnterExitTransitionDemo() },
+ ComposableDemo("Enter/ExitTransition Combo Demo") { EnterExitCombinationDemo() },
+ ComposableDemo("Sequential Enter/Exit Demo") {
+ SequentialEnterExitDemo()
+ },
)
),
DemoCategory(
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitCombinationDemo.kt
similarity index 99%
rename from compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
rename to compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitCombinationDemo.kt
index cccb234..f25a6fb 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitCombinationDemo.kt
@@ -79,7 +79,7 @@
import androidx.compose.ui.unit.dp
@Composable
-fun EnterExitTransitionDemo() {
+fun EnterExitCombinationDemo() {
Column(Modifier.fillMaxWidth().padding(top = 20.dp)) {
val oppositeAlignment = remember { mutableStateOf(true) }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SequentialEnterExitDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SequentialEnterExitDemo.kt
new file mode 100644
index 0000000..e769f8b
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SequentialEnterExitDemo.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.animation.demos
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.EnterExitState
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.ExperimentalTransitionApi
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.FloatingActionButton
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+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.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.text.font.FontWeight.Companion.Bold
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+/*
+ * This demo shows how to create a Transition<EnterExitState> with MutableTransitionState, and
+ * use the Transition<EnterExitState> to animate a few enter/exit transitions together. The
+ * MutableTransitionState is then used to add sequential enter/exit transitions.
+ *
+ * APIs used:
+ * - updateTransition
+ * - MutableTransitionState
+ * - EnterExitState
+ */
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+@Composable
+fun SequentialEnterExitDemo() {
+ Box {
+ var mainContentVisible by remember {
+ mutableStateOf(MutableTransitionState(true))
+ }
+ Column(Modifier.fillMaxSize()) {
+ Spacer(Modifier.size(40.dp))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceAround
+ ) {
+ // New MutableTransitionState instance here. This should reset the animation.
+ Button(onClick = { mainContentVisible = MutableTransitionState(false) }) {
+ Text("Reset")
+ }
+
+ Button(
+ onClick = { mainContentVisible.targetState = !mainContentVisible.targetState },
+ ) {
+ Text("Toggle Visibility")
+ }
+ }
+ Spacer(Modifier.size(40.dp))
+ AnimatedVisibility(
+ visibleState = mainContentVisible,
+ modifier = Modifier.fillMaxSize(),
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ Box {
+ Column(Modifier.fillMaxSize()) {
+ Item(Modifier.weight(1f), backgroundColor = Color(0xffff6f69))
+ Item(Modifier.weight(1f), backgroundColor = Color(0xffffcc5c))
+ }
+ FloatingActionButton(
+ onClick = {},
+ modifier = Modifier.align(Alignment.BottomEnd).padding(20.dp),
+ backgroundColor = MaterialTheme.colors.primary
+ ) {
+ Icon(Icons.Default.Favorite, contentDescription = null)
+ }
+ }
+ }
+ }
+ AnimatedVisibility(
+ visible = mainContentVisible.targetState && mainContentVisible.isIdle,
+ modifier = Modifier.align(Alignment.Center),
+ enter = expandVertically(),
+ exit = fadeOut(animationSpec = tween(50))
+ ) {
+ Text("Transition Finished", color = Color.White, fontSize = 40.sp, fontWeight = Bold)
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+private fun AnimatedVisibilityScope.Item(
+ modifier: Modifier,
+ backgroundColor: Color
+) {
+ val scale by transition.animateFloat { enterExitState ->
+ when (enterExitState) {
+ EnterExitState.PreEnter -> 0.9f
+ EnterExitState.Visible -> 1.0f
+ EnterExitState.PostExit -> 0.5f
+ }
+ }
+ Box(
+ modifier.fillMaxWidth().padding(5.dp).animateEnterExit(
+ enter = slideInVertically(initialOffsetY = { it }),
+ exit = ExitTransition.None
+ ).graphicsLayer {
+ scaleX = scale
+ scaleY = scale
+ }.clip(RoundedCornerShape(20.dp)).background(backgroundColor).fillMaxSize()
+ ) {}
+}
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
index e065cf5..2999fd4 100644
--- a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
@@ -18,12 +18,21 @@
import androidx.annotation.Sampled
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.EnterExitState
+import androidx.compose.animation.ExitTransition
import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.ExperimentalTransitionApi
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.expandIn
import androidx.compose.animation.expandVertically
@@ -38,32 +47,52 @@
import androidx.compose.animation.slideOut
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.flow.collect
@OptIn(ExperimentalAnimationApi::class)
@Sampled
@@ -163,6 +192,51 @@
@OptIn(ExperimentalAnimationApi::class)
@Sampled
@Composable
+fun AnimatedVisibilityWithBooleanVisibleParamNoReceiver() {
+ var visible by remember { mutableStateOf(true) }
+ Box(modifier = Modifier.clickable { visible = !visible }) {
+ AnimatedVisibility(
+ visible = visible,
+ modifier = Modifier.align(Alignment.Center),
+ enter = slideInVertically(
+ // Start the slide from 40 (pixels) above where the content is supposed to go, to
+ // produce a parallax effect
+ initialOffsetY = { -40 }
+ ) + expandVertically(
+ expandFrom = Alignment.Top
+ ) + fadeIn(initialAlpha = 0.3f),
+ exit = shrinkVertically() + fadeOut(animationSpec = tween(200))
+ ) { // Content that needs to appear/disappear goes here:
+ // Here we can optionally define a custom enter/exit animation by creating an animation
+ // using the Transition<EnterExitState> object from AnimatedVisibilityScope:
+ val scale by transition.animateFloat {
+ when (it) {
+ // Scale will be animating from 0.8f to 1.0f during enter transition.
+ EnterExitState.PreEnter -> 0.8f
+ EnterExitState.Visible -> 1f
+ // Scale will be animating from 1.0f to 1.2f during exit animation. If the
+ // targetValue specified for PreEnter is the same as PostExit, the enter and
+ // exit animation for this property will be symmetric.
+ EnterExitState.PostExit -> 1.2f
+ }
+ }
+ Text(
+ "Content to appear/disappear",
+ Modifier.fillMaxWidth().requiredHeight(100.dp).background(Color(0xffa1feff))
+ .graphicsLayer {
+ // Apply our custom enter/exit transition
+ scaleX = scale
+ scaleY = scale
+ }.padding(top = 20.dp),
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Sampled
+@Composable
fun ColumnScope.AnimatedFloatingActionButton() {
var expanded by remember { mutableStateOf(true) }
FloatingActionButton(
@@ -290,3 +364,340 @@
}
}
}
+
+@Sampled
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+@Composable
+fun AVScopeAnimateEnterExit() {
+ @OptIn(ExperimentalAnimationApi::class)
+ @Composable
+ fun AnimatedVisibilityScope.Item(
+ modifier: Modifier,
+ backgroundColor: Color
+ ) {
+ // Creates a custom enter/exit animation for scale property.
+ val scale by transition.animateFloat { enterExitState ->
+ // Enter transition will be animating the scale from 0.9f to 1.0f
+ // (i.e. PreEnter -> Visible). Exit transition will be from 1.0f to
+ // 0.5f (i.e. Visible -> PostExit)
+ when (enterExitState) {
+ EnterExitState.PreEnter -> 0.9f
+ EnterExitState.Visible -> 1.0f
+ EnterExitState.PostExit -> 0.5f
+ }
+ }
+
+ // Since we defined `Item` as an extension function on AnimatedVisibilityScope, we can use
+ // the `animateEnterExit` modifier to produce an enter/exit animation for it. This will
+ // run simultaneously with the `AnimatedVisibility`'s enter/exit.
+ Box(
+ modifier.fillMaxWidth().padding(5.dp).animateEnterExit(
+ // Slide in from below,
+ enter = slideInVertically(initialOffsetY = { it }),
+ // No slide on the way out. So the exit animation will be scale (from the custom
+ // scale animation defined above) and fade (from AnimatedVisibility)
+ exit = ExitTransition.None
+ ).graphicsLayer {
+ scaleX = scale
+ scaleY = scale
+ }.clip(RoundedCornerShape(20.dp)).background(backgroundColor).fillMaxSize()
+ ) {
+ // Content of the item goes here...
+ }
+ }
+
+ @OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+ @Composable
+ fun AnimateMainContent(mainContentVisible: MutableTransitionState<Boolean>) {
+ Box {
+ // Use the `MutableTransitionState<Boolean>` to specify whether AnimatedVisibility
+ // should be visible. This will also allow AnimatedVisibility animation states to be
+ // observed externally.
+ AnimatedVisibility(
+ visibleState = mainContentVisible,
+ modifier = Modifier.fillMaxSize(),
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ Box {
+ Column(Modifier.fillMaxSize()) {
+ // We have created `Item`s below as extension functions on
+ // AnimatedVisibilityScope in this example. So they can define their own
+ // enter/exit to run alongside the enter/exit defined in AnimatedVisibility.
+ Item(Modifier.weight(1f), backgroundColor = Color(0xffff6f69))
+ Item(Modifier.weight(1f), backgroundColor = Color(0xffffcc5c))
+ }
+ // This FAB will be simply fading in/out as specified by the AnimatedVisibility
+ FloatingActionButton(
+ onClick = {},
+ modifier = Modifier.align(Alignment.BottomEnd).padding(20.dp),
+ backgroundColor = MaterialTheme.colors.primary
+ ) { Icon(Icons.Default.Favorite, contentDescription = null) }
+ }
+ }
+
+ // Here we can get a signal for when the Enter/Exit animation of the content above
+ // has finished by inspecting the MutableTransitionState passed to the
+ // AnimatedVisibility. This allows sequential animation after the enter/exit.
+ AnimatedVisibility(
+ // Once the main content is visible (i.e. targetState == true), and no pending
+ // animations. We will start another enter animation sequentially.
+ visible = mainContentVisible.targetState && mainContentVisible.isIdle,
+ modifier = Modifier.align(Alignment.Center),
+ enter = expandVertically(),
+ exit = fadeOut(animationSpec = tween(50))
+ ) {
+ Text("Transition Finished")
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+@Sampled
+fun AddAnimatedVisibilityToGenericTransitionSample() {
+ @Composable
+ fun ItemMainContent() {
+ Row(Modifier.height(100.dp).fillMaxWidth(), Arrangement.SpaceEvenly) {
+ Box(
+ Modifier.size(60.dp).align(Alignment.CenterVertically)
+ .background(Color(0xffcdb7f6), CircleShape)
+ )
+ Column(Modifier.align(Alignment.CenterVertically)) {
+ Box(Modifier.height(30.dp).width(300.dp).padding(5.dp).background(Color.LightGray))
+ Box(Modifier.height(30.dp).width(300.dp).padding(5.dp).background(Color.LightGray))
+ }
+ }
+ }
+
+ @OptIn(ExperimentalAnimationApi::class)
+ @Composable
+ fun SelectableItem() {
+ // This sample animates a number of properties, including AnimatedVisibility, as a part of
+ // the Transition going between selected and unselected.
+ Box(Modifier.padding(15.dp)) {
+ var selected by remember { mutableStateOf(false) }
+ // Creates a transition to animate visual changes when `selected` is changed.
+ val selectionTransition = updateTransition(selected)
+ // Animates the border color as a part of the transition
+ val borderColor by selectionTransition.animateColor { isSelected ->
+ if (isSelected) Color(0xff03a9f4) else Color.White
+ }
+ // Animates the background color when selected state changes
+ val contentBackground by selectionTransition.animateColor { isSelected ->
+ if (isSelected) Color(0xffdbf0fe) else Color.White
+ }
+ // Animates elevation as a part of the transition
+ val elevation by selectionTransition.animateDp { isSelected ->
+ if (isSelected) 10.dp else 2.dp
+ }
+ Surface(
+ shape = RoundedCornerShape(10.dp),
+ border = BorderStroke(2.dp, borderColor),
+ modifier = Modifier.clickable { selected = !selected },
+ color = contentBackground,
+ elevation = elevation,
+ ) {
+ Column(Modifier.fillMaxWidth()) {
+ ItemMainContent()
+ // Creates an AnimatedVisibility as a part of the transition, so that when
+ // selected it's visible. This will hoist all the animations that are internal
+ // to AnimatedVisibility (i.e. fade, slide, etc) to the transition. As a result,
+ // `selectionTransition` will not finish until all the animations in
+ // AnimatedVisibility as well as animations added directly to it have finished.
+ selectionTransition.AnimatedVisibility(
+ visible = { it },
+ enter = expandVertically(),
+ exit = shrinkVertically()
+ ) {
+ Box(Modifier.fillMaxWidth().padding(10.dp)) {
+ Text(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed" +
+ " eiusmod tempor incididunt labore et dolore magna aliqua. " +
+ "Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
+ "laboris nisi ut aliquip ex ea commodo consequat. Duis aute " +
+ "irure dolor."
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+@Sampled
+@Composable
+fun AnimatedVisibilityLazyColumnSample() {
+ val turquoiseColors = listOf(
+ Color(0xff07688C),
+ Color(0xff1986AF),
+ Color(0xff50B6CD),
+ Color(0xffBCF8FF),
+ Color(0xff8AEAE9),
+ Color(0xff46CECA)
+ )
+
+ // MyModel class handles the data change of the items that are displayed in LazyColumn.
+ class MyModel {
+ private val _items: MutableList<ColoredItem> = mutableStateListOf()
+ private var lastItemId = 0
+ val items: List<ColoredItem> = _items
+
+ // Each item has a MutableTransitionState field to track as well as to mutate the item's
+ // visibility. When the MutableTransitionState's targetState changes, corresponding
+ // transition will be fired. MutableTransitionState allows animation lifecycle to be
+ // observed through it's [currentState] and [isIdle]. See below for details.
+ inner class ColoredItem(val visible: MutableTransitionState<Boolean>, val itemId: Int) {
+ val color: Color
+ get() = turquoiseColors.let {
+ it[itemId % it.size]
+ }
+ }
+
+ fun addNewItem() {
+ lastItemId++
+ _items.add(
+ ColoredItem(
+ // Here the initial state of the MutableTransitionState is set to false, and
+ // target state is set to true. This will result in an enter transition for
+ // the newly added item.
+ MutableTransitionState(false).apply { targetState = true },
+ lastItemId
+ )
+ )
+ }
+
+ fun removeItem(item: ColoredItem) {
+ // By setting the targetState to false, this will effectively trigger an exit
+ // animation in AnimatedVisibility.
+ item.visible.targetState = false
+ }
+
+ @OptIn(ExperimentalTransitionApi::class)
+ fun pruneItems() {
+ // Inspect the animation status through MutableTransitionState. If isIdle == true,
+ // all animations have finished for the transition.
+ _items.removeAll(
+ items.filter {
+ // This checks that the animations have finished && the animations are exit
+ // transitions. In other words, the item has finished animating out.
+ it.visible.isIdle && !it.visible.targetState
+ }
+ )
+ }
+
+ fun removeAll() {
+ _items.forEach {
+ it.visible.targetState = false
+ }
+ }
+ }
+
+ @OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+ @Composable
+ fun AnimatedVisibilityInLazyColumn() {
+ Column {
+ val model = remember { MyModel() }
+ Row(Modifier.fillMaxWidth()) {
+ Button({ model.addNewItem() }, modifier = Modifier.padding(15.dp).weight(1f)) {
+ Text("Add")
+ }
+ }
+
+ // This sets up a flow to check whether any item has finished animating out. If yes,
+ // notify the model to prune the list.
+ LaunchedEffect(model) {
+ snapshotFlow {
+ model.items.firstOrNull { it.visible.isIdle && !it.visible.targetState }
+ }.collect {
+ if (it != null) {
+ model.pruneItems()
+ }
+ }
+ }
+ LazyColumn {
+ items(model.items, key = { it.itemId }) { item ->
+ AnimatedVisibility(
+ item.visible,
+ enter = expandVertically(),
+ exit = shrinkVertically()
+ ) {
+ Box(Modifier.fillMaxWidth().requiredHeight(90.dp).background(item.color)) {
+ Button(
+ { model.removeItem(item) },
+ modifier = Modifier.align(Alignment.CenterEnd).padding(15.dp)
+ ) {
+ Text("Remove")
+ }
+ }
+ }
+ }
+ }
+
+ Button(
+ { model.removeAll() },
+ modifier = Modifier.align(Alignment.End).padding(15.dp)
+ ) {
+ Text("Clear All")
+ }
+ }
+ }
+}
+
+@Sampled
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun AVColumnScopeWithMutableTransitionState() {
+ var visible by remember { mutableStateOf(true) }
+ val colors = remember { listOf(Color(0xff2a9d8f), Color(0xffe9c46a), Color(0xfff4a261)) }
+ Column {
+ repeat(3) {
+ AnimatedVisibility(
+ visibleState = remember {
+ // This sets up the initial state of the AnimatedVisibility to false to
+ // guarantee an initial enter transition. In contrast, initializing this as
+ // `MutableTransitionState(visible)` would result in no initial enter
+ // transition.
+ MutableTransitionState(initialState = false)
+ }.apply {
+ // This changes the target state of the visible state. If it's different than
+ // the initial state, an enter/exit transition will be triggered.
+ targetState = visible
+ },
+ ) { // Content that needs to appear/disappear goes here:
+ Box(Modifier.fillMaxWidth().height(100.dp).background(colors[it]))
+ }
+ }
+ }
+}
+
+@Sampled
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun AnimateEnterExitPartialContent() {
+ @OptIn(ExperimentalAnimationApi::class)
+ @Composable
+ fun FullScreenNotification(visible: Boolean) {
+ AnimatedVisibility(
+ visible = visible,
+ enter = fadeIn(), exit = fadeOut()
+ ) {
+ // Fade in/out the background and foreground
+ Box(Modifier.fillMaxSize().background(Color(0x88000000))) {
+ Box(
+ Modifier.align(Alignment.TopStart).animateEnterExit(
+ // Slide in/out the rounded rect
+ enter = slideInVertically(),
+ exit = slideOutVertically()
+ ).clip(RoundedCornerShape(10.dp)).requiredHeight(100.dp)
+ .fillMaxWidth().background(Color.White)
+ ) {
+ // Content of the notification goes here
+ }
+ }
+ }
+ }
+}
diff --git a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
index 056774b..f879f76 100644
--- a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
+++ b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
@@ -16,29 +16,34 @@
package androidx.compose.animation
+import androidx.compose.animation.EnterExitState.PostExit
+import androidx.compose.animation.EnterExitState.Visible
+import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.InternalAnimationApi
import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -49,14 +54,13 @@
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
-@OptIn(ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class, InternalAnimationApi::class)
class AnimatedVisibilityTest {
@get:Rule
@@ -325,60 +329,181 @@
}
}
- @Ignore
- @OptIn(ExperimentalAnimationApi::class)
+ // Test different animations for fade in and fade out, in a complete run without interruptions
+ @OptIn(ExperimentalAnimationApi::class, InternalAnimationApi::class)
@Test
fun animateVisibilityFadeTest() {
var visible by mutableStateOf(false)
- val colors = mutableListOf<Int>()
+ val easing = FastOutLinearInEasing
+ val easingOut = FastOutSlowInEasing
+ var alpha by mutableStateOf(0f)
rule.setContent {
- Box(Modifier.size(size = 20.dp).background(Color.Black)) {
- AnimatedVisibility(
- visible,
- enter = fadeIn(animationSpec = tween(500)),
- exit = fadeOut(animationSpec = tween(500)),
- modifier = Modifier.testTag("AnimV")
- ) {
- Box(modifier = Modifier.size(size = 20.dp).background(Color.White))
+ AnimatedVisibility(
+ visible,
+ enter = fadeIn(animationSpec = tween(500, easing = easing)),
+ exit = fadeOut(animationSpec = tween(300, easing = easingOut)),
+ ) {
+ Box(modifier = Modifier.size(size = 20.dp).background(Color.White))
+ LaunchedEffect(visible) {
+ var exit = false
+ val enterExit = transition
+ while (true) {
+ withFrameNanos {
+ if (enterExit.targetState == Visible) {
+ alpha = enterExit.animations.firstOrNull {
+ it.label == "alpha"
+ }?.value as Float
+ val fraction =
+ (enterExit.playTimeNanos / 1_000_000) / 500f
+ if (enterExit.currentState != Visible) {
+ assertEquals(easing.transform(fraction), alpha, 0.01f)
+ } else {
+ // When currentState = targetState, the playTime will be reset
+ // to 0. So compare alpha against expected visible value.
+ assertEquals(1f, alpha)
+ exit = true
+ }
+ } else if (enterExit.targetState == PostExit) {
+ alpha = enterExit.animations.firstOrNull {
+ it.label == "alpha"
+ }?.value as Float
+ val fraction =
+ (enterExit.playTimeNanos / 1_000_000) / 300f
+ if (enterExit.currentState != PostExit) {
+ assertEquals(
+ 1f - easingOut.transform(fraction),
+ alpha,
+ 0.01f
+ )
+ } else {
+ // When currentState = targetState, the playTime will be reset
+ // to 0. So compare alpha against expected invisible value.
+ assertEquals(0f, alpha)
+ exit = true
+ }
+ } else {
+ exit = enterExit.currentState == enterExit.targetState
+ }
+ }
+ if (exit) break
+ }
}
}
}
rule.runOnIdle {
visible = true
}
- rule.mainClock.autoAdvance = false
- while (colors.isEmpty() || colors.last() != 0xffffffff.toInt()) {
- rule.mainClock.advanceTimeByFrame()
- rule.onNodeWithTag("AnimV").apply {
- val data = IntArray(1)
- data[0] = 0
- captureToImage().readPixels(data, 10, 10, 1, 1)
- colors.add(data[0])
- }
- }
- for (i in 1 until colors.size) {
- // Check every color against the previous one to ensure the alpha is non-decreasing
- // during fade in.
- assertTrue(colors[i] >= colors[i - 1])
- }
- assertTrue(colors[0] < 0xfffffffff)
- colors.clear()
rule.runOnIdle {
+ // At this point fade in has finished, expect alpha = 1
+ assertEquals(1f, alpha)
visible = false
}
- while (colors.isEmpty() || colors.last() != 0xff000000.toInt()) {
- rule.mainClock.advanceTimeByFrame()
- rule.onNodeWithTag("AnimV").apply {
- val data = IntArray(1)
- data[0] = 0
- captureToImage().readPixels(data, 10, 10, 1, 1)
- colors.add(data[0])
+ rule.runOnIdle {
+ // At this point fade out has finished, expect alpha = 0
+ assertEquals(0f, alpha)
+ }
+ }
+
+ @OptIn(ExperimentalAnimationApi::class)
+ @Test
+ fun testEnterTransitionNoneAndExitTransitionNone() {
+ val testModifier by mutableStateOf(TestModifier())
+ val visible = MutableTransitionState(false)
+ var disposed by mutableStateOf(false)
+ rule.mainClock.autoAdvance = false
+ rule.setContent {
+ CompositionLocalProvider(LocalDensity provides Density(1f)) {
+ AnimatedVisibility(
+ visible, testModifier,
+ enter = EnterTransition.None,
+ exit = ExitTransition.None
+ ) {
+ Box(Modifier.requiredSize(100.dp, 100.dp)) {
+ DisposableEffect(Unit) {
+ onDispose {
+ disposed = true
+ }
+ }
+ }
+ }
}
}
- for (i in 1 until colors.size) {
- // Check every color against the previous one to ensure the alpha is non-increasing
- // during fade out.
- assertTrue(colors[i] <= colors[i - 1])
+
+ rule.runOnIdle {
+ assertEquals(0, testModifier.width)
+ assertEquals(0, testModifier.height)
+ visible.targetState = true
+ }
+ rule.mainClock.advanceTimeByFrame()
+ rule.mainClock.advanceTimeByFrame()
+ rule.runOnIdle {
+ assertEquals(100, testModifier.width)
+ assertEquals(100, testModifier.height)
+ assertFalse(disposed)
+ visible.targetState = false
+ }
+ rule.mainClock.advanceTimeByFrame()
+ rule.mainClock.advanceTimeByFrame()
+ rule.runOnIdle {
+ assertTrue(disposed)
+ }
+ }
+
+ private enum class TestState { State1, State2, State3 }
+
+ @OptIn(ExperimentalAnimationApi::class)
+ @Test
+ fun testTransitionExtensionAnimatedVisibility() {
+ val testModifier by mutableStateOf(TestModifier())
+ val testState = mutableStateOf(TestState.State1)
+ var currentState = TestState.State1
+ var disposed by mutableStateOf(false)
+ rule.mainClock.autoAdvance = false
+ rule.setContent {
+ CompositionLocalProvider(LocalDensity provides Density(1f)) {
+ val transition = updateTransition(testState.value)
+ currentState = transition.currentState
+ transition.AnimatedVisibility(
+ // Only visible in State2
+ visible = { it == TestState.State2 },
+ modifier = testModifier,
+ enter = expandIn(animationSpec = tween(100)),
+ exit = shrinkOut(animationSpec = tween(100))
+ ) {
+ Box(Modifier.requiredSize(100.dp, 100.dp)) {
+ DisposableEffect(Unit) {
+ onDispose {
+ disposed = true
+ }
+ }
+ }
+ }
+ }
+ }
+ rule.runOnIdle {
+ assertEquals(0, testModifier.width)
+ assertEquals(0, testModifier.height)
+ testState.value = TestState.State2
+ }
+ while (currentState != TestState.State2) {
+ assertTrue(testModifier.width < 100)
+ rule.mainClock.advanceTimeByFrame()
+ }
+ rule.runOnIdle {
+ assertEquals(100, testModifier.width)
+ assertEquals(100, testModifier.height)
+ testState.value = TestState.State3
+ }
+ while (currentState != TestState.State3) {
+ assertTrue(testModifier.width > 0)
+ assertFalse(disposed)
+ rule.mainClock.advanceTimeByFrame()
+ }
+ rule.mainClock.advanceTimeByFrame()
+ rule.runOnIdle {
+ assertEquals(0, testModifier.width)
+ assertEquals(0, testModifier.height)
+ assertTrue(disposed)
}
}
}
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
index 252b4b4..1a8e49a 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
@@ -16,63 +16,87 @@
package androidx.compose.animation
+import androidx.compose.animation.EnterExitState.PostExit
+import androidx.compose.animation.EnterExitState.PreEnter
+import androidx.compose.animation.EnterExitState.Visible
+import androidx.compose.animation.core.ExperimentalTransitionApi
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.core.Transition
+import androidx.compose.animation.core.createChildTransition
+import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMaxBy
+import kotlinx.coroutines.flow.collect
/**
* [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
* [visible] value changes. Different [EnterTransition]s and [ExitTransition]s can be defined in
* [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
- * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink, and Slide. The enter transitions
* and exit transitions can be combined using `+`. The order of the combination does not matter,
* as the transition animations will start simultaneously. See [EnterTransition] and
- * [ExitTransition] for details on the three types of transition. Here's an example of combining
- * all three types of transitions together:
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See the second sample code snippet below for example.
+ * These custom animations will be running alongside of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish before
+ * it considers itself idle. [content] will only be removed after all the (built-in and custom)
+ * exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. If there's a need to observe the state change of the enter/exit
+ * transition and follow up additional action (e.g. remove data, sequential animation, etc),
+ * consider the AnimatedVisibility API variant that takes a [MutableTransitionState] parameter.
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards the
+ * bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking will
+ * likely also animate the parent and siblings if they rely on the size of appearing/disappearing
+ * content. When the [AnimatedVisibility] composable is put in a [Row] or a [Column], the default
+ * enter and exit transitions are tailored to that particular container. See
+ * [RowScope.AnimatedVisibility] and [ColumnScope.AnimatedVisibility] for details.
+ *
+ * Here are two examples of [AnimatedVisibility]: one using the built-in enter/exit transition, the
+ * other using a custom enter/exit animation.
*
* @sample androidx.compose.animation.samples.FullyLoadedTransition
*
- * This composable function creates a custom [Layout] for its content. The size of the custom
- * layout is determined by the largest width and largest height of the children. All children
- * will be arranged in a box (aligned to the top start of the [Layout]).
+ * The example blow shows how a custom enter/exit animation can be created using the Transition
+ * object (i.e. Transition<EnterExitState>) from [AnimatedVisibilityScope].
*
- * __Note__: Once the exit transition is finished, the [content] composable will be skipped (i.e.
- * the content will be removed from the tree, and disposed).
- *
- * By default, the enter transition will be a combination of fading in and expanding the content in
- * from the bottom end. And the exit transition will be shrinking the content towards the bottom
- * end while fading out. The expanding and shrinking will likely also animate the parent and
- * siblings if they rely on the size of appearing/disappearing content. When the
- * [AnimatedVisibility] composable is put in a [Row] or a [Column], the default enter and exit
- * transitions are tailored to that particular container. See [RowScope.AnimatedVisibility] and
- * [ColumnScope.AnimatedVisibility] for details.
- *
- * [initiallyVisible] defaults to the same value as [visible]. This means when the
- * [AnimatedVisibility] is first added to the tree, there is no appearing animation. If it is
- * desired to show an appearing animation for the first appearance of the content,
- * [initiallyVisible] can be set to false and [visible] to true.
+ * @sample androidx.compose.animation.samples.AnimatedVisibilityWithBooleanVisibleParamNoReceiver
*
* @param visible defines whether the content should be visible
* @param modifier modifier for the [Layout] created to contain the [content]
- * @param enter [EnterTransition]s used for the appearing animation, fading in while expanding by
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding by
* default
- * @param exit [ExitTransition](s) used for the disappearing animation, fading out while
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
* shrinking by default
- * @param initiallyVisible controls whether the first appearance should be animated, defaulting
- * to match [visible] (i.e. not animating the first appearance)
+ * @param content Content to appear or disappear based on the value of [visible]
*
* @see EnterTransition
* @see ExitTransition
@@ -80,8 +104,7 @@
* @see expandIn
* @see fadeOut
* @see shrinkOut
- * @see RowScope.AnimatedVisibility
- * @see ColumnScope.AnimatedVisibility
+ * @see AnimatedVisibilityScope
*/
@ExperimentalAnimationApi
@Composable
@@ -90,51 +113,63 @@
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandIn(),
exit: ExitTransition = shrinkOut() + fadeOut(),
- initiallyVisible: Boolean = visible,
- content: @Composable () -> Unit
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
) {
- AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
+ val transition = updateTransition(visible)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
}
/**
- * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
- * [visible] value changes. Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [RowScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content when the [AnimatedVisibility] is in a [Row]. The default animations are tailored
+ * specific to the [Row] layout. See more details below.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
* [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
* [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
* and exit transitions can be combined using `+`. The order of the combination does not matter,
* as the transition animations will start simultaneously. See [EnterTransition] and
- * [ExitTransition] for details on the three types of transition. Here's an example of using
- * [RowScope.AnimatedVisibility] in a [Row]:
+ * [ExitTransition] for details on the three types of transition.
*
- * @sample androidx.compose.animation.samples.AnimatedFloatingActionButton
- *
- * This composable function creates a custom [Layout] for its content. The size of the custom
- * layout is determined by the largest width and largest height of the children. All children
- * will be arranged in a box (aligned to the top start of the [Layout]).
- *
- * __Note__: Once the exit transition is finished, the [content] composable will be skipped (i.e.
- * the content will be removed from the tree, and disposed).
- *
- * By default, the enter transition will be a combination of fading in and expanding the content
- * horizontally. The end of the content will be the leading edge as the content expands to its
- * full width. And the exit transition will be shrinking the content with the end of the
+ * The default [enter] and [exit] transition is configured based on the horizontal layout of a
+ * [Row]. [enter] defaults to a combination of fading in and expanding the content horizontally.
+ * (The end of the content will be the leading edge as the content expands to its
+ * full width.) And [exit] defaults to shrinking the content horizontally with the end of the
* content being the leading edge while fading out. The expanding and shrinking will likely also
* animate the parent and siblings in the row since they rely on the size of appearing/disappearing
* content.
*
- * [initiallyVisible] defaults to the same value as [visible]. This means when the
- * [AnimatedVisibility] is first added to the tree, there is no appearing animation. If it is
- * desired to show an appearing animation for the first appearance of the content,
- * [initiallyVisible] can be set to false and [visible] to true.
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. If there's a need to observe the state change of the enter/exit
+ * transition and follow up additional action (e.g. remove data, sequential animation, etc),
+ * consider the AnimatedVisibility API variant that takes a [MutableTransitionState] parameter.
+ *
+ * Here's an example of using [RowScope.AnimatedVisibility] in a [Row]:
+ * @sample androidx.compose.animation.samples.AnimatedFloatingActionButton
*
* @param visible defines whether the content should be visible
* @param modifier modifier for the [Layout] created to contain the [content]
- * @param enter [EnterTransition]s used for the appearing animation, fading in while expanding
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
* horizontally by default
- * @param exit [ExitTransition](s) used for the disappearing animation, fading out while
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
* shrinking horizontally by default
- * @param initiallyVisible controls whether the first appearance should be animated, defaulting
- * to match [visible] (i.e. not animating the first appearance)
+ * @param content Content to appear or disappear based on the value of [visible]
*
* @see EnterTransition
* @see ExitTransition
@@ -144,6 +179,7 @@
* @see shrinkOut
* @see AnimatedVisibility
* @see ColumnScope.AnimatedVisibility
+ * @see AnimatedVisibilityScope
*/
@ExperimentalAnimationApi
@Composable
@@ -152,51 +188,62 @@
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandHorizontally(),
exit: ExitTransition = fadeOut() + shrinkHorizontally(),
- initiallyVisible: Boolean = visible,
- content: @Composable () -> Unit
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
) {
- AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
+ val transition = updateTransition(visible)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
}
/**
- * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
- * [visible] value changes. Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [ColumnScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content when the [AnimatedVisibility] is in a [Column]. The default animations are tailored
+ * specific to the [Column] layout. See more details below.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
* [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
* [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
* and exit transitions can be combined using `+`. The order of the combination does not matter,
* as the transition animations will start simultaneously. See [EnterTransition] and
- * [ExitTransition] for details on the three types of transition. Here's an example of using
- * [ColumnScope.AnimatedVisibility] in a [Column]:
+ * [ExitTransition] for details on the three types of transition.
*
- * @sample androidx.compose.animation.samples.ColumnAnimatedVisibilitySample
+ * The default [enter] and [exit] transition is configured based on the vertical layout of a
+ * [Column]. [enter] defaults to a combination of fading in and expanding the content vertically.
+ * (The bottom of the content will be the leading edge as the content expands to its full height.)
+ * And the [exit] defaults to shrinking the content vertically with the bottom of the content being
+ * the leading edge while fading out. The expanding and shrinking will likely also animate the
+ * parent and siblings in the column since they rely on the size of appearing/disappearing content.
*
- * This composable function creates a custom [Layout] for its content. The size of the custom
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
* layout is determined by the largest width and largest height of the children. All children
- * will be arranged in a box (aligned to the top start of the [Layout]).
+ * will be aligned to the top start of the [Layout].
*
- * __Note__: Once the exit transition is finished, the [content] composable will be skipped (i.e.
- * the content will be removed from the tree, and disposed).
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. If there's a need to observe the state change of the enter/exit
+ * transition and follow up additional action (e.g. remove data, sequential animation, etc),
+ * consider the AnimatedVisibility API variant that takes a [MutableTransitionState] parameter.
*
- * By default, the enter transition will be a combination of fading in and expanding the content
- * vertically in the [Column]. The bottom of the content will be the leading edge as the content
- * expands to its full height. And the exit transition will be shrinking the content with the
- * bottom of the content being the leading edge while fading out. The expanding and shrinking will
- * likely also animate the parent and siblings in the column since they rely on the size of
- * appearing/disappearing content.
- *
- * [initiallyVisible] defaults to the same value as [visible]. This means when the
- * [AnimatedVisibility] is first added to the tree, there is no appearing animation. If it is
- * desired to show an appearing animation for the first appearance of the content,
- * [initiallyVisible] can be set to false and [visible] to true.
+ * Here's an example of using [ColumnScope.AnimatedVisibility] in a [Column]:
+ * @sample androidx.compose.animation.samples.ColumnAnimatedVisibilitySample
*
* @param visible defines whether the content should be visible
* @param modifier modifier for the [Layout] created to contain the [content]
- * @param enter [EnterTransition]s used for the appearing animation, fading in while expanding
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
* vertically by default
- * @param exit [ExitTransition](s) used for the disappearing animation, fading out while
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
* shrinking vertically by default
- * @param initiallyVisible controls whether the first appearance should be animated, defaulting
- * to match [visible] (i.e. not animating the first appearance)
+ * @param content Content to appear or disappear based on the value of [visible]
*
* @see EnterTransition
* @see ExitTransition
@@ -205,7 +252,7 @@
* @see fadeOut
* @see shrinkOut
* @see AnimatedVisibility
- * @see ColumnScope.AnimatedVisibility
+ * @see AnimatedVisibilityScope
*/
@ExperimentalAnimationApi
@Composable
@@ -214,97 +261,529 @@
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandVertically(),
exit: ExitTransition = fadeOut() + shrinkVertically(),
- initiallyVisible: Boolean = visible,
- content: @Composable () -> Unit
+ content: @Composable AnimatedVisibilityScope.() -> Unit
) {
- AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
+ val transition = updateTransition(visible)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * [EnterExitState] contains the three states that are involved in the enter and exit transition
+ * of [AnimatedVisibility]. More specifically, [PreEnter] and [Visible] defines the initial and
+ * target state of an *enter* transition, whereas [Visible] and [PostExit] are the initial and
+ * target state of an *exit* transition.
+ *
+ * See blow for an example of custom enter/exit animation in [AnimatedVisibility] using
+ * `Transition<EnterExitState>` (i.e. [AnimatedVisibilityScope.transition]):
+ *
+ * @sample androidx.compose.animation.samples.AnimatedVisibilityWithBooleanVisibleParamNoReceiver
+ * @see AnimatedVisibility
+ */
+@ExperimentalAnimationApi
+enum class EnterExitState {
+ /**
+ * The initial state of a custom enter animation in [AnimatedVisibility]..
+ */
+ PreEnter,
+
+ /**
+ * The `Visible` state is the target state of a custom *enter* animation, also the initial
+ * state of a custom *exit* animation in [AnimatedVisibility].
+ */
+ Visible,
+
+ /**
+ * Target state of a custom *exit* animation in [AnimatedVisibility].
+ */
+ PostExit
+}
+
+/**
+ * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
+ * [visibleState]'s [targetState][MutableTransitionState.targetState] changes. The [visibleState]
+ * can also be used to observe the state of [AnimatedVisibility]. For example:
+ * `visibleState.idIdle` indicates whether the all animations have finished in [AnimatedVisibility],
+ * and `visibleState.currentState` returns the initial state of the current animations.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. Both `currentState` and `targetState` will be `false` for
+ * [visibleState].
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards the
+ * bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking will
+ * likely also animate the parent and siblings if they rely on the size of appearing/disappearing
+ * content. When the [AnimatedVisibility] composable is put in a [Row] or a [Column], the default
+ * enter and exit transitions are tailored to that particular container. See
+ * [RowScope.AnimatedVisibility] and [ColumnScope.AnimatedVisibility] for details.
+ *
+ * @sample androidx.compose.animation.samples.AnimatedVisibilityLazyColumnSample
+ *
+ * @param visibleState defines whether the content should be visible
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ * vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ * shrinking vertically by default
+ * @param content Content to appear or disappear based on the value of [visibleState]
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibility
+ * @see Transition.AnimatedVisibility
+ * @see AnimatedVisibilityScope
+ */
+@ExperimentalAnimationApi
+@Composable
+fun AnimatedVisibility(
+ visibleState: MutableTransitionState<Boolean>,
+ modifier: Modifier = Modifier,
+ enter: EnterTransition,
+ exit: ExitTransition,
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+ val transition = updateTransition(visibleState)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * [RowScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content as [visibleState]'s [targetState][MutableTransitionState.targetState] changes. The
+ * default [enter] and [exit] transitions are tailored specific to the [Row] layout. See more
+ * details below. The [visibleState] can also be used to observe the state of [AnimatedVisibility].
+ * For example: `visibleState.idIdle` indicates whether the all animations have finished in
+ * [AnimatedVisibility], and `visibleState.currentState` returns the initial state of the current
+ * animations.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * The default [enter] and [exit] transition is configured based on the horizontal layout of a
+ * [Row]. [enter] defaults to a combination of fading in and expanding the content horizontally.
+ * (The end of the content will be the leading edge as the content expands to its
+ * full width.) And [exit] defaults to shrinking the content horizontally with the end of the
+ * content being the leading edge while fading out. The expanding and shrinking will likely also
+ * animate the parent and siblings in the row since they rely on the size of appearing/disappearing
+ * content.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. Both `currentState` and `targetState` will be `false` for
+ * [visibleState].
+ *
+ * @param visibleState defines whether the content should be visible
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ * vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ * shrinking vertically by default
+ * @param content Content to appear or disappear based on the value of [visibleState]
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibility
+ * @see Transition.AnimatedVisibility
+ * @see AnimatedVisibilityScope
+ */
+@ExperimentalAnimationApi
+@Composable
+fun RowScope.AnimatedVisibility(
+ visibleState: MutableTransitionState<Boolean>,
+ modifier: Modifier = Modifier,
+ enter: EnterTransition = expandHorizontally() + fadeIn(),
+ exit: ExitTransition = shrinkHorizontally() + fadeOut(),
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+ val transition = updateTransition(visibleState)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * [ColumnScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content as [visibleState]'s [targetState][MutableTransitionState.targetState] changes. The
+ * default [enter] and [exit] transitions are tailored specific to the [Column] layout. See more
+ * details below. The [visibleState] can also be used to observe the state of [AnimatedVisibility].
+ * For example: `visibleState.idIdle` indicates whether the all animations have finished in
+ * [AnimatedVisibility], and `visibleState.currentState` returns the initial state of the current
+ * animations.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * The default [enter] and [exit] transition is configured based on the vertical layout of a
+ * [Column]. [enter] defaults to a combination of fading in and expanding the content vertically.
+ * (The bottom of the content will be the leading edge as the content expands to its full height.)
+ * And the [exit] defaults to shrinking the content vertically with the bottom of the content being
+ * the leading edge while fading out. The expanding and shrinking will likely also animate the
+ * parent and siblings in the column since they rely on the size of appearing/disappearing content.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. Both `currentState` and `targetState` will be `false` for
+ * [visibleState].
+ *
+ * @sample androidx.compose.animation.samples.AVColumnScopeWithMutableTransitionState
+ *
+ * @param visibleState defines whether the content should be visible
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ * vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ * shrinking vertically by default
+ * @param content Content to appear or disappear based on of [visibleState]
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibility
+ * @see Transition.AnimatedVisibility
+ * @see AnimatedVisibilityScope
+ */
+@ExperimentalAnimationApi
+@Composable
+fun ColumnScope.AnimatedVisibility(
+ visibleState: MutableTransitionState<Boolean>,
+ modifier: Modifier = Modifier,
+ enter: EnterTransition = expandVertically() + fadeIn(),
+ exit: ExitTransition = shrinkVertically() + fadeOut(),
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+ val transition = updateTransition(visibleState)
+ AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * This extension function creates an [AnimatedVisibility] composable as a child Transition of
+ * the given Transition. This means: 1) the enter/exit transition is now triggered by the provided
+ * [Transition]'s [targetState][Transition.targetState] change. When the targetState changes, the
+ * visibility will be derived using the [visible] lambda and [Transition.targetState]. 2)
+ * The enter/exit transitions, as well as any custom enter/exit animations defined in
+ * [AnimatedVisibility] are now hoisted to the parent Transition. The parent Transition will wait
+ * for all of them to finish before it considers itself finished (i.e. [Transition.currentState]
+ * = [Transition.targetState]), and subsequently removes the content in the exit case.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed.
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards the
+ * bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking will
+ * likely also animate the parent and siblings if they rely on the size of appearing/disappearing
+ * content.
+ *
+ * @sample androidx.compose.animation.samples.AddAnimatedVisibilityToGenericTransitionSample
+ *
+ * @param visible defines whether the content should be visible based on transition state T
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ * vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ * shrinking vertically by default
+ * @param content Content to appear or disappear based on the visibility derived from the
+ * [Transition.targetState] and the provided [visible] lambda
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibilityScope
+ * @see Transition.AnimatedVisibility
+ */
+@ExperimentalAnimationApi
+@Composable
+fun <T> Transition<T>.AnimatedVisibility(
+ visible: (T) -> Boolean,
+ modifier: Modifier = Modifier,
+ enter: EnterTransition = fadeIn() + expandIn(),
+ exit: ExitTransition = shrinkOut() + fadeOut(),
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) = AnimatedEnterExitImpl(this, visible, modifier, enter, exit, content)
+
+/**
+ * This is the scope for the content of [AnimatedVisibility]. In this scope, direct and
+ * indirect children of [AnimatedVisibility] will be able to define their own enter/exit
+ * transitions using the built-in options via [Modifier.animateEnterExit]. They will also be able
+ * define custom enter/exit animations using the [transition] object. [AnimatedVisibility] will
+ * ensure both custom and built-in enter/exit animations finish before it considers itself idle,
+ * and subsequently removes its content in the case of exit.
+ *
+ * __Note:__ Custom enter/exit animations that are created *independent* of the
+ * [AnimatedVisibilityScope.transition] will have no guarantee to finish when
+ * exiting, as [AnimatedVisibility] would have no visibility of such animations.
+ *
+ * @sample androidx.compose.animation.samples.AVScopeAnimateEnterExit
+ */
+@ExperimentalAnimationApi
+class AnimatedVisibilityScope internal constructor(transition: Transition<EnterExitState>) {
+ /**
+ * [transition] allows custom enter/exit animations to be specified. It will run simultaneously
+ * with the built-in enter/exit transitions specified in [AnimatedVisibility].
+ */
+ var transition: Transition<EnterExitState> = transition
+ internal set
+
+ /**
+ * [animateEnterExit] modifier can be used for any direct or indirect children of
+ * [AnimatedVisibility] to create a different enter/exit animation than what's specified in
+ * [AnimatedVisibility]. The visual effect of these children will be a combination of the
+ * [AnimatedVisibility]'s animation and their own enter/exit animations.
+ *
+ * [enter] and [exit] defines different [EnterTransition]s and [ExitTransition]s that will be
+ * used for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink, and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards
+ * the bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking
+ * will likely also animate the parent and siblings if they rely on the size of
+ * appearing/disappearing content.
+ *
+ * In some cases it may be desirable to have [AnimatedVisibility] apply no animation at all for
+ * enter and/or exit, such that children of [AnimatedVisibility] can each have their distinct
+ * animations. To achieve this, [EnterTransition.None] and/or [ExitTransition.None] can be
+ * used for [AnimatedVisibility].
+ *
+ * @sample androidx.compose.animation.samples.AnimateEnterExitPartialContent
+ */
+ fun Modifier.animateEnterExit(
+ enter: EnterTransition = fadeIn() + expandIn(),
+ exit: ExitTransition = fadeOut() + shrinkOut()
+ ): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "animateEnterExit"
+ properties["enter"] = enter
+ properties["exit"] = exit
+ }
+ ) {
+ this.then(transition.createModifier(enter, exit))
+ }
}
@ExperimentalAnimationApi
@Composable
-private fun AnimatedVisibilityImpl(
+@Deprecated(
+ "AnimatedVisibility no longer accepts initiallyVisible as a parameter",
+ replaceWith = ReplaceWith(
+ "AnimatedVisibility(" +
+ "transitionState = remember { MutableTransitionState(initiallyVisible) }\n" +
+ ".apply { targetState = visible },\n" +
+ "modifier = modifier,\n" +
+ "enter = enter,\n" +
+ "exit = exit) {\n" +
+ "content() \n" +
+ "}",
+ "androidx.compose.animation.core.MutableTransitionState"
+ )
+)
+fun AnimatedVisibility(
visible: Boolean,
- modifier: Modifier,
+ modifier: Modifier = Modifier,
enter: EnterTransition,
exit: ExitTransition,
initiallyVisible: Boolean,
content: @Composable () -> Unit
+) = AnimatedVisibility(
+ visibleState = remember { MutableTransitionState(initiallyVisible) }
+ .apply { targetState = visible },
+ modifier = modifier,
+ enter = enter,
+ exit = exit
) {
+ content()
+}
- // Set up initial transition states, based on the initial visibility.
- var transitionState by remember {
- mutableStateOf(if (initiallyVisible) AnimStates.Visible else AnimStates.Gone)
+// RowScope and ColumnScope AnimatedEnterExit extensions and AnimatedEnterExit without a receiver
+// converge here.
+@OptIn(ExperimentalTransitionApi::class)
+@ExperimentalAnimationApi
+@Composable
+private fun <T> AnimatedEnterExitImpl(
+ transition: Transition<T>,
+ visible: (T) -> Boolean,
+ modifier: Modifier,
+ enter: EnterTransition,
+ exit: ExitTransition,
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+ val isAnimationVisible = remember(transition) {
+ mutableStateOf(visible(transition.currentState))
}
-
- var isAnimating by remember { mutableStateOf(false) }
-
- // Update transition states, based on the current visibility.
- if (visible) {
- if (transitionState == AnimStates.Gone ||
- transitionState == AnimStates.Exiting
- ) {
- transitionState = AnimStates.Entering
- isAnimating = true
+ if (visible(transition.targetState) || isAnimationVisible.value) {
+ val childTransition = transition.createChildTransition {
+ transition.targetEnterExit(visible, it)
}
- } else {
- if (transitionState == AnimStates.Visible ||
- transitionState == AnimStates.Entering
- ) {
- transitionState = AnimStates.Exiting
- isAnimating = true
+ LaunchedEffect(childTransition) {
+ snapshotFlow {
+ childTransition.currentState == EnterExitState.Visible ||
+ childTransition.targetState == EnterExitState.Visible
+ }.collect {
+ isAnimationVisible.value = it
+ }
}
- }
- val scope = rememberCoroutineScope()
- val animations = remember(scope, enter, exit) {
- // TODO: Should we delay changing enter/exit after on-going animations are finished?
- TransitionAnimations(enter, exit, scope) {
- isAnimating = false
- }
- }
- animations.updateState(transitionState)
-
- // If the exit animation has finished, skip the child composable altogether
- if (transitionState == AnimStates.Gone) {
- return
- }
-
- Layout(
- content = content,
- modifier = modifier.then(animations.modifier)
- ) { measureables, constraints ->
-
- val placeables = measureables.map { it.measure(constraints) }
- val maxWidth: Int = placeables.fastMaxBy { it.width }?.width ?: 0
- val maxHeight = placeables.fastMaxBy { it.height }?.height ?: 0
-
- val offset: IntOffset
- val animatedSize: IntSize
- val animSize = animations.getAnimatedSize(
- IntSize(maxWidth, maxHeight)
+ AnimatedEnterExitImpl(
+ childTransition,
+ modifier,
+ enter = enter,
+ exit = exit,
+ content = content
)
- if (animSize != null) {
- offset = animSize.first
- animatedSize = animSize.second
+ }
+}
+
+@OptIn(ExperimentalTransitionApi::class)
+@ExperimentalAnimationApi
+@Composable
+private inline fun AnimatedEnterExitImpl(
+ transition: Transition<EnterExitState>,
+ modifier: Modifier,
+ enter: EnterTransition,
+ exit: ExitTransition,
+ content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+ // TODO: Get some feedback on whether there's a need to observe this state change in user
+ // code. If there is, this if check will need to be moved to measure stage, along with some
+ // structural changes.
+ if (transition.currentState == EnterExitState.Visible ||
+ transition.targetState == EnterExitState.Visible
+ ) {
+ val scope = remember(transition) { AnimatedVisibilityScope(transition) }
+ Layout(
+ content = { scope.content() },
+ modifier = modifier.then(transition.createModifier(enter, exit))
+ ) { measureables, constraints ->
+ val placeables = measureables.map { it.measure(constraints) }
+ val maxWidth: Int = placeables.fastMaxBy { it.width }?.width ?: 0
+ val maxHeight = placeables.fastMaxBy { it.height }?.height ?: 0
+ // Position the children.
+ layout(maxWidth, maxHeight) {
+ placeables.fastForEach {
+ it.place(0, 0)
+ }
+ }
+ }
+ }
+}
+
+// This converts Boolean visible to EnterExitState
+@ExperimentalAnimationApi
+@Composable
+private fun <T> Transition<T>.targetEnterExit(
+ visible: (T) -> Boolean,
+ targetState: T
+): EnterExitState = key(this) {
+ val hasBeenVisible = remember { mutableStateOf(false) }
+ if (visible(currentState)) {
+ hasBeenVisible.value = true
+ }
+ if (visible(targetState)) {
+ EnterExitState.Visible
+ } else {
+ // If never been visible, visible = false means PreEnter, otherwise PostExit
+ if (hasBeenVisible.value) {
+ EnterExitState.PostExit
} else {
- offset = IntOffset.Zero
- animatedSize = IntSize(maxWidth, maxHeight)
- }
-
- // If animation has finished update state
- if (!isAnimating) {
- if (transitionState == AnimStates.Exiting) {
- transitionState = AnimStates.Gone
- } else if (transitionState == AnimStates.Entering) {
- transitionState = AnimStates.Visible
- }
- }
-
- // Position the children.
- layout(animatedSize.width, animatedSize.height) {
- placeables.fastForEach {
- it.place(offset.x, offset.y)
- }
+ EnterExitState.PreEnter
}
}
}
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
index e1f756b..b869a5e 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
@@ -14,16 +14,30 @@
* limitations under the License.
*/
+@file:OptIn(InternalAnimationApi::class, ExperimentalAnimationApi::class)
+
package androidx.compose.animation
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationEndReason
+import android.annotation.SuppressLint
import androidx.compose.animation.core.AnimationVector2D
import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.InternalAnimationApi
+import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.createDeferredAnimation
import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.VisibilityThreshold
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.key
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
@@ -37,10 +51,6 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.util.fastFirstOrNull
-import androidx.compose.ui.util.fastForEach
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
@RequiresOptIn(message = "This is an experimental animation API.")
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@@ -92,13 +102,24 @@
)
)
}
- // TODO: Support EnterTransition.None
override fun equals(other: Any?): Boolean {
return other is EnterTransition && other.data == data
}
override fun hashCode(): Int = data.hashCode()
+
+ companion object {
+ /**
+ * This can be used when no enter transition is desired. It can be useful in cases where
+ * there are other forms of enter animation defined indirectly for an
+ * [AnimatedVisibility]. e.g.The children of the [AnimatedVisibility] have all defined
+ * their own [EnterTransition], or when the parent is fading in, etc.
+ *
+ * @see [ExitTransition.None]
+ */
+ val None: EnterTransition = EnterTransitionImpl(TransitionData())
+ }
}
/**
@@ -150,12 +171,26 @@
)
}
- // TODO: Support ExitTransition.None
override fun equals(other: Any?): Boolean {
return other is ExitTransition && other.data == data
}
override fun hashCode(): Int = data.hashCode()
+
+ companion object {
+ /**
+ * This can be used when no built-in [ExitTransition] (i.e. fade/slide, etc) is desired for
+ * the [AnimatedVisibility], but rather the children are defining their own exit
+ * animation using the [Transition] scope.
+ *
+ * __Note:__ If [None] is used, and nothing is animating in the Transition<EnterExitState>
+ * scope that [AnimatedVisibility] provided, the content will be removed from
+ * [AnimatedVisibility] right away.
+ *
+ * @sample androidx.compose.animation.samples.AVScopeAnimateEnterExit
+ */
+ val None: ExitTransition = ExitTransitionImpl(TransitionData())
+ }
}
/**
@@ -556,7 +591,7 @@
*
* [targetOffsetX] is a lambda that takes the full width of the content and returns an
* offset. This allows the target offset to be defined proportional to the full size, or as an
- * absolute value. It defaults to return half of negaive width, which would slide the content to
+ * absolute value. It defaults to return half of negative width, which would slide the content to
* the left by half of its width.
*
* @sample androidx.compose.animation.samples.SlideTransition
@@ -615,7 +650,7 @@
@Immutable
internal data class ChangeSize(
val alignment: Alignment,
- val startSize: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) },
+ val size: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) },
val animationSpec: FiniteAnimationSpec<IntSize> = spring(),
val clip: Boolean = true
)
@@ -651,517 +686,285 @@
val changeSize: ChangeSize? = null
)
-/**
- * Alignment does NOT stay consistent in enter vs. exit animations. For example, the enter could be
- * expanding from top, but exit could be shrinking towards the bottom. As a result, when such an
- * animation is interrupted, it becomes very tricky to handle that. This is why there needs to be
- * two types of size animations: alignment based and rect based. When alignment stays the same,
- * size is the only value that needs to be animated. When alignment changes, however, the only
- * sensible solution is to fall back to rect based solution. Namely, this calculates the current
- * clip rect based on alignment and size, and the final rect based on the new alignment and the
- * ending size. In rect based animations, the size will still be animated using the provided size
- * animation, and the offset will be animated using physics as a part of the interruption
- * handling logic.
- */
-internal interface SizeAnimation {
- val anim: Animatable<IntSize, AnimationVector2D>
- var clip: Boolean
- val size: IntSize
- get() = anim.value
-
- val offset: (IntSize) -> IntOffset
- val isAnimating: Boolean
- val listener: (AnimationEndReason, Any) -> Unit
-
- /**
- * The instance returned may be different than the caller if the alignment has changed. Either
- * way the returned animation will be configured to animate to the new target.
- */
- fun animateTo(
- target: IntSize,
- alignment: Alignment,
- fullSize: IntSize,
- spec: FiniteAnimationSpec<IntSize>,
- scope: CoroutineScope,
- ): SizeAnimation
-
- /**
- * Returns what the offset will be once the target size is applied.
- */
- fun snapTo(target: IntSize, scope: CoroutineScope): IntOffset
-
- val alignment: Alignment
-}
-
-/**
- * This is the animation class used to animate content size. However, this animation may get
- * interrupted before it finishes. If the new size target is based on the same alignment, this
- * instance can be re-used to handle that interruption. A more complicated and hairy case is when
- * the alignment changes (from top aligned to bottom aligned), in which case we have to fall back
- * to Rect based animation to properly handle the alignment change.
- */
-private class AlignmentBasedSizeAnimation(
- override val anim: Animatable<IntSize, AnimationVector2D>,
- override val alignment: Alignment,
- override var clip: Boolean,
- override val listener: (AnimationEndReason, Any) -> Unit
-) : SizeAnimation {
-
- override val offset: (IntSize) -> IntOffset
- get() = {
- alignment.align(it, anim.value, LayoutDirection.Ltr)
- }
-
- override fun animateTo(
- target: IntSize,
- alignment: Alignment,
- fullSize: IntSize,
- spec: FiniteAnimationSpec<IntSize>,
- scope: CoroutineScope,
- ): SizeAnimation {
- if (anim.targetValue != target) {
- scope.launch {
- anim.animateTo(target, spec)
- listener(AnimationEndReason.Finished, anim.value)
- }
- }
-
- if (alignment == this.alignment) {
- return this
- } else {
- // Alignment changed
- val offset = this.offset(fullSize)
- return RectBasedSizeAnimation(anim, offset, clip, scope, listener)
- }
- }
-
- override fun snapTo(target: IntSize, scope: CoroutineScope): IntOffset {
- scope.launch {
- anim.snapTo(target)
- }
- return alignment.align(target, target, LayoutDirection.Ltr)
- }
-
- override val isAnimating: Boolean
- get() = anim.isRunning
-}
-
-/**
- * This class animates the rect of the clip bounds, as a fallback for when enter and exit size
- * change animations have different alignment.
- */
-private class RectBasedSizeAnimation(
- override val anim: Animatable<IntSize, AnimationVector2D>,
- targetOffset: IntOffset,
- override var clip: Boolean,
- val scope: CoroutineScope,
- override val listener: (AnimationEndReason, Any) -> Unit
-) : SizeAnimation {
- private val offsetAnim: Animatable<IntOffset, AnimationVector2D>
-
- init {
- offsetAnim = Animatable(
- IntOffset(0, 0), IntOffset.VectorConverter,
- IntOffset(1, 1)
- )
- scope.launch {
- offsetAnim.animateTo(targetOffset)
- listener(AnimationEndReason.Finished, offsetAnim.value)
- }
- }
-
- override val alignment: Alignment
- get() = Alignment.TopStart
-
- override val offset: (IntSize) -> IntOffset
- get() = {
- offsetAnim.value
- }
-
- override fun animateTo(
- target: IntSize,
- alignment: Alignment,
- fullSize: IntSize,
- spec: FiniteAnimationSpec<IntSize>,
- scope: CoroutineScope,
- ): SizeAnimation {
- val targetOffSet = alignment.align(fullSize, target, LayoutDirection.Ltr)
- if (offsetAnim.targetValue != targetOffSet) {
- scope.launch {
- offsetAnim.animateTo(targetOffSet)
- listener(AnimationEndReason.Finished, offsetAnim.value)
- }
- }
- if (target != anim.targetValue) {
- scope.launch {
- anim.animateTo(target, spec)
- listener(AnimationEndReason.Finished, anim.value)
- }
- }
- return this
- }
-
- override fun snapTo(target: IntSize, scope: CoroutineScope): IntOffset {
- val targetOffSet = alignment.align(target, target, LayoutDirection.Ltr)
- scope.launch {
- offsetAnim.snapTo(targetOffSet)
- anim.snapTo(target)
- }
- return targetOffSet
- }
-
- override val isAnimating: Boolean
- get() = (anim.isRunning || offsetAnim.isRunning)
-}
-
-private operator fun IntSize.minus(b: IntSize) =
- IntSize(width - b.width, height - b.height)
-
-internal interface TransitionAnimation {
- val isRunning: Boolean
- var state: AnimStates
- val modifier: Modifier
- fun getAnimatedSize(fullSize: IntSize): Pair<IntOffset, IntSize>? = null
- val listener: (AnimationEndReason, Any) -> Unit
-}
-
-/**
- * This class animates alpha through a graphics layer modifier.
- */
-private class FadeTransition(
- val enter: Fade? = null,
- val exit: Fade? = null,
- val scope: CoroutineScope,
- override val listener: (AnimationEndReason, Any) -> Unit
-) : TransitionAnimation {
- override val isRunning: Boolean
- get() = alphaAnim.isRunning
- override val modifier: Modifier
- get() = Modifier.graphicsLayer {
- alpha = alphaAnim.value
- }
-
- override var state: AnimStates = AnimStates.Gone
- set(value) {
- if (value == field) {
- return
- }
- // Animation state has changed if we get here.
- if (value == AnimStates.Entering) {
- // Animation is interrupted from fade out, now fade in
- if (alphaAnim.isRunning) {
- enter?.apply {
- // If fade in animation specified, use that. Otherwise use default.
- animateTo(1f, animationSpec, listener)
- } ?: animateTo(1f, listener = listener)
- } else {
- // set up initial values for alphaAnimation
- enter?.apply {
- // If fade in is defined start from pre-defined `alphaFrom`. If no fade in is defined,
- // snap the alpha to 1f
- alphaAnim = Animatable(alpha, 0.02f)
- scope.launch {
- alphaAnim.animateTo(1f, animationSpec)
- listener(AnimationEndReason.Finished, alphaAnim.value)
- }
- // If no enter is defined and animation isn't running, snap to alpha = 1
- } ?: scope.launch {
- alphaAnim.snapTo(1f)
- }
- }
- } else if (value == AnimStates.Exiting) {
- if (alphaAnim.isRunning) {
- // interrupting alpha animation: directly animating to out value if defined,
- // otherwise let the fade-in finish
- exit?.apply {
- animateTo(alpha, animationSpec, listener)
- }
- } else {
- // set up alpha animation to fade out, if fade out is defined
- exit?.apply {
- animateTo(alpha, animationSpec, listener)
- }
- }
- }
- field = value
- }
-
- private fun animateTo(
- target: Float,
- animationSpec: FiniteAnimationSpec<Float> = spring(visibilityThreshold = 0.02f),
- listener: (AnimationEndReason, Any) -> Unit
- ) {
- scope.launch {
- alphaAnim.animateTo(target, animationSpec)
- listener(AnimationEndReason.Finished, alphaAnim.value)
- }
- }
-
- var alphaAnim = Animatable(1f, visibilityThreshold = 0.02f)
-}
-
-private class SlideTransition(
- val enter: Slide? = null,
- val exit: Slide? = null,
- val scope: CoroutineScope,
- override val listener: (AnimationEndReason, Any) -> Unit
-) : TransitionAnimation {
- override val isRunning: Boolean
- get() {
- if (slideAnim?.isRunning == true) {
- return true
- }
- if (state != currentState) {
- if (state == AnimStates.Entering && enter != null) {
- return true
- } else if (state == AnimStates.Exiting && exit != null) {
- return true
- }
- }
- return false
- }
- override var state: AnimStates = AnimStates.Gone
- var currentState: AnimStates = AnimStates.Gone
- override val modifier: Modifier = Modifier.composed {
- SlideModifier()
- }
-
- inner class SlideModifier : LayoutModifier {
- override fun MeasureScope.measure(
- measurable: Measurable,
- constraints: Constraints
- ): MeasureResult {
- val placeable = measurable.measure(constraints)
-
- updateAnimation(IntSize(placeable.width, placeable.height))
- return layout(placeable.width, placeable.height) {
- placeable.place(slideAnim?.value ?: IntOffset.Zero)
- }
- }
- }
-
- fun updateAnimation(fullSize: IntSize) {
- if (state == currentState) {
- return
- }
- // state changed
- if (state == AnimStates.Entering) {
- // Animation is interrupted from slide out, now slide in
- enter?.apply {
- // If slide in animation specified, use that. Otherwise use default.
- val anim = if (slideAnim?.isRunning != true) {
- Animatable(
- slideOffset(fullSize), IntOffset.VectorConverter, IntOffset(1, 1)
- )
- } else {
- slideAnim
- }
- scope.launch {
- anim!!.animateTo(IntOffset.Zero, animationSpec)
- listener(AnimationEndReason.Finished, anim.value)
- }
- slideAnim = anim
- } ?: slideAnim?.also {
- scope.launch {
- it.animateTo(IntOffset.Zero)
- listener(AnimationEndReason.Finished, it.value)
- }
- }
- } else if (state == AnimStates.Exiting) {
- // interrupting alpha animation: directly animating to out value if defined,
- // otherwise let it finish
- exit?.apply {
- val anim = slideAnim
- ?: Animatable(
- IntOffset.Zero, IntOffset.VectorConverter, IntOffset(1, 1)
- )
- scope.launch {
- anim.animateTo(slideOffset(fullSize), animationSpec)
- listener(AnimationEndReason.Finished, anim.value)
- }
- slideAnim = anim
- }
- }
- currentState = state
- }
-
- var slideAnim: Animatable<IntOffset, AnimationVector2D>? = null
-}
-
-private class ChangeSizeTransition(
- val enter: ChangeSize? = null,
- val exit: ChangeSize? = null,
- val scope: CoroutineScope,
- override val listener: (AnimationEndReason, Any) -> Unit
-) : TransitionAnimation {
-
- override val isRunning: Boolean
- get() {
- if (sizeAnim?.isAnimating == true) {
- return true
- }
-
- // If the state has changed, and corresponding animations are defined, then animation
- // will be running in this current frame in the layout stage.
- if (state != currentState) {
- if (state == AnimStates.Entering && enter != null) {
- return true
- } else if (state == AnimStates.Exiting && exit != null) {
- return true
- }
- }
- return false
- }
-
- // This is the pending state, which sets currentState in layout stage
- override var state: AnimStates = AnimStates.Gone
-
- // This tracks the current resolved state. State change happens in composition, but the
- // resolution happens during layout, since we won't know the size until then.
- var currentState: AnimStates = AnimStates.Gone
-
- override fun getAnimatedSize(fullSize: IntSize): Pair<IntOffset, IntSize> {
- sizeAnim?.apply {
- if (state == currentState) {
- // If no state change, return the current size animation value.
- if (state == AnimStates.Entering) {
- animateTo(fullSize, alignment, fullSize, spring(), scope)
- } else if (state == AnimStates.Visible) {
- return snapTo(fullSize, scope) to fullSize
- }
- return offset(fullSize) to size
- }
- }
-
- // If we get here, animate state has changed.
- if (state == AnimStates.Entering) {
- if (enter != null) {
- // if no on-going size animation, create a new one.
- val anim = sizeAnim?.run {
- // If the animation is not running and the alignment isn't the same, prefer
- // AlignmentBasedSizeAnimation over rect based animation.
- if (!isAnimating) {
- null
- } else {
- this
- }
- } ?: AlignmentBasedSizeAnimation(
- Animatable(
- enter.startSize.invoke(fullSize),
- IntSize.VectorConverter,
- visibilityThreshold = IntSize(1, 1)
- ),
- enter.alignment, enter.clip, listener
- )
- // Animate to full size
- sizeAnim = anim.animateTo(
- fullSize, enter.alignment, fullSize, enter.animationSpec, scope
- )
- } else {
- // If enter isn't defined for size change, re-target the current animation, if any
- sizeAnim?.apply {
- animateTo(fullSize, alignment, fullSize, spring(), scope)
- }
- }
- } else if (state == AnimStates.Exiting) {
- exit?.apply {
- // If a size change exit animation is defined, re-target on-going animation if
- // any, otherwise create a new one.
- val anim = sizeAnim?.run {
- // If the current size animation is idling, switch to AlignmentBasedAnimation if
- // needed.
- if (isRunning && alignment != exit.alignment) {
- null
- } else {
- this
- }
- } ?: AlignmentBasedSizeAnimation(
- Animatable(fullSize, IntSize.VectorConverter, IntSize(1, 1)),
- alignment, clip, listener
- )
-
- sizeAnim = anim.animateTo(
- startSize(fullSize), alignment, fullSize, animationSpec, scope
- )
- }
- // If exit isn't defined, but the enter animation is still on-going, let it finish
- }
- currentState = state
- return sizeAnim?.run { offset(fullSize) to size } ?: IntOffset.Zero to fullSize
- }
-
- override val modifier: Modifier
- get() {
- val clip: Boolean = sizeAnim?.clip
- ?: if (state == AnimStates.Entering) {
- enter?.clip
- } else {
- exit?.clip
- } ?: false
- return if (clip) Modifier.clipToBounds() else Modifier
- }
-
- var sizeAnim: SizeAnimation? = null
-}
-
-@OptIn(ExperimentalAnimationApi::class)
-internal class TransitionAnimations constructor(
+@SuppressLint("ModifierFactoryExtensionFunction", "ComposableModifierFactory")
+@Composable
+internal fun Transition<EnterExitState>.createModifier(
enter: EnterTransition,
- exit: ExitTransition,
- scope: CoroutineScope,
- onFinished: () -> Unit
-) {
- // This happens during composition.
- fun updateState(state: AnimStates) {
- animations.fastForEach { it.state = state }
- }
+ exit: ExitTransition
+): Modifier {
- val listener: (AnimationEndReason, Any) -> Unit = { reason, _ ->
- if (reason == AnimationEndReason.Finished && !isAnimating) {
- onFinished()
+ // Generates up to 3 modifiers, one for each type of enter/exit transition in the order:
+ // slide then shrink/expand then alpha.
+ var modifier: Modifier = Modifier
+
+ modifier = modifier.slideInOut(
+ this,
+ rememberUpdatedState(enter.data.slide),
+ rememberUpdatedState(exit.data.slide)
+ ).shrinkExpand(
+ this,
+ rememberUpdatedState(enter.data.changeSize),
+ rememberUpdatedState(exit.data.changeSize)
+ )
+
+ // Fade - it's important to put fade in the end. Otherwise fade will clip slide.
+ // We'll animate if at any point during the transition fadeIn/fadeOut becomes non-null. This
+ // would ensure the removal of fadeIn/Out amid a fade animation doesn't result in a jump.
+ var shouldAnimateAlpha by remember(this) { mutableStateOf(false) }
+ if (currentState == targetState) {
+ shouldAnimateAlpha = false
+ } else {
+ if (enter.data.fade != null || exit.data.fade != null) {
+ shouldAnimateAlpha = true
}
}
- // This is called after measure before placement.
- fun getAnimatedSize(fullSize: IntSize): Pair<IntOffset, IntSize>? {
- animations.fastForEach {
- val animSize = it.getAnimatedSize(fullSize)
- if (animSize != null) {
- return animSize
+ if (shouldAnimateAlpha) {
+ val alpha by animateFloat(
+ transitionSpec = {
+ when {
+ EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible ->
+ enter.data.fade?.animationSpec ?: spring()
+ EnterExitState.Visible isTransitioningTo EnterExitState.PostExit ->
+ exit.data.fade?.animationSpec ?: spring()
+ else -> spring()
+ }
+ },
+ label = "alpha"
+ ) {
+ when (it) {
+ EnterExitState.Visible -> 1f
+ EnterExitState.PreEnter -> enter.data.fade?.alpha ?: 1f
+ EnterExitState.PostExit -> exit.data.fade?.alpha ?: 1f
}
}
- return null
+ modifier = modifier.graphicsLayer {
+ this.alpha = alpha
+ }
}
+ return modifier
+}
- val isAnimating: Boolean
- get() = animations.fastFirstOrNull { it.isRunning }?.isRunning ?: false
-
- val animations: List<TransitionAnimation>
-
- init {
- animations = mutableListOf()
- // Only set up animations when either enter or exit transition is defined.
- if (enter.data.slide != null || exit.data.slide != null) {
- animations.add(
- SlideTransition(enter.data.slide, exit.data.slide, scope, listener)
- )
- }
- if (enter.data.changeSize != null || exit.data.changeSize != null) {
- animations.add(
- ChangeSizeTransition(enter.data.changeSize, exit.data.changeSize, scope, listener)
- )
- }
- if (enter.data.fade != null || exit.data.fade != null) {
- animations.add(
- FadeTransition(enter.data.fade, exit.data.fade, scope, listener)
- )
+@SuppressLint("ModifierInspectorInfo")
+private fun Modifier.slideInOut(
+ transition: Transition<EnterExitState>,
+ slideIn: State<Slide?>,
+ slideOut: State<Slide?>
+): Modifier = composed {
+ // We'll animate if at any point during the transition slideIn/slideOut becomes non-null. This
+ // would ensure the removal of slideIn/Out amid a slide animation doesn't result in a jump.
+ var shouldAnimate by remember(transition) { mutableStateOf(false) }
+ if (transition.currentState == transition.targetState) {
+ shouldAnimate = false
+ } else {
+ if (slideIn.value != null || slideOut.value != null) {
+ shouldAnimate = true
}
}
- val modifier: Modifier
- get() {
- var modifier: Modifier = Modifier
- animations.fastForEach { modifier = modifier.then(it.modifier) }
- return modifier
+ if (shouldAnimate) {
+ val animation = transition.createDeferredAnimation(IntOffset.VectorConverter, "slide")
+ val modifier = remember(transition) {
+ SlideModifier(animation, slideIn, slideOut)
}
+ this.then(modifier)
+ } else {
+ this
+ }
+}
+
+private val defaultOffsetAnimationSpec = spring(visibilityThreshold = IntOffset.VisibilityThreshold)
+
+private class SlideModifier(
+ val lazyAnimation: Transition<EnterExitState>.DeferredAnimation<IntOffset, AnimationVector2D>,
+ val slideIn: State<Slide?>,
+ val slideOut: State<Slide?>
+) : LayoutModifier {
+ val transitionSpec: Transition.Segment<EnterExitState>.() -> FiniteAnimationSpec<IntOffset> =
+ {
+ when {
+ EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible -> {
+ slideIn.value?.animationSpec ?: defaultOffsetAnimationSpec
+ }
+ EnterExitState.Visible isTransitioningTo EnterExitState.PostExit -> {
+ slideOut.value?.animationSpec ?: defaultOffsetAnimationSpec
+ }
+ else -> defaultOffsetAnimationSpec
+ }
+ }
+
+ fun targetValueByState(targetState: EnterExitState, fullSize: IntSize): IntOffset {
+ val preEnter = slideIn.value?.slideOffset?.invoke(fullSize) ?: IntOffset.Zero
+ val postExit = slideOut.value?.slideOffset?.invoke(fullSize) ?: IntOffset.Zero
+ return when (targetState) {
+ EnterExitState.Visible -> IntOffset.Zero
+ EnterExitState.PreEnter -> preEnter
+ EnterExitState.PostExit -> postExit
+ }
+ }
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ val placeable = measurable.measure(constraints)
+
+ val measuredSize = IntSize(placeable.width, placeable.height)
+ return layout(placeable.width, placeable.height) {
+ val slideOffset = lazyAnimation.animate(
+ transitionSpec
+ ) {
+ targetValueByState(it, measuredSize)
+ }
+ placeable.placeWithLayer(slideOffset.value)
+ }
+ }
+}
+
+@SuppressLint("ModifierInspectorInfo")
+private fun Modifier.shrinkExpand(
+ transition: Transition<EnterExitState>,
+ expand: State<ChangeSize?>,
+ shrink: State<ChangeSize?>
+): Modifier = composed {
+ // We'll animate if at any point during the transition shrink/expand becomes non-null. This
+ // would ensure the removal of shrink/expand amid a size change animation doesn't result in a
+ // jump.
+ var shouldAnimate by remember(transition) { mutableStateOf(false) }
+ if (transition.currentState == transition.targetState) {
+ shouldAnimate = false
+ } else {
+ if (expand.value != null || shrink.value != null) {
+ shouldAnimate = true
+ }
+ }
+
+ if (shouldAnimate) {
+ val alignment: State<Alignment?> = rememberUpdatedState(
+ with(transition.segment) {
+ EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible
+ }.let {
+ if (it) {
+ expand.value?.alignment ?: shrink.value?.alignment
+ } else {
+ shrink.value?.alignment ?: expand.value?.alignment
+ }
+ }
+ )
+ val sizeAnimation = transition.createDeferredAnimation(
+ IntSize.VectorConverter,
+ "shrink/expand"
+ )
+ val offsetAnimation = key(transition.currentState == transition.targetState) {
+ transition.createDeferredAnimation(
+ IntOffset.VectorConverter,
+ "InterruptionHandlingOffset"
+ )
+ }
+
+ val expandShrinkModifier = remember(transition) {
+ ExpandShrinkModifier(
+ sizeAnimation,
+ offsetAnimation,
+ expand,
+ shrink,
+ alignment
+ )
+ }
+
+ if (transition.currentState == transition.targetState) {
+ expandShrinkModifier.currentAlignment = null
+ } else if (expandShrinkModifier.currentAlignment == null) {
+ expandShrinkModifier.currentAlignment = alignment.value ?: Alignment.TopStart
+ }
+ this.clipToBounds().then(expandShrinkModifier)
+ } else {
+ this
+ }
+}
+
+private val defaultSizeAnimationSpec = spring(visibilityThreshold = IntSize.VisibilityThreshold)
+
+private class ExpandShrinkModifier(
+ val sizeAnimation: Transition<EnterExitState>.DeferredAnimation<IntSize, AnimationVector2D>,
+ val offsetAnimation: Transition<EnterExitState>.DeferredAnimation<IntOffset,
+ AnimationVector2D>,
+ val expand: State<ChangeSize?>,
+ val shrink: State<ChangeSize?>,
+ val alignment: State<Alignment?>
+) : LayoutModifier {
+ var currentAlignment: Alignment? = null
+ val sizeTransitionSpec: Transition.Segment<EnterExitState>.() -> FiniteAnimationSpec<IntSize> =
+ {
+ when {
+ EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible ->
+ expand.value?.animationSpec
+ EnterExitState.Visible isTransitioningTo EnterExitState.PostExit ->
+ shrink.value?.animationSpec
+ else -> defaultSizeAnimationSpec
+ } ?: defaultSizeAnimationSpec
+ }
+
+ fun sizeByState(targetState: EnterExitState, fullSize: IntSize): IntSize {
+ val preEnterSize = expand.value?.let { it.size(fullSize) } ?: fullSize
+ val postExitSize = shrink.value?.let { it.size(fullSize) } ?: fullSize
+
+ return when (targetState) {
+ EnterExitState.Visible -> fullSize
+ EnterExitState.PreEnter -> preEnterSize
+ EnterExitState.PostExit -> postExitSize
+ }
+ }
+
+ // This offset is only needed when the alignment value changes during the shrink/expand
+ // animation. For example, if user specify an enter that expands from the left, and an exit
+ // that shrinks towards the right, the asymmetric enter/exit will be brittle to interruption.
+ // Hence the following offset animation to smooth over such interruption.
+ fun targetOffsetByState(targetState: EnterExitState, fullSize: IntSize): IntOffset =
+ when {
+ currentAlignment == null -> IntOffset.Zero
+ alignment.value == null -> IntOffset.Zero
+ currentAlignment == alignment.value -> IntOffset.Zero
+ else -> when (targetState) {
+ EnterExitState.Visible -> IntOffset.Zero
+ EnterExitState.PreEnter -> IntOffset.Zero
+ EnterExitState.PostExit -> shrink.value?.let {
+ val endSize = it.size(fullSize)
+ val targetOffset = alignment.value!!.align(
+ fullSize,
+ endSize,
+ LayoutDirection.Ltr
+ )
+ val currentOffset = currentAlignment!!.align(
+ fullSize,
+ endSize,
+ LayoutDirection.Ltr
+ )
+ targetOffset - currentOffset
+ } ?: IntOffset.Zero
+ }
+ }
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ val placeable = measurable.measure(constraints)
+
+ val measuredSize = IntSize(placeable.width, placeable.height)
+ val currentSize = sizeAnimation.animate(sizeTransitionSpec) {
+ sizeByState(it, measuredSize)
+ }.value
+
+ val offsetDelta = offsetAnimation.animate({ defaultOffsetAnimationSpec }) {
+ targetOffsetByState(it, measuredSize)
+ }.value
+
+ val offset =
+ currentAlignment?.align(measuredSize, currentSize, LayoutDirection.Ltr)
+ ?: IntOffset.Zero
+ return layout(currentSize.width, currentSize.height) {
+ placeable.place(offset.x + offsetDelta.x, offset.y + offsetDelta.y)
+ }
+ }
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
index 2bb31eb..5e11763 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
@@ -154,13 +154,13 @@
.scrollBy(y = 103.dp, density = rule.density)
rule.onNodeWithTag("1")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("2")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("3")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("4")
.assertIsDisplayed()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
index 7f44d31..8604782 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
@@ -89,7 +89,7 @@
Modifier.requiredSize(20.dp),
state = rememberLazyListState().also { state = it }
) {
- items((0..1).toList()) {
+ items((0..10).toList()) {
if (it == 0) {
realState = rememberSaveable { counter0++ }
DisposableEffect(Unit) {
@@ -106,8 +106,11 @@
rule.runOnIdle {
assertThat(realState).isEqualTo(1)
runBlocking {
- state.scrollToItem(1, 5)
- state.scrollToItem(1, 6)
+ // we scroll through multiple items to make sure the 0th element is not kept in
+ // the reusable items buffer
+ state.scrollToItem(3)
+ state.scrollToItem(5)
+ state.scrollToItem(8)
}
}
@@ -185,7 +188,7 @@
Modifier.requiredSize(20.dp),
state = rememberLazyListState().also { state = it }
) {
- items((0..1).toList()) {
+ items((0..10).toList()) {
if (it == 0) {
LazyRow {
item {
@@ -208,7 +211,11 @@
rule.runOnIdle {
assertThat(realState).isEqualTo(1)
runBlocking {
- state.scrollToItem(1, 5)
+ // we scroll through multiple items to make sure the 0th element is not kept in
+ // the reusable items buffer
+ state.scrollToItem(3)
+ state.scrollToItem(5)
+ state.scrollToItem(8)
}
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt
index 276a499..f50f8e7 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
@@ -162,7 +163,7 @@
.scrollBy(y = 105.dp, density = rule.density)
rule.onNodeWithTag(firstHeaderTag)
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag(secondHeaderTag)
.assertIsDisplayed()
@@ -289,7 +290,7 @@
.scrollBy(x = 105.dp, density = rule.density)
rule.onNodeWithTag(firstHeaderTag)
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag(secondHeaderTag)
.assertIsDisplayed()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListSlotsReuseTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListSlotsReuseTest.kt
new file mode 100644
index 0000000..8f626d0
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListSlotsReuseTest.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class LazyListSlotsReuseTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ val itemsSizePx = 30f
+ val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
+
+ @Test
+ fun scroll1ItemScrolledOffItemIsKeptForReuse() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag("0")
+ .assertIsDisplayed()
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(1)
+ }
+ }
+
+ rule.onNodeWithTag("0")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun scroll2ItemsScrolledOffItemsAreKeptForReuse() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag("0")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(2)
+ }
+ }
+
+ rule.onNodeWithTag("0")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("1")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("2")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun scroll3Items2OfScrolledOffItemsAreKeptForReuse() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag("0")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+
+ rule.runOnIdle {
+ runBlocking {
+ // after this step 0 and 1 are in reusable buffer
+ state.scrollToItem(2)
+
+ // this step requires one item and will take the last item from the buffer - item
+ // 1 plus will put 2 in the buffer. so expected buffer is items 2 and 0
+ state.scrollToItem(3)
+ }
+ }
+
+ // recycled
+ rule.onNodeWithTag("1")
+ .assertDoesNotExist()
+
+ // in buffer
+ rule.onNodeWithTag("0")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("2")
+ .assertExists()
+ .assertIsNotDisplayed()
+
+ // visible
+ rule.onNodeWithTag("3")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("4")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun doMultipleScrollsOneByOne() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(1) // buffer is [0]
+ state.scrollToItem(2) // 0 used, buffer is [1]
+ state.scrollToItem(3) // 1 used, buffer is [2]
+ state.scrollToItem(4) // 2 used, buffer is [3]
+ }
+ }
+
+ // recycled
+ rule.onNodeWithTag("0")
+ .assertDoesNotExist()
+ rule.onNodeWithTag("1")
+ .assertDoesNotExist()
+ rule.onNodeWithTag("2")
+ .assertDoesNotExist()
+
+ // in buffer
+ rule.onNodeWithTag("3")
+ .assertExists()
+ .assertIsNotDisplayed()
+
+ // visible
+ rule.onNodeWithTag("4")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("5")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun scrollBackwardOnce() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState(10)
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(8) // buffer is [10, 11]
+ }
+ }
+
+ // in buffer
+ rule.onNodeWithTag("10")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("11")
+ .assertExists()
+ .assertIsNotDisplayed()
+
+ // visible
+ rule.onNodeWithTag("8")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("9")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun scrollBackwardOneByOne() {
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState(10)
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(9) // buffer is [11]
+ state.scrollToItem(7) // 11 reused, buffer is [9]
+ state.scrollToItem(6) // 9 reused, buffer is [8]
+ }
+ }
+
+ // in buffer
+ rule.onNodeWithTag("8")
+ .assertExists()
+ .assertIsNotDisplayed()
+
+ // visible
+ rule.onNodeWithTag("6")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("7")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun scrollingBackReusesTheSameSlot() {
+ lateinit var state: LazyListState
+ var counter0 = 0
+ var counter1 = 10
+ var rememberedValue0 = -1
+ var rememberedValue1 = -1
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.height(itemsSizeDp * 1.5f),
+ state
+ ) {
+ items(100) {
+ if (it == 0) {
+ rememberedValue0 = remember { counter0++ }
+ }
+ if (it == 1) {
+ rememberedValue1 = remember { counter1++ }
+ }
+ Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+ }
+ }
+ }
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(2) // buffer is [0, 1]
+ state.scrollToItem(0) // scrolled back, 0 and 1 are reused back. buffer: [2, 3]
+ }
+ }
+
+ rule.runOnIdle {
+ Truth.assertWithMessage("Item 0 restored remembered value is $rememberedValue0")
+ .that(rememberedValue0).isEqualTo(0)
+ Truth.assertWithMessage("Item 1 restored remembered value is $rememberedValue1")
+ .that(rememberedValue1).isEqualTo(10)
+ }
+
+ rule.onNodeWithTag("0")
+ .assertIsDisplayed()
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("2")
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag("3")
+ .assertExists()
+ .assertIsNotDisplayed()
+ }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
index f4d511a..31062be 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
@@ -237,7 +237,7 @@
.scrollBy(x = 105.dp, density = rule.density)
rule.onNodeWithTag("1")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("2")
.assertIsDisplayed()
@@ -264,7 +264,7 @@
.scrollBy(x = 150.dp, density = rule.density)
rule.onNodeWithTag("1")
- .assertDoesNotExist()
+ .assertIsNotDisplayed()
rule.onNodeWithTag("2")
.assertIsDisplayed()
@@ -524,10 +524,12 @@
@Test
fun scrollsLeftInRtl() {
+ lateinit var state: LazyListState
rule.setContentWithTestViewConfiguration {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
Box(Modifier.width(100.dp)) {
- LazyRow(Modifier.testTag(LazyListTag)) {
+ state = rememberLazyListState()
+ LazyRow(Modifier.testTag(LazyListTag), state) {
items(4) {
Spacer(
Modifier.width(101.dp).fillParentMaxHeight().testTag("$it")
@@ -541,11 +543,10 @@
rule.onNodeWithTag(LazyListTag)
.scrollBy(x = (-150).dp, density = rule.density)
- rule.onNodeWithTag("0")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+ assertThat(state.firstVisibleItemScrollOffset).isGreaterThan(0)
+ }
}
@Test
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index acca6c2..141341d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -70,7 +70,7 @@
val itemContentFactory = rememberItemContentFactory(stateOfItemsProvider, state)
- val subcomposeLayoutState = remember { SubcomposeLayoutState() }
+ val subcomposeLayoutState = remember { SubcomposeLayoutState(MaxItemsToRetainForReuse) }
LazyListPrefetcher(state, stateOfItemsProvider, itemContentFactory, subcomposeLayoutState)
SubcomposeLayout(
@@ -184,6 +184,8 @@
}
}
+private const val MaxItemsToRetainForReuse = 2
+
/**
* Platform specific implementation of lazy list prefetching - precomposing next items in
* advance during the scrolling.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
index f32e896..19d0e06 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
@@ -100,8 +100,11 @@
val content: @Composable () -> Unit = @Composable {
val itemsProvider = itemsProvider.value
if (index < itemsProvider.itemsCount) {
- val content = itemsProvider.getContent(index, scope)
- saveableStateHolder.SaveableStateProvider(key, content)
+ val key = itemsProvider.getKey(index)
+ if (key == this.key) {
+ val content = itemsProvider.getContent(index, scope)
+ saveableStateHolder.SaveableStateProvider(key, content)
+ }
}
}
}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
new file mode 100644
index 0000000..566c2ab
--- /dev/null
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2021 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.
+ */
+
+// ktlint-disable indent https://github.com/pinterest/ktlint/issues/967
+// Ignore lint warnings in documentation snippets
+@file:Suppress(
+ "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
+ "RedundantSuspendModifier", "CascadeIf", "ClassName", "RemoveExplicitTypeArguments",
+ "ControlFlowWithEmptyBody", "PropertyName", "CanBeParameter"
+)
+
+package androidx.compose.integration.docs.interoperability
+
+import android.content.Context
+import android.os.Bundle
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.ComposeView
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+
+/**
+ * This file lets DevRel track changes to snippets present in
+ * https://developer.android.com/jetpack/compose/interop/compose-in-existing-arch
+ *
+ * No action required if it's modified.
+ */
+
+private object InteropArchitectureSnippet1 {
+ class ExampleActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ MaterialTheme {
+ Column {
+ Greeting("user1")
+ Greeting("user2")
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun Greeting(userId: String) {
+ val greetingViewModel: GreetingViewModel = viewModel(
+ factory = GreetingViewModelFactory(userId)
+ )
+ val messageUser by greetingViewModel.message.observeAsState("")
+
+ Text(messageUser)
+ }
+
+ class GreetingViewModel(private val userId: String) : ViewModel() {
+ private val _message = MutableLiveData("Hi $userId")
+ val message: LiveData<String> = _message
+ }
+
+ class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+ return GreetingViewModel(userId) as T
+ }
+ }
+}
+
+private object InteropArchitectureSnippet2 {
+ @Composable
+ fun MyScreen() {
+ NavHost(rememberNavController(), startDestination = "profile/{userId}") {
+ /* ... */
+ composable("profile/{userId}") { backStackEntry ->
+ Greeting(backStackEntry.arguments?.getString("userId") ?: "")
+ }
+ }
+ }
+
+ @Composable
+ fun Greeting(userId: String) {
+ val greetingViewModel: GreetingViewModel = viewModel(
+ factory = GreetingViewModelFactory(userId)
+ )
+ val messageUser by greetingViewModel.message.observeAsState("")
+
+ Text(messageUser)
+ }
+}
+
+private object InteropArchitectureSnippet3 {
+ @Composable
+ fun BackHandler(
+ enabled: Boolean,
+ backDispatcher: OnBackPressedDispatcher,
+ onBack: () -> Unit
+ ) {
+
+ // Safely update the current `onBack` lambda when a new one is provided
+ val currentOnBack by rememberUpdatedState(onBack)
+
+ // Remember in Composition a back callback that calls the `onBack` lambda
+ val backCallback = remember {
+ // Always intercept back events. See the SideEffect for a more complete version
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ currentOnBack()
+ }
+ }
+ }
+
+ // On every successful composition, update the callback with the `enabled` value
+ // to tell `backCallback` whether back events should be intercepted or not
+ SideEffect {
+ backCallback.isEnabled = enabled
+ }
+
+ // If `backDispatcher` changes, dispose and reset the effect
+ DisposableEffect(backDispatcher) {
+ // Add callback to the backDispatcher
+ backDispatcher.addCallback(backCallback)
+
+ // When the effect leaves the Composition, remove the callback
+ onDispose {
+ backCallback.remove()
+ }
+ }
+ }
+}
+
+private object InteropArchitectureSnippet4 {
+ class CustomViewGroup @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+ ) : LinearLayout(context, attrs, defStyle) {
+
+ // Source of truth in the View system as mutableStateOf
+ // to make it thread-safe for Compose
+ private var text by mutableStateOf("")
+
+ private val textView: TextView
+
+ init {
+ orientation = VERTICAL
+
+ textView = TextView(context)
+ val composeView = ComposeView(context).apply {
+ setContent {
+ MaterialTheme {
+ TextField(value = text, onValueChange = { updateState(it) })
+ }
+ }
+ }
+
+ addView(textView)
+ addView(composeView)
+ }
+
+ // Update both the source of truth and the TextView
+ private fun updateState(newValue: String) {
+ text = newValue
+ textView.text = newValue
+ }
+ }
+}
+
+/*
+Fakes needed for snippets to build:
+ */
+
+private class GreetingViewModel(userId: String) : ViewModel() {
+ val _message = MutableLiveData("")
+ val message: LiveData<String> = _message
+}
+private class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+ return GreetingViewModel(userId) as T
+ }
+}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
new file mode 100644
index 0000000..5dc4770
--- /dev/null
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2021 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.
+ */
+
+// ktlint-disable indent https://github.com/pinterest/ktlint/issues/967
+// Ignore lint warnings in documentation snippets
+@file:Suppress(
+ "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
+ "RedundantSuspendModifier", "CascadeIf", "ClassName", "RemoveExplicitTypeArguments",
+ "ControlFlowWithEmptyBody", "PropertyName", "CanBeParameter", "PackageDirectoryMismatch"
+)
+
+package androidx.compose.integration.docs.interoperabilityui
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.FloatingActionButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.AbstractComposeView
+import androidx.compose.ui.unit.dp
+
+/**
+ * This file lets DevRel track changes to snippets present in
+ * https://developer.android.com/jetpack/compose/interop/compose-in-existing-ui
+ *
+ * No action required if it's modified.
+ */
+
+private object InteropUiSnippet1 {
+ @Composable
+ fun CallToActionButton(
+ text: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ ) {
+ Button(
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = MaterialTheme.colors.secondary
+ ),
+ onClick = onClick,
+ modifier = modifier,
+ ) {
+ Text(text)
+ }
+ }
+
+ class CallToActionViewButton @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+ ) : AbstractComposeView(context, attrs, defStyle) {
+
+ var text by mutableStateOf<String>("")
+ var onClick by mutableStateOf<() -> Unit>({})
+
+ @Composable
+ override fun Content() {
+ YourAppTheme {
+ CallToActionButton(text, onClick)
+ }
+ }
+ }
+}
+
+private object InteropUiSnippet2 {
+ class ExampleActivity : Activity() {
+
+ private lateinit var binding: ActivityExampleBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityExampleBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ binding.callToAction.apply {
+ text = getString(R.string.something)
+ onClick = { /* Do something */ }
+ }
+ }
+ }
+}
+
+private object InteropUiSnippet3 {
+ // import com.google.android.material.composethemeadapter.MdcTheme
+
+ class ExampleActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ // Use MdcTheme instead of MaterialTheme
+ // Colors, typography, and shape have been read from the
+ // View-based theme used in this Activity
+ MdcTheme {
+ ExampleComposable(/*...*/)
+ }
+ }
+ }
+ }
+}
+
+private object InteropUiSnippet4 {
+ class ExampleActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ AppCompatTheme {
+ // Colors, typography, and shape have been read from the
+ // View-based theme used in this Activity
+ ExampleComposable(/*...*/)
+ }
+ }
+ }
+ }
+}
+
+private object InteropUiSnippet5 {
+ class ExampleActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ setContent {
+ MaterialTheme {
+ ProvideWindowInsets {
+ MyScreen()
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun MyScreen() {
+ Box {
+ FloatingActionButton(
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ .padding(16.dp) // normal 16dp of padding for FABs
+ .navigationBarsPadding(), // Move it out from under the nav bar
+ onClick = { }
+ ) {
+ Icon( /* ... */)
+ }
+ }
+ }
+}
+
+private object InteropUiSnippet6 {
+ @Composable
+ fun MyComposable() {
+ BoxWithConstraints {
+ if (minWidth < 480.dp) {
+ /* Show grid with 4 columns */
+ } else if (minWidth < 720.dp) {
+ /* Show grid with 8 columns */
+ } else {
+ /* Show grid with 12 columns */
+ }
+ }
+ }
+}
+
+/*
+Fakes needed for snippets to build:
+ */
+
+private object R {
+ object string {
+ const val something = 1
+ }
+}
+
+private fun ExampleComposable() {}
+@Composable
+private fun MdcTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun AppCompatTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun BlueTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun PinkTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun YourAppTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun ProvideWindowInsets(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun Icon() {
+}
+
+private class WindowCompat {
+ companion object {
+ fun setDecorFitsSystemWindows(window: Any, bool: Boolean) {}
+ }
+}
+
+private fun Modifier.navigationBarsPadding(): Modifier = this
+
+private class ActivityExampleBinding {
+ val root: Int = 0
+ lateinit var callToAction: InteropUiSnippet1.CallToActionViewButton
+ companion object {
+ fun inflate(li: LayoutInflater): ActivityExampleBinding { TODO() }
+ }
+}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
index ab8da92..82f2c4a 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
@@ -24,7 +24,6 @@
package androidx.compose.integration.docs.interoperability
-import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -32,58 +31,36 @@
import android.graphics.Bitmap
import android.graphics.Color
import android.os.Bundle
-import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.activity.OnBackPressedCallback
-import androidx.activity.OnBackPressedDispatcher
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
import androidx.compose.integration.docs.databinding.ExampleLayoutBinding
import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.FloatingActionButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
-import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.viewinterop.AndroidViewBinding
-import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
/**
* This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/interop
+ * https://developer.android.com/jetpack/compose/interop/interop-apis
*
* No action required if it's modified.
*/
@@ -251,274 +228,6 @@
}
}
-private object InteropSnippet9 {
- // import com.google.android.material.composethemeadapter.MdcTheme
-
- class ExampleActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContent {
- // Use MdcTheme instead of MaterialTheme
- // Colors, typography, and shape have been read from the
- // View-based theme used in this Activity
- MdcTheme {
- ExampleComposable(/*...*/)
- }
- }
- }
- }
-}
-
-private object InteropSnippet10 {
- class ExampleActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContent {
- AppCompatTheme {
- // Colors, typography, and shape have been read from the
- // View-based theme used in this Activity
- ExampleComposable(/*...*/)
- }
- }
- }
- }
-}
-
-private object InteropSnippet11 {
- class ExampleActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- WindowCompat.setDecorFitsSystemWindows(window, false)
-
- setContent {
- MaterialTheme {
- ProvideWindowInsets {
- MyScreen()
- }
- }
- }
- }
- }
-
- @Composable
- fun MyScreen() {
- Box {
- FloatingActionButton(
- modifier = Modifier
- .align(Alignment.BottomEnd)
- .padding(16.dp) // normal 16dp of padding for FABs
- .navigationBarsPadding(), // Move it out from under the nav bar
- onClick = { }
- ) {
- Icon( /* ... */)
- }
- }
- }
-}
-
-private object InteropSnippet12 {
- @Composable
- fun MyComposable() {
- BoxWithConstraints {
- if (minWidth < 480.dp) {
- /* Show grid with 4 columns */
- } else if (minWidth < 720.dp) {
- /* Show grid with 8 columns */
- } else {
- /* Show grid with 12 columns */
- }
- }
- }
-}
-
-private object InteropSnippet13 {
- class ExampleActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContent {
- MaterialTheme {
- Column {
- Greeting("user1")
- Greeting("user2")
- }
- }
- }
- }
- }
-
- @Composable
- fun Greeting(userId: String) {
- val greetingViewModel: GreetingViewModel = viewModel(
- factory = GreetingViewModelFactory(userId)
- )
- val messageUser by greetingViewModel.message.observeAsState("")
-
- Text(messageUser)
- }
-
- class GreetingViewModel(private val userId: String) : ViewModel() {
- private val _message = MutableLiveData("Hi $userId")
- val message: LiveData<String> = _message
- }
-}
-
-private object InteropSnippet14 {
- @Composable
- fun MyScreen() {
- NavHost(rememberNavController(), startDestination = "profile/{userId}") {
- /* ... */
- composable("profile/{userId}") { backStackEntry ->
- Greeting(backStackEntry.arguments?.getString("userId") ?: "")
- }
- }
- }
-
- @Composable
- fun Greeting(userId: String) {
- val greetingViewModel: GreetingViewModel = viewModel(
- factory = GreetingViewModelFactory(userId)
- )
- val messageUser by greetingViewModel.message.observeAsState("")
-
- Text(messageUser)
- }
-}
-
-private object InteropSnippet15 {
- @Composable
- fun BackHandler(
- enabled: Boolean,
- backDispatcher: OnBackPressedDispatcher,
- onBack: () -> Unit
- ) {
-
- // Safely update the current `onBack` lambda when a new one is provided
- val currentOnBack by rememberUpdatedState(onBack)
-
- // Remember in Composition a back callback that calls the `onBack` lambda
- val backCallback = remember {
- // Always intercept back events. See the SideEffect for a more complete version
- object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- currentOnBack()
- }
- }
- }
-
- // On every successful composition, update the callback with the `enabled` value
- // to tell `backCallback` whether back events should be intercepted or not
- SideEffect {
- backCallback.isEnabled = enabled
- }
-
- // If `backDispatcher` changes, dispose and reset the effect
- DisposableEffect(backDispatcher) {
- // Add callback to the backDispatcher
- backDispatcher.addCallback(backCallback)
-
- // When the effect leaves the Composition, remove the callback
- onDispose {
- backCallback.remove()
- }
- }
- }
-}
-
-private object InteropSnippet16 {
- class CustomViewGroup @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyle: Int = 0
- ) : LinearLayout(context, attrs, defStyle) {
-
- // Source of truth in the View system as mutableStateOf
- // to make it thread-safe for Compose
- private var text by mutableStateOf("")
-
- private val textView: TextView
-
- init {
- orientation = VERTICAL
-
- textView = TextView(context)
- val composeView = ComposeView(context).apply {
- setContent {
- MaterialTheme {
- TextField(value = text, onValueChange = { updateState(it) })
- }
- }
- }
-
- addView(textView)
- addView(composeView)
- }
-
- // Update both the source of truth and the TextView
- private fun updateState(newValue: String) {
- text = newValue
- textView.text = newValue
- }
- }
-}
-
-private object InteropSnippet17 {
- @Composable
- fun CallToActionButton(
- text: String,
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
- ) {
- Button(
- colors = ButtonDefaults.buttonColors(
- backgroundColor = MaterialTheme.colors.secondary
- ),
- onClick = onClick,
- modifier = modifier,
- ) {
- Text(text)
- }
- }
-
- class CallToActionViewButton @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyle: Int = 0
- ) : AbstractComposeView(context, attrs, defStyle) {
-
- var text by mutableStateOf<String>("")
- var onClick by mutableStateOf<() -> Unit>({})
-
- @Composable
- override fun Content() {
- YourAppTheme {
- CallToActionButton(text, onClick)
- }
- }
- }
-}
-
-private object InteropSnippet18 {
- class ExampleActivity : Activity() {
-
- private lateinit var binding: ActivityExampleBinding
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- binding = ActivityExampleBinding.inflate(layoutInflater)
- setContentView(binding.root)
-
- binding.callToAction.apply {
- text = getString(R.string.something)
- onClick = { /* Do something */ }
- }
- }
- }
-}
-
private object InteropSnippet19 {
@Composable
fun SystemBroadcastReceiver(
@@ -578,19 +287,18 @@
object string {
const val ok = 4
const val plane_description = 5
- const val something = 6
}
object dimen {
- const val padding_small = 7
+ const val padding_small = 6
}
object drawable {
- const val ic_plane = 8
+ const val ic_plane = 7
}
object color {
- const val Blue700 = 9
+ const val Blue700 = 8
}
}
@@ -605,7 +313,7 @@
private val data = DataExample()
private fun startActivity(): Nothing = TODO()
-class ExampleViewModel : ViewModel() {
+private class ExampleViewModel : ViewModel() {
val exampleLiveData = MutableLiveData(" ")
}
@@ -627,35 +335,6 @@
fun into(listener: ExampleImageLoader.Listener) {}
}
-private fun ExampleComposable() {}
-@Composable
-private fun MdcTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun AppCompatTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun BlueTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun PinkTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun YourAppTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun ProvideWindowInsets(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun Icon() {
-}
-
private open class Fragment {
lateinit var context: Context
@@ -669,28 +348,3 @@
fun requireContext(): Context = TODO()
}
-
-private class WindowCompat {
- companion object {
- fun setDecorFitsSystemWindows(window: Any, bool: Boolean) {}
- }
-}
-
-private fun Modifier.navigationBarsPadding(): Modifier = this
-
-private class GreetingViewModel : ViewModel() {
- val _message = MutableLiveData("")
- val message: LiveData<String> = _message
-}
-private class GreetingViewModelFactory(val userId: String) : ViewModelProvider.Factory {
- override fun <T : ViewModel?> create(modelClass: Class<T>): T {
- TODO("Not yet implemented")
- }
-}
-private class ActivityExampleBinding {
- val root: Int = 0
- lateinit var callToAction: InteropSnippet17.CallToActionViewButton
- companion object {
- fun inflate(li: LayoutInflater): ActivityExampleBinding { TODO() }
- }
-}
\ No newline at end of file
diff --git a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
index f9f5377..30224a5 100644
--- a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
+++ b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
@@ -17,9 +17,9 @@
package androidx.compose.runtime.saveable
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.key
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.ReusableContent
import androidx.compose.runtime.remember
/**
@@ -73,7 +73,7 @@
@Composable
override fun SaveableStateProvider(key: Any, content: @Composable () -> Unit) {
- key(key) {
+ ReusableContent(key) {
val registryHolder = remember {
require(parentSaveableStateRegistry?.canBeSaved(key) ?: true) {
"Type of the key $key is not supported. On Android you can only use types " +
@@ -85,7 +85,7 @@
LocalSaveableStateRegistry provides registryHolder.registry,
content = content
)
- DisposableEffect(key) {
+ DisposableEffect(Unit) {
require(key !in registryHolders) { "Key $key was used multiple times " }
savedStates -= key
registryHolders[key] = registryHolder
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt
index 188825d..85d8bcf 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt
@@ -248,7 +248,7 @@
else -> throw IllegalStateException("Unknown font type: $font")
}
} catch (e: Exception) {
- throw IllegalStateException("Cannot create Typeface from $font")
+ throw IllegalStateException("Cannot create Typeface from $font", e)
}
val loadedFontIsSameAsRequest = fontWeight == font.weight && fontStyle == font.style
diff --git a/compose/ui/ui-tooling/api/1.0.0-beta07.txt b/compose/ui/ui-tooling/api/1.0.0-beta07.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/1.0.0-beta07.txt
+++ b/compose/ui/ui-tooling/api/1.0.0-beta07.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/current.ignore b/compose/ui/ui-tooling/api/current.ignore
new file mode 100644
index 0000000..050747e
--- /dev/null
+++ b/compose/ui/ui-tooling/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedPackage: androidx.compose.ui.tooling.inspector:
+ Removed package androidx.compose.ui.tooling.inspector
diff --git a/compose/ui/ui-tooling/api/current.txt b/compose/ui/ui-tooling/api/current.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/current.txt
+++ b/compose/ui/ui-tooling/api/current.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt b/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/public_plus_experimental_current.txt b/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt b/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt
+++ b/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/restricted_current.ignore b/compose/ui/ui-tooling/api/restricted_current.ignore
new file mode 100644
index 0000000..050747e
--- /dev/null
+++ b/compose/ui/ui-tooling/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedPackage: androidx.compose.ui.tooling.inspector:
+ Removed package androidx.compose.ui.tooling.inspector
diff --git a/compose/ui/ui-tooling/api/restricted_current.txt b/compose/ui/ui-tooling/api/restricted_current.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/restricted_current.txt
+++ b/compose/ui/ui-tooling/api/restricted_current.txt
@@ -7,95 +7,6 @@
}
-package androidx.compose.ui.tooling.inspector {
-
- public final class InspectorNode {
- method public int[] getBounds();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
- method public String getFileName();
- method public int getHeight();
- method public long getId();
- method public int getLeft();
- method public int getLength();
- method public int getLineNumber();
- method public String getName();
- method public int getOffset();
- method public int getPackageHash();
- method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
- method public int getTop();
- method public int getWidth();
- property public final int[] bounds;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
- property public final String fileName;
- property public final int height;
- property public final long id;
- property public final int left;
- property public final int length;
- property public final int lineNumber;
- property public final String name;
- property public final int offset;
- property public final int packageHash;
- property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
- property public final int top;
- property public final int width;
- }
-
- public final class InspectorNodeKt {
- }
-
- public final class LayoutInspectorTree {
- ctor public LayoutInspectorTree();
- method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
- method public boolean getHideSystemNodes();
- method public void resetGeneratedId();
- method public void setHideSystemNodes(boolean p);
- property public final boolean hideSystemNodes;
- }
-
- public final class LayoutInspectorTreeKt {
- }
-
- public final class NodeParameter {
- method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
- method public String getName();
- method public androidx.compose.ui.tooling.inspector.ParameterType getType();
- method public Object? getValue();
- property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
- property public final String name;
- property public final androidx.compose.ui.tooling.inspector.ParameterType type;
- property public final Object? value;
- }
-
- public final class ParameterFactoryKt {
- }
-
- public enum ParameterType {
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
- enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
- }
-
- public final class RawParameter {
- ctor public RawParameter(String name, Object? value);
- method public String getName();
- method public Object? getValue();
- property public final String name;
- property public final Object? value;
- }
-
-}
-
package androidx.compose.ui.tooling.preview {
public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/InlineClassConverterTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/InlineClassConverterTest.kt
deleted file mode 100644
index 57ae98f..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/InlineClassConverterTest.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.tooling.CompositionDataRecord
-import androidx.compose.ui.tooling.Inspectable
-import androidx.compose.ui.tooling.ToolingTest
-import androidx.compose.ui.tooling.data.Group
-import androidx.compose.ui.tooling.data.UiToolingDataApi
-import androidx.compose.ui.tooling.data.asTree
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@OptIn(UiToolingDataApi::class)
-class InlineClassConverterTest : ToolingTest() {
-
- @Test
- fun parameterValueTest() {
- val slotTableRecord = CompositionDataRecord.create()
- show {
- Inspectable(slotTableRecord) {
- Surface {
- Text(text = "OK", fontSize = 12.sp)
- }
- }
- }
-
- val tree = slotTableRecord.store.first().asTree()
- val groups = flatten(tree)
- val surface = find(groups, "Surface")
- val text = find(groups, "Text")
-
- val mapper = InlineClassConverter()
-
- fun validate(caller: Group, parameterName: String, valueType: Class<*>) {
- val parameter = caller.parameters.single { it.name == parameterName }
- val value = mapper.castParameterValue(parameter.inlineClass, parameter.value)
- assertThat(value).isInstanceOf(valueType)
- }
-
- validate(surface, "color", Color::class.java)
- validate(surface, "elevation", Dp::class.java)
- validate(text, "color", Color::class.java)
- validate(text, "fontSize", TextUnit::class.java)
- }
-
- private fun flatten(group: Group): Sequence<Group> =
- sequenceOf(group).plus(group.children.asSequence().flatMap { flatten(it) })
-
- private fun find(groups: Sequence<Group>, calleeName: String) =
- groups.first {
- it.parameters.isNotEmpty() && it.name == calleeName
- }
-}
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
deleted file mode 100644
index 7a375f3..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
+++ /dev/null
@@ -1,570 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.material.Button
-import androidx.compose.material.Icon
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.ModalDrawer
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Call
-import androidx.compose.material.icons.filled.FavoriteBorder
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.GraphicLayerInfo
-import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.tooling.CompositionDataRecord
-import androidx.compose.ui.tooling.Inspectable
-import androidx.compose.ui.tooling.R
-import androidx.compose.ui.tooling.ToolingTest
-import androidx.compose.ui.tooling.data.Group
-import androidx.compose.ui.tooling.data.UiToolingDataApi
-import androidx.compose.ui.tooling.data.asTree
-import androidx.compose.ui.tooling.data.position
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import kotlin.math.roundToInt
-
-private const val DEBUG = false
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = 29) // Render id is not returned for api < 29
-@OptIn(UiToolingDataApi::class)
-class LayoutInspectorTreeTest : ToolingTest() {
- private lateinit var density: Density
- private lateinit var view: View
-
- @Before
- fun before() {
- density = Density(activity)
- view = activityTestRule.activity.findViewById<ViewGroup>(android.R.id.content)
- isDebugInspectorInfoEnabled = true
- }
-
- @After
- fun after() {
- isDebugInspectorInfoEnabled = false
- }
-
- @Test
- fun buildTree() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- Column {
- Text(text = "Hello World", color = Color.Green)
- Icon(Icons.Filled.FavoriteBorder, null)
- Surface {
- Button(onClick = {}) { Text(text = "OK") }
- }
- }
- }
- }
-
- // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- val builder = LayoutInspectorTree()
- val nodes = builder.convert(view)
- dumpNodes(nodes, builder)
-
- validate(nodes, builder, checkParameters = false) {
- node(
- name = "Inspectable",
- fileName = "LayoutInspectorTreeTest.kt",
- left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 78.9.dp,
- children = listOf("Column")
- )
- node(
- name = "Column",
- fileName = "LayoutInspectorTreeTest.kt",
- left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 78.9.dp,
- children = listOf("Text", "Icon", "Surface")
- )
- node(
- name = "Text",
- isRenderNode = true,
- fileName = "LayoutInspectorTreeTest.kt",
- left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 18.9.dp,
- )
- node(
- name = "Icon",
- isRenderNode = true,
- fileName = "LayoutInspectorTreeTest.kt",
- left = 0.0.dp, top = 18.9.dp, width = 24.0.dp, height = 24.0.dp,
- )
- node(
- name = "Surface",
- fileName = "LayoutInspectorTreeTest.kt",
- isRenderNode = true,
- left = 0.0.dp,
- top = 42.9.dp, width = 64.0.dp, height = 36.0.dp,
- children = listOf("Button")
- )
- node(
- name = "Button",
- fileName = "LayoutInspectorTreeTest.kt",
- isRenderNode = true,
- left = 0.0.dp,
- top = 42.9.dp, width = 64.0.dp, height = 36.0.dp,
- children = listOf("Text")
- )
- node(
- name = "Text",
- isRenderNode = true,
- fileName = "LayoutInspectorTreeTest.kt",
- left = 21.7.dp, top = 51.6.dp, width = 20.9.dp, height = 18.9.dp,
- )
- }
- }
-
- @Test
- fun buildTreeWithTransformedText() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- MaterialTheme {
- Text(
- text = "Hello World",
- modifier = Modifier.graphicsLayer(rotationZ = 225f)
- )
- }
- }
- }
-
- // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- val builder = LayoutInspectorTree()
- val nodes = builder.convert(view)
- dumpNodes(nodes, builder)
-
- validate(nodes, builder, checkParameters = false) {
- node(
- name = "Inspectable",
- hasTransformations = true,
- fileName = "LayoutInspectorTreeTest.kt",
- children = listOf("MaterialTheme")
- )
- node(
- name = "MaterialTheme",
- hasTransformations = true,
- fileName = "LayoutInspectorTreeTest.kt",
- left = 65.0.dp, top = 49.7.dp, width = 86.dp, height = 21.7.dp,
- children = listOf("Text")
- )
- node(
- name = "Text",
- isRenderNode = true,
- hasTransformations = true,
- fileName = "LayoutInspectorTreeTest.kt",
- left = 65.0.dp, top = 49.7.dp, width = 86.dp, height = 21.7.dp,
- )
- }
- }
-
- @Test
- fun testStitchTreeFromModelDrawerLayout() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- ModalDrawer(
- drawerContent = { Text("Something") },
- content = {
- Column {
- Text(text = "Hello World", color = Color.Green)
- Button(onClick = {}) { Text(text = "OK") }
- }
- }
- )
- }
- }
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- dumpSlotTableSet(slotTableRecord)
- val builder = LayoutInspectorTree()
- val nodes = builder.convert(view)
- dumpNodes(nodes, builder)
-
- if (DEBUG) {
- validate(nodes, builder, checkParameters = false) {
- node("Inspectable", children = listOf("Box"))
- node("Box", children = listOf("ModalDrawer"))
- node("ModalDrawer", children = listOf("Column", "Text"))
- node("Column", children = listOf("Text", "Button"))
- node("Text")
- node("Button", children = listOf("Text"))
- node("Text")
- node("Text")
- }
- }
- assertThat(nodes.size).isEqualTo(1)
- }
-
- @LargeTest
- @Test
- fun testStitchTreeFromModelDrawerLayoutWithSystemNodes() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- ModalDrawer(
- drawerContent = { Text("Something") },
- content = {
- Column {
- Text(text = "Hello World", color = Color.Green)
- Button(onClick = {}) { Text(text = "OK") }
- }
- }
- )
- }
- }
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- dumpSlotTableSet(slotTableRecord)
- val builder = LayoutInspectorTree()
- builder.hideSystemNodes = false
- val nodes = builder.convert(view)
- dumpNodes(nodes, builder)
-
- if (DEBUG) {
- validate(nodes, builder, checkParameters = false) {
- node("Inspectable", children = listOf("Box"))
- node("Box", children = listOf("ModalDrawer"))
- node("ModalDrawer", children = listOf("WithConstraints"))
- node("WithConstraints", children = listOf("SubcomposeLayout"))
- node("SubcomposeLayout", children = listOf("Box"))
- node("Box", children = listOf("Box", "Canvas", "Surface"))
- node("Box", children = listOf("Column"))
- node("Column", children = listOf("Text", "Button"))
- node("Text", children = listOf("Text"))
- node("Text", children = listOf("CoreText"))
- node("CoreText", children = listOf())
- node("Button", children = listOf("Surface"))
- node("Surface", children = listOf("ProvideTextStyle"))
- node("ProvideTextStyle", children = listOf("Row"))
- node("Row", children = listOf("Text"))
- node("Text", children = listOf("Text"))
- node("Text", children = listOf("CoreText"))
- node("CoreText", children = listOf())
- node("Canvas", children = listOf("Spacer"))
- node("Spacer", children = listOf())
- node("Surface", children = listOf("Column"))
- node("Column", children = listOf("Text"))
- node("Text", children = listOf("Text"))
- node("Text", children = listOf("CoreText"))
- node("CoreText", children = listOf())
- }
- }
- assertThat(nodes.size).isEqualTo(1)
- }
-
- @Test
- fun testSpacer() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- Column {
- Text(text = "Hello World", color = Color.Green)
- Spacer(Modifier.height(16.dp))
- Image(Icons.Filled.Call, null)
- }
- }
- }
-
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- val builder = LayoutInspectorTree()
- val node = builder.convert(view)
- .flatMap { flatten(it) }
- .firstOrNull { it.name == "Spacer" }
-
- // Spacer should show up in the Compose tree:
- assertThat(node).isNotNull()
- }
-
- @Test // regression test b/174855322
- fun testBasicText() {
- val slotTableRecord = CompositionDataRecord.create()
-
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- show {
- Inspectable(slotTableRecord) {
- Column {
- BasicText(
- text = "Some text",
- style = TextStyle(textDecoration = TextDecoration.Underline)
- )
- }
- }
- }
-
- val builder = LayoutInspectorTree()
- val node = builder.convert(view)
- .flatMap { flatten(it) }
- .firstOrNull { it.name == "BasicText" }
-
- assertThat(node).isNotNull()
-
- assertThat(node?.parameters).isNotEmpty()
- }
-
- @Test
- fun testTextId() {
- val slotTableRecord = CompositionDataRecord.create()
-
- show {
- Inspectable(slotTableRecord) {
- Text(text = "Hello World")
- }
- }
-
- view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
- val builder = LayoutInspectorTree()
- val node = builder.convert(view)
- .flatMap { flatten(it) }
- .firstOrNull { it.name == "Text" }
-
- // LayoutNode id should be captured by the Text node:
- assertThat(node?.id).isGreaterThan(0)
- }
-
- @Suppress("SameParameterValue")
- private fun validate(
- result: List<InspectorNode>,
- builder: LayoutInspectorTree,
- checkParameters: Boolean,
- block: TreeValidationReceiver.() -> Unit = {}
- ) {
- val nodes = result.flatMap { flatten(it) }.iterator()
- val tree = TreeValidationReceiver(nodes, density, checkParameters, builder)
- tree.block()
- }
-
- private class TreeValidationReceiver(
- val nodeIterator: Iterator<InspectorNode>,
- val density: Density,
- val checkParameters: Boolean,
- val builder: LayoutInspectorTree
- ) {
- fun node(
- name: String,
- fileName: String? = null,
- lineNumber: Int = -1,
- isRenderNode: Boolean = false,
- hasTransformations: Boolean = false,
-
- left: Dp = Dp.Unspecified,
- top: Dp = Dp.Unspecified,
- width: Dp = Dp.Unspecified,
- height: Dp = Dp.Unspecified,
- children: List<String> = listOf(),
- block: ParameterValidationReceiver.() -> Unit = {}
- ) {
- assertWithMessage("No such node found: $name").that(nodeIterator.hasNext()).isTrue()
- val node = nodeIterator.next()
- assertThat(node.name).isEqualTo(name)
- val message = "Node: $name"
- assertWithMessage(message).that(node.children.map { it.name })
- .containsExactlyElementsIn(children).inOrder()
- fileName?.let { assertWithMessage(message).that(node.fileName).isEqualTo(fileName) }
- if (lineNumber != -1) {
- assertWithMessage(message).that(node.lineNumber).isEqualTo(lineNumber)
- }
- if (isRenderNode) {
- assertWithMessage(message).that(node.id).isGreaterThan(0L)
- } else {
- assertWithMessage(message).that(node.id).isLessThan(0L)
- }
- if (hasTransformations) {
- assertWithMessage(message).that(node.bounds).isNotEmpty()
- } else {
- assertWithMessage(message).that(node.bounds).isEmpty()
- }
- if (left != Dp.Unspecified) {
- val tolerance = 5.0f
- with(density) {
- assertWithMessage(message).that(node.left.toDp().value)
- .isWithin(tolerance).of(left.value)
- assertWithMessage(message).that(node.top.toDp().value)
- .isWithin(tolerance).of(top.value)
- assertWithMessage(message).that(node.width.toDp().value)
- .isWithin(tolerance).of(width.value)
- assertWithMessage(message).that(node.height.toDp().value)
- .isWithin(tolerance).of(height.value)
- }
- }
-
- if (checkParameters) {
- val params = builder.convertParameters(node)
- val receiver = ParameterValidationReceiver(params.listIterator())
- receiver.block()
- if (receiver.parameterIterator.hasNext()) {
- val elementNames = mutableListOf<String>()
- receiver.parameterIterator.forEachRemaining { elementNames.add(it.name) }
- error("$name: has more parameters like: ${elementNames.joinToString()}")
- }
- }
- }
- }
-
- private fun flatten(node: InspectorNode): List<InspectorNode> =
- listOf(node).plus(node.children.flatMap { flatten(it) })
-
- // region DEBUG print methods
- private fun dumpNodes(nodes: List<InspectorNode>, builder: LayoutInspectorTree) {
- @Suppress("ConstantConditionIf")
- if (!DEBUG) {
- return
- }
- println()
- println("=================== Nodes ==========================")
- nodes.forEach { dumpNode(it, indent = 0) }
- println()
- println("=================== validate statements ==========================")
- nodes.forEach { generateValidate(it, builder) }
- }
-
- private fun dumpNode(node: InspectorNode, indent: Int) {
- println(
- "\"${" ".repeat(indent * 2)}\", \"${node.name}\", \"${node.fileName}\", " +
- "${node.lineNumber}, ${node.left}, ${node.top}, " +
- "${node.width}, ${node.height}"
- )
- node.children.forEach { dumpNode(it, indent + 1) }
- }
-
- private fun generateValidate(
- node: InspectorNode,
- builder: LayoutInspectorTree,
- generateParameters: Boolean = false
- ) {
- with(density) {
- val left = round(node.left.toDp())
- val top = round(node.top.toDp())
- val width = if (node.width == view.width) "viewWidth" else round(node.width.toDp())
- val height = if (node.height == view.height) "viewHeight" else round(node.height.toDp())
-
- print(
- """
- validate(
- name = "${node.name}",
- fileName = "${node.fileName}",
- left = $left, top = $top, width = $width, height = $height
- """.trimIndent()
- )
- }
- if (node.id > 0L) {
- println(",")
- print(" isRenderNode = true")
- }
- if (node.children.isNotEmpty()) {
- println(",")
- val children = node.children.joinToString { "\"${it.name}\"" }
- print(" children = listOf($children)")
- }
- println()
- print(")")
- if (generateParameters && node.parameters.isNotEmpty()) {
- generateParameters(builder.convertParameters(node), 0)
- }
- println()
- node.children.forEach { generateValidate(it, builder) }
- }
-
- private fun generateParameters(parameters: List<NodeParameter>, indent: Int) {
- val indentation = " ".repeat(indent * 2)
- println(" {")
- for (param in parameters) {
- val name = param.name
- val type = param.type
- val value = toDisplayValue(type, param.value)
- print("$indentation parameter(name = \"$name\", type = $type, value = $value)")
- if (param.elements.isNotEmpty()) {
- generateParameters(param.elements, indent + 1)
- }
- println()
- }
- print("$indentation}")
- }
-
- private fun toDisplayValue(type: ParameterType, value: Any?): String =
- when (type) {
- ParameterType.Boolean -> value.toString()
- ParameterType.Color ->
- "0x${Integer.toHexString(value as Int)}${if (value < 0) ".toInt()" else ""}"
- ParameterType.DimensionSp,
- ParameterType.DimensionDp -> "${value}f"
- ParameterType.Int32 -> value.toString()
- ParameterType.String -> "\"$value\""
- else -> value?.toString() ?: "null"
- }
-
- private fun dumpSlotTableSet(slotTableRecord: CompositionDataRecord) {
- @Suppress("ConstantConditionIf")
- if (!DEBUG) {
- return
- }
- println()
- println("=================== Groups ==========================")
- slotTableRecord.store.forEach { dumpGroup(it.asTree(), indent = 0) }
- }
-
- private fun dumpGroup(group: Group, indent: Int) {
- val position = group.position?.let { "\"$it\"" } ?: "null"
- val box = group.box
- val id = group.modifierInfo.mapNotNull { (it.extra as? GraphicLayerInfo)?.layerId }
- .singleOrNull() ?: 0
- println(
- "\"${" ".repeat(indent)}\", ${group.javaClass.simpleName}, \"${group.name}\", " +
- "params: ${group.parameters.size}, children: ${group.children.size}, " +
- "$id, $position, " +
- "${box.left}, ${box.right}, ${box.right - box.left}, ${box.bottom - box.top}"
- )
- for (parameter in group.parameters) {
- println("\"${" ".repeat(indent + 4)}\"- ${parameter.name}")
- }
- group.children.forEach { dumpGroup(it, indent + 1) }
- }
-
- private fun round(dp: Dp): Dp = Dp((dp.value * 10.0f).roundToInt() / 10.0f)
-
- //endregion
-}
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt
deleted file mode 100644
index 83459eb..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt
+++ /dev/null
@@ -1,737 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.wrapContentHeight
-import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.foundation.shape.CutCornerShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.shape.ZeroCornerSize
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Call
-import androidx.compose.material.icons.rounded.Add
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.AbsoluteAlignment
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.paint
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.colorspace.ColorModel
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.painter.Painter
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.intl.Locale
-import androidx.compose.ui.text.intl.LocaleList
-import androidx.compose.ui.text.style.BaselineShift
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.text.style.TextGeometricTransform
-import androidx.compose.ui.text.style.TextIndent
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.em
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.runBlocking
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@Suppress("unused")
-private fun topLevelFunction() {}
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class ParameterFactoryTest {
- private val factory = ParameterFactory(InlineClassConverter())
- private val node = MutableInspectorNode().apply {
- width = 1000
- height = 500
- }.build()
-
- @Before
- fun before() {
- factory.density = Density(2.0f)
- isDebugInspectorInfoEnabled = true
- }
-
- @After
- fun after() {
- isDebugInspectorInfoEnabled = false
- }
-
- @Test
- fun testAbsoluteAlignment() {
- assertThat(lookup(AbsoluteAlignment.TopLeft))
- .isEqualTo(ParameterType.String to "TopLeft")
- assertThat(lookup(AbsoluteAlignment.TopRight))
- .isEqualTo(ParameterType.String to "TopRight")
- assertThat(lookup(AbsoluteAlignment.CenterLeft))
- .isEqualTo(ParameterType.String to "CenterLeft")
- assertThat(lookup(AbsoluteAlignment.CenterRight))
- .isEqualTo(ParameterType.String to "CenterRight")
- assertThat(lookup(AbsoluteAlignment.BottomLeft))
- .isEqualTo(ParameterType.String to "BottomLeft")
- assertThat(lookup(AbsoluteAlignment.BottomRight))
- .isEqualTo(ParameterType.String to "BottomRight")
- assertThat(lookup(AbsoluteAlignment.Left))
- .isEqualTo(ParameterType.String to "Left")
- assertThat(lookup(AbsoluteAlignment.Right))
- .isEqualTo(ParameterType.String to "Right")
- }
-
- @Test
- fun testAlignment() {
- assertThat(lookup(Alignment.Top)).isEqualTo(ParameterType.String to "Top")
- assertThat(lookup(Alignment.Bottom)).isEqualTo(ParameterType.String to "Bottom")
- assertThat(lookup(Alignment.CenterVertically))
- .isEqualTo(ParameterType.String to "CenterVertically")
-
- assertThat(lookup(Alignment.Start)).isEqualTo(ParameterType.String to "Start")
- assertThat(lookup(Alignment.End)).isEqualTo(ParameterType.String to "End")
- assertThat(lookup(Alignment.CenterHorizontally))
- .isEqualTo(ParameterType.String to "CenterHorizontally")
-
- assertThat(lookup(Alignment.TopStart)).isEqualTo(ParameterType.String to "TopStart")
- assertThat(lookup(Alignment.TopCenter)).isEqualTo(ParameterType.String to "TopCenter")
- assertThat(lookup(Alignment.TopEnd)).isEqualTo(ParameterType.String to "TopEnd")
- assertThat(lookup(Alignment.CenterStart)).isEqualTo(ParameterType.String to "CenterStart")
- assertThat(lookup(Alignment.Center)).isEqualTo(ParameterType.String to "Center")
- assertThat(lookup(Alignment.CenterEnd)).isEqualTo(ParameterType.String to "CenterEnd")
- assertThat(lookup(Alignment.BottomStart)).isEqualTo(ParameterType.String to "BottomStart")
- assertThat(lookup(Alignment.BottomCenter)).isEqualTo(ParameterType.String to "BottomCenter")
- assertThat(lookup(Alignment.BottomEnd)).isEqualTo(ParameterType.String to "BottomEnd")
- }
-
- @Test
- fun testAnnotatedString() {
- assertThat(lookup(AnnotatedString("Hello"))).isEqualTo(ParameterType.String to "Hello")
- }
-
- @Test
- fun testArrangement() {
- assertThat(lookup(Arrangement.Top)).isEqualTo(ParameterType.String to "Top")
- assertThat(lookup(Arrangement.Bottom)).isEqualTo(ParameterType.String to "Bottom")
- assertThat(lookup(Arrangement.Center)).isEqualTo(ParameterType.String to "Center")
- assertThat(lookup(Arrangement.Start)).isEqualTo(ParameterType.String to "Start")
- assertThat(lookup(Arrangement.End)).isEqualTo(ParameterType.String to "End")
- assertThat(lookup(Arrangement.SpaceEvenly)).isEqualTo(ParameterType.String to "SpaceEvenly")
- assertThat(lookup(Arrangement.SpaceBetween))
- .isEqualTo(ParameterType.String to "SpaceBetween")
- assertThat(lookup(Arrangement.SpaceAround)).isEqualTo(ParameterType.String to "SpaceAround")
- }
-
- @Test
- fun testBaselineShift() {
- assertThat(lookup(BaselineShift.None)).isEqualTo(ParameterType.String to "None")
- assertThat(lookup(BaselineShift.Subscript)).isEqualTo(ParameterType.String to "Subscript")
- assertThat(lookup(BaselineShift.Superscript))
- .isEqualTo(ParameterType.String to "Superscript")
- assertThat(lookup(BaselineShift(0.0f))).isEqualTo(ParameterType.String to "None")
- assertThat(lookup(BaselineShift(-0.5f))).isEqualTo(ParameterType.String to "Subscript")
- assertThat(lookup(BaselineShift(0.5f))).isEqualTo(ParameterType.String to "Superscript")
- assertThat(lookup(BaselineShift(0.1f))).isEqualTo(ParameterType.Float to 0.1f)
- assertThat(lookup(BaselineShift(0.75f))).isEqualTo(ParameterType.Float to 0.75f)
- }
-
- @Test
- fun testBoolean() {
- assertThat(lookup(true)).isEqualTo(ParameterType.Boolean to true)
- assertThat(lookup(false)).isEqualTo(ParameterType.Boolean to false)
- }
-
- @Test
- fun testBorder() {
- validate(factory.create(node, "borderstroke", BorderStroke(2.0.dp, Color.Magenta))!!) {
- parameter("borderstroke", ParameterType.String, "BorderStroke") {
- parameter("brush", ParameterType.Color, Color.Magenta.toArgb())
- parameter("width", ParameterType.DimensionDp, 2.0f)
- }
- }
- }
-
- @Test
- fun testBrush() {
- assertThat(lookup(SolidColor(Color.Red)))
- .isEqualTo(ParameterType.Color to Color.Red.toArgb())
- validate(
- factory.create(
- node,
- "brush",
- Brush.linearGradient(
- colors = listOf(Color.Red, Color.Blue),
- start = Offset(0.0f, 0.5f),
- end = Offset(5.0f, 10.0f)
- )
- )!!
- ) {
- parameter("brush", ParameterType.String, "LinearGradient") {
- parameter("colors", ParameterType.String, "") {
- parameter("0", ParameterType.Color, Color.Red.toArgb())
- parameter("1", ParameterType.Color, Color.Blue.toArgb())
- }
- // Parameters are traversed in alphabetical order through reflection queries.
- // Validate createdSize exists before validating end parameter
- parameter("createdSize", ParameterType.String, "Unspecified")
- parameter("end", ParameterType.String, Offset::class.java.simpleName) {
- parameter("x", ParameterType.DimensionDp, 2.5f)
- parameter("y", ParameterType.DimensionDp, 5.0f)
- }
- parameter("start", ParameterType.String, Offset::class.java.simpleName) {
- parameter("x", ParameterType.DimensionDp, 0.0f)
- parameter("y", ParameterType.DimensionDp, 0.25f)
- }
- parameter("tileMode", ParameterType.String, "Clamp")
- }
- }
- // TODO: add tests for RadialGradient & ShaderBrush
- }
-
- @Test
- fun testColor() {
- assertThat(lookup(Color.Blue)).isEqualTo(ParameterType.Color to 0xff0000ff.toInt())
- assertThat(lookup(Color.Red)).isEqualTo(ParameterType.Color to 0xffff0000.toInt())
- assertThat(lookup(Color.Transparent)).isEqualTo(ParameterType.Color to 0x00000000)
- assertThat(lookup(Color.Unspecified)).isEqualTo(ParameterType.String to "Unspecified")
- }
-
- @Test
- fun testComposableLambda() = runBlocking {
- // capture here to force the lambda to not be created as a singleton.
- val capture = "Hello World"
- val c: @Composable () -> Unit = { Text(text = capture) }
- val result = lookup(c as Any) ?: error("Lookup of ComposableLambda failed")
- val array = result.second as Array<*>
- assertThat(result.first).isEqualTo(ParameterType.Lambda)
- assertThat(array).hasLength(1)
- assertThat(array[0]?.javaClass?.name).isEqualTo(
- "${ParameterFactoryTest::class.java.name}\$testComposableLambda\$1\$c\$1"
- )
- }
-
- @Test
- fun testCornerBasedShape() {
- validate(
- factory.create(
- node, "corner",
- RoundedCornerShape(2.0.dp, 0.5.dp, 2.5.dp, 0.7.dp)
- )!!
- ) {
- parameter("corner", ParameterType.String, RoundedCornerShape::class.java.simpleName) {
- parameter("bottomEnd", ParameterType.DimensionDp, 2.5f)
- parameter("bottomStart", ParameterType.DimensionDp, 0.7f)
- parameter("topEnd", ParameterType.DimensionDp, 0.5f)
- parameter("topStart", ParameterType.DimensionDp, 2.0f)
- }
- }
- validate(factory.create(node, "corner", CutCornerShape(2))!!) {
- parameter("corner", ParameterType.String, CutCornerShape::class.java.simpleName) {
- parameter("bottomEnd", ParameterType.DimensionDp, 5.0f)
- parameter("bottomStart", ParameterType.DimensionDp, 5.0f)
- parameter("topEnd", ParameterType.DimensionDp, 5.0f)
- parameter("topStart", ParameterType.DimensionDp, 5.0f)
- }
- }
- validate(factory.create(node, "corner", RoundedCornerShape(1.0f, 10.0f, 2.0f, 3.5f))!!) {
- parameter("corner", ParameterType.String, RoundedCornerShape::class.java.simpleName) {
- parameter("bottomEnd", ParameterType.DimensionDp, 1.0f)
- parameter("bottomStart", ParameterType.DimensionDp, 1.75f)
- parameter("topEnd", ParameterType.DimensionDp, 5.0f)
- parameter("topStart", ParameterType.DimensionDp, 0.5f)
- }
- }
- }
-
- @Test
- fun testCornerSize() {
- assertThat(lookup(ZeroCornerSize)).isEqualTo(ParameterType.String to "ZeroCornerSize")
- assertThat(lookup(CornerSize(2.4.dp))).isEqualTo(ParameterType.DimensionDp to 2.4f)
- assertThat(lookup(CornerSize(2.4f))).isEqualTo(ParameterType.DimensionDp to 1.2f)
- assertThat(lookup(CornerSize(3))).isEqualTo(ParameterType.DimensionDp to 7.5f)
- }
-
- @Test
- fun testDouble() {
- assertThat(lookup(3.1428)).isEqualTo(ParameterType.Double to 3.1428)
- }
-
- @Test
- fun testDp() {
- assertThat(lookup(2.0.dp)).isEqualTo(ParameterType.DimensionDp to 2.0f)
- assertThat(lookup(Dp.Hairline)).isEqualTo(ParameterType.DimensionDp to 0.0f)
- assertThat(lookup(Dp.Unspecified)).isEqualTo(ParameterType.DimensionDp to Float.NaN)
- assertThat(lookup(Dp.Infinity))
- .isEqualTo(ParameterType.DimensionDp to Float.POSITIVE_INFINITY)
- }
-
- @Test
- fun testEnum() {
- assertThat(lookup(ColorModel.Lab)).isEqualTo(ParameterType.String to "Lab")
- assertThat(lookup(ColorModel.Rgb)).isEqualTo(ParameterType.String to "Rgb")
- assertThat(lookup(ColorModel.Cmyk)).isEqualTo(ParameterType.String to "Cmyk")
- }
-
- @Test
- fun testFloat() {
- assertThat(lookup(3.1428f)).isEqualTo(ParameterType.Float to 3.1428f)
- }
-
- @Test
- fun testFontFamily() {
- assertThat(lookup(FontFamily.Cursive)).isEqualTo(ParameterType.String to "Cursive")
- assertThat(lookup(FontFamily.Default)).isEqualTo(ParameterType.String to "Default")
- assertThat(lookup(FontFamily.SansSerif)).isEqualTo(ParameterType.String to "SansSerif")
- assertThat(lookup(FontFamily.Serif)).isEqualTo(ParameterType.String to "Serif")
- assertThat(lookup(FontFamily.Monospace)).isEqualTo(ParameterType.String to "Monospace")
- }
-
- @Test
- fun testFontListFontFamily() {
- val family = FontFamily(
- listOf(
- Font(1234, FontWeight.Normal, FontStyle.Italic),
- Font(1235, FontWeight.Normal, FontStyle.Normal),
- Font(1236, FontWeight.Bold, FontStyle.Italic),
- Font(1237, FontWeight.Bold, FontStyle.Normal)
- )
- )
- assertThat(lookup(family)).isEqualTo(ParameterType.Resource to 1235)
- }
-
- @Test
- fun testFontWeight() {
- assertThat(lookup(FontWeight.Thin)).isEqualTo(ParameterType.String to "W100")
- assertThat(lookup(FontWeight.ExtraLight)).isEqualTo(ParameterType.String to "W200")
- assertThat(lookup(FontWeight.Light)).isEqualTo(ParameterType.String to "W300")
- assertThat(lookup(FontWeight.Normal)).isEqualTo(ParameterType.String to "W400")
- assertThat(lookup(FontWeight.Medium)).isEqualTo(ParameterType.String to "W500")
- assertThat(lookup(FontWeight.SemiBold)).isEqualTo(ParameterType.String to "W600")
- assertThat(lookup(FontWeight.Bold)).isEqualTo(ParameterType.String to "W700")
- assertThat(lookup(FontWeight.ExtraBold)).isEqualTo(ParameterType.String to "W800")
- assertThat(lookup(FontWeight.Black)).isEqualTo(ParameterType.String to "W900")
- assertThat(lookup(FontWeight.W100)).isEqualTo(ParameterType.String to "W100")
- assertThat(lookup(FontWeight.W200)).isEqualTo(ParameterType.String to "W200")
- assertThat(lookup(FontWeight.W300)).isEqualTo(ParameterType.String to "W300")
- assertThat(lookup(FontWeight.W400)).isEqualTo(ParameterType.String to "W400")
- assertThat(lookup(FontWeight.W500)).isEqualTo(ParameterType.String to "W500")
- assertThat(lookup(FontWeight.W600)).isEqualTo(ParameterType.String to "W600")
- assertThat(lookup(FontWeight.W700)).isEqualTo(ParameterType.String to "W700")
- assertThat(lookup(FontWeight.W800)).isEqualTo(ParameterType.String to "W800")
- assertThat(lookup(FontWeight.W900)).isEqualTo(ParameterType.String to "W900")
- }
-
- @Test
- fun testFunctionReference() {
- val ref1 = ::testInt
- val map1 = lookup(ref1)!!
- val array1 = map1.second as Array<*>
- assertThat(map1.first).isEqualTo(ParameterType.FunctionReference)
- assertThat(array1.contentEquals(arrayOf(ref1, "testInt"))).isTrue()
- val ref2 = ::topLevelFunction
- val map2 = lookup(ref2)!!
- val array2 = map2.second as Array<*>
- assertThat(map2.first).isEqualTo(ParameterType.FunctionReference)
- assertThat(array2.contentEquals(arrayOf(ref2, "topLevelFunction"))).isTrue()
- }
-
- @Test
- fun testPaddingValues() {
- validate(factory.create(node, "padding", PaddingValues(2.0.dp, 0.5.dp, 2.5.dp, 0.7.dp))!!) {
- parameter(
- "padding",
- ParameterType.String,
- "PaddingValuesImpl"
- ) {
- parameter("bottom", ParameterType.DimensionDp, 0.7f)
- parameter("end", ParameterType.DimensionDp, 2.5f)
- parameter("start", ParameterType.DimensionDp, 2.0f)
- parameter("top", ParameterType.DimensionDp, 0.5f)
- }
- }
- }
-
- @Test
- fun testInt() {
- assertThat(lookup(12345)).isEqualTo(ParameterType.Int32 to 12345)
- }
-
- @Test
- fun testLambda() {
- val a: (Int) -> Int = { it }
- val map = lookup(a)!!
- val array = map.second as Array<*>
- assertThat(map.first).isEqualTo(ParameterType.Lambda)
- assertThat(array.contentEquals(arrayOf<Any>(a))).isTrue()
- }
-
- @Test
- fun testLocale() {
- assertThat(lookup(Locale("fr-CA"))).isEqualTo(ParameterType.String to "fr-CA")
- assertThat(lookup(Locale("fr-BE"))).isEqualTo(ParameterType.String to "fr-BE")
- }
-
- @Test
- fun testLocaleList() {
- validate(factory.create(node, "locales", LocaleList(Locale("fr-ca"), Locale("fr-be")))!!) {
- parameter("locales", ParameterType.String, "") {
- parameter("0", ParameterType.String, "fr-CA")
- parameter("1", ParameterType.String, "fr-BE")
- }
- }
- }
-
- @Test
- fun testLong() {
- assertThat(lookup(12345L)).isEqualTo(ParameterType.Int64 to 12345L)
- }
-
- @Test
- fun testModifier() {
- validate(
- factory.create(
- node, "modifier",
- Modifier
- .background(Color.Blue)
- .border(width = 5.dp, color = Color.Red)
- .padding(2.0.dp)
- .fillMaxWidth()
- .wrapContentHeight(Alignment.Bottom)
- .width(30.0.dp)
- .paint(TestPainter(10f, 20f))
- )!!
- ) {
- parameter("modifier", ParameterType.String, "") {
- parameter("background", ParameterType.Color, Color.Blue.toArgb()) {
- parameter("color", ParameterType.Color, Color.Blue.toArgb())
- parameter("shape", ParameterType.String, "RectangleShape")
- }
- parameter("border", ParameterType.Color, Color.Red.toArgb()) {
- parameter("color", ParameterType.Color, Color.Red.toArgb())
- parameter("shape", ParameterType.String, "RectangleShape")
- parameter("width", ParameterType.DimensionDp, 5.0f)
- }
- parameter("padding", ParameterType.DimensionDp, 2.0f)
- parameter("fillMaxWidth", ParameterType.String, "") {
- parameter("fraction", ParameterType.Float, 1.0f)
- }
- parameter("wrapContentHeight", ParameterType.String, "") {
- parameter("align", ParameterType.String, "Bottom")
- parameter("unbounded", ParameterType.Boolean, false)
- }
- parameter("width", ParameterType.DimensionDp, 30.0f)
- parameter("paint", ParameterType.String, "") {
- parameter("alignment", ParameterType.String, "Center")
- parameter("alpha", ParameterType.Float, 1.0f)
- parameter("contentScale", ParameterType.String, "Inside")
- parameter("painter", ParameterType.String, "TestPainter") {
- parameter("alpha", ParameterType.Float, 1.0f)
- parameter("color", ParameterType.Color, Color.Red.toArgb())
- parameter("drawLambda", ParameterType.Lambda, null)
- parameter("height", ParameterType.Float, 20.0f)
- parameter("intrinsicSize", ParameterType.String, "Size") {
- parameter("height", ParameterType.Float, 20.0f)
- parameter("maxDimension", ParameterType.Float, 20.0f)
- parameter("minDimension", ParameterType.Float, 10.0f)
- parameter("packedValue", ParameterType.Int64, 4692750812821061632L)
- parameter("width", ParameterType.Float, 10.0f)
- }
- parameter("layoutDirection", ParameterType.String, "Ltr")
- parameter("useLayer", ParameterType.Boolean, false)
- parameter("width", ParameterType.Float, 10.0f)
- }
- parameter("sizeToIntrinsics", ParameterType.Boolean, true)
- }
- }
- }
- }
-
- @Test
- fun testSingleModifier() {
- validate(factory.create(node, "modifier", Modifier.padding(2.0.dp))!!) {
- parameter("modifier", ParameterType.String, "") {
- parameter("padding", ParameterType.DimensionDp, 2.0f)
- }
- }
- }
-
- @Test
- fun testSingleModifierWithParameters() {
- validate(factory.create(node, "modifier", Modifier.padding(1.dp, 2.dp, 3.dp, 4.dp))!!) {
- parameter("modifier", ParameterType.String, "") {
- parameter("padding", ParameterType.String, "") {
- parameter("bottom", ParameterType.DimensionDp, 4.0f)
- parameter("end", ParameterType.DimensionDp, 3.0f)
- parameter("start", ParameterType.DimensionDp, 1.0f)
- parameter("top", ParameterType.DimensionDp, 2.0f)
- }
- }
- }
- }
-
- @Test
- fun testOffset() {
- validate(factory.create(node, "offset", Offset(1.0f, 5.0f))!!) {
- parameter("offset", ParameterType.String, Offset::class.java.simpleName) {
- parameter("x", ParameterType.DimensionDp, 0.5f)
- parameter("y", ParameterType.DimensionDp, 2.5f)
- }
- }
- validate(factory.create(node, "offset", Offset.Zero)!!) {
- parameter("offset", ParameterType.String, "Zero")
- }
- }
-
- @Test
- fun testRecursiveStructure() {
- val v1 = MyClass()
- val v2 = MyClass()
- v1.other = v2
- v2.other = v1
- val name = MyClass::class.java.simpleName
- validate(factory.create(node, "mine", v1)!!) {
- parameter("mine", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name) {
- parameter("other", ParameterType.String, name)
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- @Test
- fun testDoNotRecurseIntoAndroidAndJavaPackages() {
- runBlocking {
- assertThat(factory.create(node, "v1", java.net.URL("http://domain.com"))).isNull()
- assertThat(factory.create(node, "v1", android.app.Notification())).isNull()
- }
- }
-
- @Test
- fun testShadow() {
- assertThat(lookup(Shadow.None)).isEqualTo(ParameterType.String to "None")
- validate(factory.create(node, "shadow", Shadow(Color.Cyan, Offset.Zero, 2.5f))!!) {
- parameter("shadow", ParameterType.String, Shadow::class.java.simpleName) {
- parameter("blurRadius", ParameterType.DimensionDp, 1.25f)
- parameter("color", ParameterType.Color, Color.Cyan.toArgb())
- parameter("offset", ParameterType.String, "Zero")
- }
- }
- validate(factory.create(node, "shadow", Shadow(Color.Blue, Offset(1.0f, 4.0f), 1.5f))!!) {
- parameter("shadow", ParameterType.String, Shadow::class.java.simpleName) {
- parameter("blurRadius", ParameterType.DimensionDp, 0.75f)
- parameter("color", ParameterType.Color, Color.Blue.toArgb())
- parameter("offset", ParameterType.String, Offset::class.java.simpleName) {
- parameter("x", ParameterType.DimensionDp, 0.5f)
- parameter("y", ParameterType.DimensionDp, 2.0f)
- }
- }
- }
- }
-
- @Test
- fun testShape() {
- assertThat(lookup(RectangleShape)).isEqualTo(ParameterType.String to "RectangleShape")
- }
-
- @Test
- fun testString() {
- assertThat(lookup("Hello")).isEqualTo(ParameterType.String to "Hello")
- }
-
- @Test
- fun testTextDecoration() {
- assertThat(lookup(TextDecoration.None)).isEqualTo(ParameterType.String to "None")
- assertThat(lookup(TextDecoration.LineThrough))
- .isEqualTo(ParameterType.String to "LineThrough")
- assertThat(lookup(TextDecoration.Underline))
- .isEqualTo(ParameterType.String to "Underline")
- assertThat(lookup(TextDecoration.LineThrough + TextDecoration.Underline))
- .isEqualTo(ParameterType.String to "LineThrough+Underline")
- }
-
- @Test
- fun testTextGeometricTransform() {
- validate(factory.create(node, "transform", TextGeometricTransform(2.0f, 1.5f))!!) {
- parameter(
- "transform", ParameterType.String,
- TextGeometricTransform::class.java.simpleName
- ) {
- parameter("scaleX", ParameterType.Float, 2.0f)
- parameter("skewX", ParameterType.Float, 1.5f)
- }
- }
- }
-
- @Test
- fun testTextIndent() {
- assertThat(lookup(TextIndent.None)).isEqualTo(ParameterType.String to "None")
-
- validate(factory.create(node, "textIndent", TextIndent(4.0.sp, 0.5.sp))!!) {
- parameter("textIndent", ParameterType.String, "TextIndent") {
- parameter("firstLine", ParameterType.DimensionSp, 4.0f)
- parameter("restLine", ParameterType.DimensionSp, 0.5f)
- }
- }
- }
-
- @Test
- fun testTextStyle() {
- val style = TextStyle(
- color = Color.Red,
- textDecoration = TextDecoration.Underline
- )
- validate(factory.create(node, "style", style)!!) {
- parameter("style", ParameterType.String, TextStyle::class.java.simpleName) {
- parameter("background", ParameterType.String, "Unspecified")
- parameter("color", ParameterType.Color, Color.Red.toArgb())
- parameter("fontSize", ParameterType.String, "Unspecified")
- parameter("letterSpacing", ParameterType.String, "Unspecified")
- parameter("lineHeight", ParameterType.String, "Unspecified")
- parameter("textDecoration", ParameterType.String, "Underline")
- }
- }
- }
-
- @Test
- fun testTextUnit() {
- assertThat(lookup(TextUnit.Unspecified)).isEqualTo(ParameterType.String to "Unspecified")
- assertThat(lookup(12.0.sp)).isEqualTo(ParameterType.DimensionSp to 12.0f)
- assertThat(lookup(2.0.em)).isEqualTo(ParameterType.DimensionEm to 2.0f)
- assertThat(lookup(9.0f.sp)).isEqualTo(ParameterType.DimensionSp to 9.0f)
- assertThat(lookup(10.sp)).isEqualTo(ParameterType.DimensionSp to 10.0f)
- assertThat(lookup(26.0.sp)).isEqualTo(ParameterType.DimensionSp to 26.0f)
- assertThat(lookup(2.0f.em)).isEqualTo(ParameterType.DimensionEm to 2.0f)
- assertThat(lookup(1.em)).isEqualTo(ParameterType.DimensionEm to 1.0f)
- assertThat(lookup(3.0.em)).isEqualTo(ParameterType.DimensionEm to 3.0f)
- }
-
- @Test
- fun testVectorAssert() {
- assertThat(lookup(Icons.Filled.Call)).isEqualTo(ParameterType.String to "Filled.Call")
- assertThat(lookup(Icons.Rounded.Add)).isEqualTo(ParameterType.String to "Rounded.Add")
- }
-
- private fun lookup(value: Any): Pair<ParameterType, Any?>? {
- val parameter = factory.create(node, "property", value) ?: return null
- assertThat(parameter.elements).isEmpty()
- return Pair(parameter.type, parameter.value)
- }
-
- private fun validate(
- parameter: NodeParameter,
- expected: ParameterValidationReceiver.() -> Unit = {}
- ) {
- val elements = ParameterValidationReceiver(listOf(parameter).listIterator())
- elements.expected()
- }
-}
-
-private class TestPainter(
- val width: Float,
- val height: Float
-) : Painter() {
-
- var color = Color.Red
-
- override val intrinsicSize: Size
- get() = Size(width, height)
-
- override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
- color = if (layoutDirection == LayoutDirection.Rtl) Color.Blue else Color.Red
- return true
- }
-
- override fun DrawScope.onDraw() {
- drawRect(color = color)
- }
-}
-
-class ParameterValidationReceiver(val parameterIterator: Iterator<NodeParameter>) {
- fun parameter(
- name: String,
- type: ParameterType,
- value: Any?,
- block: ParameterValidationReceiver.() -> Unit = {}
- ) {
- assertWithMessage("No such element found: $name").that(parameterIterator.hasNext()).isTrue()
- val parameter = parameterIterator.next()
- assertThat(parameter.name).isEqualTo(name)
- assertWithMessage(name).that(parameter.type).isEqualTo(type)
- if (type != ParameterType.Lambda || value != null) {
- assertWithMessage(name).that(parameter.value).isEqualTo(value)
- }
- var elements: List<NodeParameter> = parameter.elements
- if (name != "modifier") {
- // Do not sort modifiers: the order is important
- elements = elements.sortedBy { it.name }
- }
- val children = ParameterValidationReceiver(elements.listIterator())
- children.block()
- if (children.parameterIterator.hasNext()) {
- val elementNames = mutableListOf<String>()
- while (children.parameterIterator.hasNext()) {
- elementNames.add(children.parameterIterator.next().name)
- }
- error("$name: has more elements like: ${elementNames.joinToString()}")
- }
- }
-}
-
-class MyClass {
- var other: MyClass? = null
-}
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/SynthesizedLambdaNameTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/SynthesizedLambdaNameTest.kt
deleted file mode 100644
index 68c361e..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/SynthesizedLambdaNameTest.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-private val topLambda1 = {}
-private val topLambda2 = withArgument {}
-private val topLambda3 = withArguments({}, {})
-
-private fun withArgument(a: (Int) -> Unit = {}): (Int) -> Unit = a
-private fun withArguments(a1: () -> Unit = {}, a2: () -> Unit = {}): List<() -> Unit> =
- listOf(a1, a2)
-
-/**
- * Test the compiler generated lambda names.
- *
- * There is code in Studio that relies on this format.
- * If this test should start to fail, please check the LambdaResolver in the Layout Inspector.
- */
-@SmallTest
-@Suppress("JoinDeclarationAndAssignment")
-class SynthesizedLambdaNameTest {
- private val cls = SynthesizedLambdaNameTest::class.java.name
- private val memberLambda1 = {}
- private val memberLambda2 = withArgument {}
- private val memberLambda3 = withArguments({}, {})
- private val initLambda1: (Int) -> Unit
- private val initLambda2: (Int) -> Unit
- private val defaultLambda1 = withArgument()
- private val defaultLambda2 = withArguments()
-
- init {
- initLambda1 = withArgument {}
- initLambda2 = withArgument {}
- }
-
- @Test
- fun testSynthesizedNames() {
- assertThat(name(topLambda1)).isEqualTo("${cls}Kt\$topLambda1$1")
- assertThat(name(topLambda2)).isEqualTo("${cls}Kt\$topLambda2$1")
- assertThat(name(topLambda3[0])).isEqualTo("${cls}Kt\$topLambda3$1")
- assertThat(name(topLambda3[1])).isEqualTo("${cls}Kt\$topLambda3$2")
- assertThat(name(memberLambda1)).isEqualTo("$cls\$memberLambda1$1")
- assertThat(name(memberLambda2)).isEqualTo("$cls\$memberLambda2$1")
- assertThat(name(memberLambda3[0])).isEqualTo("$cls\$memberLambda3$1")
- assertThat(name(memberLambda3[1])).isEqualTo("$cls\$memberLambda3$2")
- assertThat(name(initLambda1)).isEqualTo("$cls$1")
- assertThat(name(initLambda2)).isEqualTo("$cls$2")
- assertThat(name(defaultLambda1)).isEqualTo("${cls}Kt\$withArgument$1")
- assertThat(name(defaultLambda2[0])).isEqualTo("${cls}Kt\$withArguments$1")
- assertThat(name(defaultLambda2[1])).isEqualTo("${cls}Kt\$withArguments$2")
- }
-
- private fun name(lambda: Any) = lambda.javaClass.name
-}
\ No newline at end of file
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InlineClassConverter.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InlineClassConverter.kt
deleted file mode 100644
index e80975f..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InlineClassConverter.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-/**
- * Converter for casting a parameter represented by its primitive value to its inline class type.
- *
- * For example: an androidx.compose.ui.graphics.Color instance is often represented by a long
- */
-internal class InlineClassConverter {
- // Map from inline type name to inline class and conversion lambda
- private val typeMap = mutableMapOf<String, (Any) -> Any>()
- // Return value used in functions
- private val notInlineType: (Any) -> Any = { it }
-
- /**
- * Clear any cached data.
- */
- fun clear() {
- typeMap.clear()
- }
-
- /**
- * Cast the specified [value] to a value of type [inlineClassName] if possible.
- *
- * @param inlineClassName the fully qualified name of the inline class.
- * @param value the value to convert to an instance of [inlineClassName].
- */
- fun castParameterValue(inlineClassName: String?, value: Any?): Any? =
- if (value != null && inlineClassName != null)
- typeMapperFor(inlineClassName)(value) else value
-
- private fun typeMapperFor(typeName: String): (Any) -> (Any) =
- typeMap.getOrPut(typeName) { loadTypeMapper(typeName.replace('.', '/')) }
-
- private fun loadTypeMapper(className: String): (Any) -> Any {
- val javaClass = loadClassOrNull(className) ?: return notInlineType
- val create = javaClass.declaredConstructors.singleOrNull() ?: return notInlineType
- create.isAccessible = true
- return { value -> create.newInstance(value) }
- }
-
- private fun loadClassOrNull(className: String): Class<*>? =
- try {
- javaClass.classLoader!!.loadClass(className)
- } catch (ex: Exception) {
- null
- }
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt
deleted file mode 100644
index ae66150..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import androidx.compose.ui.layout.LayoutInfo
-
-private val EmptyIntArray = IntArray(0)
-
-/**
- * Node representing a Composable for the Layout Inspector.
- */
-class InspectorNode internal constructor(
- /**
- * The associated render node id or 0.
- */
- val id: Long,
-
- /**
- * The name of the Composable.
- */
- val name: String,
-
- /**
- * The fileName where the Composable was called.
- */
- val fileName: String,
-
- /**
- * A hash of the package name to help disambiguate duplicate [fileName] values.
- *
- * This hash is calculated by,
- *
- * `packageName.fold(0) { hash, current -> hash * 31 + current.toInt() }?.absoluteValue`
- *
- * where the package name is the dotted name of the package. This can be used to disambiguate
- * which file is referenced by [fileName]. This number is -1 if there was no package hash
- * information generated such as when the file does not contain a package declaration.
- */
- val packageHash: Int,
-
- /**
- * The line number where the Composable was called.
- */
- val lineNumber: Int,
-
- /**
- * The UTF-16 offset in the file where the Composable was called
- */
- val offset: Int,
-
- /**
- * The number of UTF-16 code point comprise the Composable call
- */
- val length: Int,
-
- /**
- * Left side of the Composable in pixels.
- */
- val left: Int,
-
- /**
- * Top of the Composable in pixels.
- */
- val top: Int,
-
- /**
- * Width of the Composable in pixels.
- */
- val width: Int,
-
- /**
- * Width of the Composable in pixels.
- */
- val height: Int,
-
- /**
- * The 4 corners of the node after modifier transformations.
- * If there are no coordinate transforms the array will be empty.
- * Otherwise the content will be 8 integers representing 4 (x,y) corners
- * in clockwise order of the original, untransformed coordinates.
- */
- val bounds: IntArray,
-
- /**
- * The parameters of this Composable.
- */
- val parameters: List<RawParameter>,
-
- /**
- * The children nodes of this Composable.
- */
- val children: List<InspectorNode>
-)
-
-/**
- * Parameter definition with a raw value reference.
- */
-class RawParameter(val name: String, val value: Any?)
-
-/**
- * Mutable version of [InspectorNode].
- */
-internal class MutableInspectorNode {
- var id = 0L
- var layoutNodes = mutableListOf<LayoutInfo>()
- var name = ""
- var fileName = ""
- var packageHash = -1
- var lineNumber = 0
- var offset = 0
- var length = 0
- var left = 0
- var top = 0
- var width = 0
- var height = 0
- var bounds = EmptyIntArray
- val parameters = mutableListOf<RawParameter>()
- val children = mutableListOf<InspectorNode>()
-
- fun reset() {
- markUnwanted()
- id = 0L
- left = 0
- top = 0
- width = 0
- height = 0
- layoutNodes.clear()
- bounds = EmptyIntArray
- children.clear()
- }
-
- fun markUnwanted() {
- name = ""
- fileName = ""
- packageHash = -1
- lineNumber = 0
- offset = 0
- length = 0
- parameters.clear()
- }
-
- fun shallowCopy(node: InspectorNode): MutableInspectorNode = apply {
- id = node.id
- name = node.name
- fileName = node.fileName
- packageHash = node.packageHash
- lineNumber = node.lineNumber
- offset = node.offset
- length = node.length
- left = node.left
- top = node.top
- width = node.width
- height = node.height
- bounds = node.bounds
- parameters.addAll(node.parameters)
- children.addAll(node.children)
- }
-
- fun build(): InspectorNode =
- InspectorNode(
- id, name, fileName, packageHash, lineNumber, offset, length, left, top, width, height,
- bounds, parameters.toList(), children.toList()
- )
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt
deleted file mode 100644
index 85f0933..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import android.view.View
-import androidx.compose.runtime.tooling.CompositionData
-import androidx.compose.runtime.InternalComposeApi
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.layout.GraphicLayerInfo
-import androidx.compose.ui.layout.LayoutInfo
-import androidx.compose.ui.tooling.R
-import androidx.compose.ui.tooling.data.Group
-import androidx.compose.ui.tooling.data.NodeGroup
-import androidx.compose.ui.tooling.data.ParameterInformation
-import androidx.compose.ui.tooling.data.UiToolingDataApi
-import androidx.compose.ui.tooling.data.asTree
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.toSize
-import java.util.ArrayDeque
-import java.util.Collections
-import java.util.IdentityHashMap
-import kotlin.math.absoluteValue
-import kotlin.math.roundToInt
-
-private val systemPackages = setOf(
- -1,
- packageNameHash("androidx.compose.animation"),
- packageNameHash("androidx.compose.animation.core"),
- packageNameHash("androidx.compose.desktop"),
- packageNameHash("androidx.compose.foundation"),
- packageNameHash("androidx.compose.foundation.layout"),
- packageNameHash("androidx.compose.foundation.text"),
- packageNameHash("androidx.compose.material"),
- packageNameHash("androidx.compose.material.ripple"),
- packageNameHash("androidx.compose.runtime"),
- packageNameHash("androidx.compose.ui"),
- packageNameHash("androidx.compose.ui.layout"),
- packageNameHash("androidx.compose.ui.platform"),
- packageNameHash("androidx.compose.ui.tooling"),
- packageNameHash("androidx.compose.ui.selection"),
- packageNameHash("androidx.compose.ui.semantics"),
- packageNameHash("androidx.compose.ui.viewinterop"),
- packageNameHash("androidx.compose.ui.window"),
-)
-
-private val unwantedCalls = setOf(
- "emit",
- "remember",
- "CompositionLocalProvider",
- "Content",
- "Inspectable",
- "ProvideAndroidCompositionLocals",
- "ProvideCommonCompositionLocals",
-)
-
-private fun packageNameHash(packageName: String) =
- packageName.fold(0) { hash, char -> hash * 31 + char.toInt() }.absoluteValue
-
-/**
- * Generator of a tree for the Layout Inspector.
- */
-class LayoutInspectorTree {
- @Suppress("MemberVisibilityCanBePrivate")
- var hideSystemNodes = true
- private val inlineClassConverter = InlineClassConverter()
- private val parameterFactory = ParameterFactory(inlineClassConverter)
- private val cache = ArrayDeque<MutableInspectorNode>()
- private var generatedId = -1L
- /** Map from [LayoutInfo] to the nearest [InspectorNode] that contains it */
- private val claimedNodes = IdentityHashMap<LayoutInfo, InspectorNode>()
- /** Map from parent tree to child trees that are about to be stitched together */
- private val treeMap = IdentityHashMap<MutableInspectorNode, MutableList<MutableInspectorNode>>()
- /** Map from owner node to child trees that are about to be stitched to this owner */
- private val ownerMap = IdentityHashMap<InspectorNode, MutableList<MutableInspectorNode>>()
- /** Set of tree nodes that were stitched into another tree */
- private val stitched =
- Collections.newSetFromMap(IdentityHashMap<MutableInspectorNode, Boolean>())
-
- /**
- * Converts the [CompositionData] set held by [view] into a list of root nodes.
- */
- @OptIn(InternalComposeApi::class)
- fun convert(view: View): List<InspectorNode> {
- parameterFactory.density = Density(view.context)
- @Suppress("UNCHECKED_CAST")
- val tables = view.getTag(R.id.inspection_slot_table_set) as? Set<CompositionData>
- ?: return emptyList()
- clear()
- val result = convert(tables)
- clear()
- return result
- }
-
- /**
- * Converts the [RawParameter]s of the [node] into displayable parameters.
- */
- fun convertParameters(node: InspectorNode): List<NodeParameter> {
- return node.parameters.mapNotNull { parameterFactory.create(node, it.name, it.value) }
- }
-
- /**
- * Reset the generated id. Nodes are assigned an id if there isn't a layout node id present.
- */
- @Suppress("unused")
- fun resetGeneratedId() {
- generatedId = -1L
- }
-
- private fun clear() {
- cache.clear()
- inlineClassConverter.clear()
- claimedNodes.clear()
- treeMap.clear()
- ownerMap.clear()
- stitched.clear()
- }
-
- @OptIn(InternalComposeApi::class)
- private fun convert(tables: Set<CompositionData>): List<InspectorNode> {
- val trees = tables.map { convert(it) }
- return when (trees.size) {
- 0 -> listOf()
- 1 -> addTree(mutableListOf(), trees.single())
- else -> stitchTreesByLayoutInfo(trees)
- }
- }
-
- /**
- * Stitch separate trees together using the [LayoutInfo]s found in the [CompositionData]s.
- *
- * Some constructs in Compose (e.g. ModalDrawer) will result is multiple
- * [CompositionData]s. This code will attempt to stitch the resulting [InspectorNode] trees
- * together by looking at the parent of each [LayoutInfo].
- *
- * If this algorithm is successful the result of this function will be a list with a single
- * tree.
- */
- private fun stitchTreesByLayoutInfo(trees: List<MutableInspectorNode>): List<InspectorNode> {
- val layoutToTreeMap = IdentityHashMap<LayoutInfo, MutableInspectorNode>()
- trees.forEach { tree -> tree.layoutNodes.forEach { layoutToTreeMap[it] = tree } }
- trees.forEach { tree ->
- val layout = tree.layoutNodes.lastOrNull()
- val parentLayout = generateSequence(layout) { it.parentInfo }.firstOrNull {
- val otherTree = layoutToTreeMap[it]
- otherTree != null && otherTree != tree
- }
- if (parentLayout != null) {
- val ownerNode = claimedNodes[parentLayout]
- val ownerTree = layoutToTreeMap[parentLayout]
- if (ownerNode != null && ownerTree != null) {
- ownerMap.getOrPut(ownerNode) { mutableListOf() }.add(tree)
- treeMap.getOrPut(ownerTree) { mutableListOf() }.add(tree)
- }
- }
- }
- var parentTree = findDeepParentTree()
- while (parentTree != null) {
- addSubTrees(parentTree)
- treeMap.remove(parentTree)
- parentTree = findDeepParentTree()
- }
- val result = mutableListOf<InspectorNode>()
- trees.asSequence().filter { !stitched.contains(it) }.forEach { addTree(result, it) }
- return result
- }
-
- /**
- * Return a parent tree where the children trees (to be stitched under the parent) are not
- * a parent themselves. Do this to avoid rebuilding the same tree more than once.
- */
- private fun findDeepParentTree(): MutableInspectorNode? =
- treeMap.entries.asSequence()
- .filter { (_, children) -> children.none { treeMap.containsKey(it) } }
- .firstOrNull()?.key
-
- private fun addSubTrees(tree: MutableInspectorNode) {
- for ((index, child) in tree.children.withIndex()) {
- tree.children[index] = addSubTrees(child) ?: child
- }
- }
-
- /**
- * Rebuild [node] with any possible sub trees added (stitched in).
- * Return the rebuild node, or null if no changes were found in this node or its children.
- * Lazily allocate the new node to avoid unnecessary allocations.
- */
- private fun addSubTrees(node: InspectorNode): InspectorNode? {
- var newNode: MutableInspectorNode? = null
- for ((index, child) in node.children.withIndex()) {
- val newChild = addSubTrees(child)
- if (newChild != null) {
- val newCopy = newNode ?: newNode(node)
- newCopy.children[index] = newChild
- newNode = newCopy
- }
- }
- val trees = ownerMap[node]
- if (trees == null && newNode == null) {
- return null
- }
- val newCopy = newNode ?: newNode(node)
- if (trees != null) {
- trees.forEach { addTree(newCopy.children, it) }
- stitched.addAll(trees)
- }
- return buildAndRelease(newCopy)
- }
-
- /**
- * Add [tree] to the end of the [out] list.
- * The root nodes of [tree] may be a fake node that hold a list of [LayoutInfo].
- */
- private fun addTree(
- out: MutableList<InspectorNode>,
- tree: MutableInspectorNode
- ): List<InspectorNode> {
- tree.children.forEach {
- if (it.name.isNotEmpty()) {
- out.add(it)
- } else {
- out.addAll(it.children)
- }
- }
- return out
- }
-
- @OptIn(InternalComposeApi::class, UiToolingDataApi::class)
- private fun convert(table: CompositionData): MutableInspectorNode {
- val fakeParent = newNode()
- addToParent(fakeParent, listOf(convert(table.asTree())), buildFakeChildNodes = true)
- return fakeParent
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun convert(group: Group): MutableInspectorNode {
- val children = convertChildren(group)
- val parent = parse(group)
- addToParent(parent, children)
- return parent
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun convertChildren(group: Group): List<MutableInspectorNode> {
- if (group.children.isEmpty()) {
- return emptyList()
- }
- val result = mutableListOf<MutableInspectorNode>()
- for (child in group.children) {
- val node = convert(child)
- if (node.name.isNotEmpty() || node.children.isNotEmpty() ||
- node.id != 0L || node.layoutNodes.isNotEmpty()
- ) {
- result.add(node)
- } else {
- release(node)
- }
- }
- return result
- }
-
- /**
- * Adds the nodes in [input] to the children of [parentNode].
- * Nodes without a reference to a wanted Composable are skipped unless [buildFakeChildNodes].
- * A single skipped render id and layoutNode will be added to [parentNode].
- */
- private fun addToParent(
- parentNode: MutableInspectorNode,
- input: List<MutableInspectorNode>,
- buildFakeChildNodes: Boolean = false
- ) {
- var id: Long? = null
- input.forEach { node ->
- if (node.name.isEmpty() && !(buildFakeChildNodes && node.layoutNodes.isNotEmpty())) {
- parentNode.children.addAll(node.children)
- if (node.id != 0L) {
- // If multiple siblings with a render ids are dropped:
- // Ignore them all. And delegate the drawing to a parent in the inspector.
- id = if (id == null) node.id else 0L
- }
- } else {
- node.id = if (node.id != 0L) node.id else --generatedId
- val resultNode = node.build()
- // TODO: replace getOrPut with putIfAbsent which requires API level 24
- node.layoutNodes.forEach { claimedNodes.getOrPut(it) { resultNode } }
- parentNode.children.add(resultNode)
- }
- if (node.bounds.isNotEmpty() && sameBoundingRectangle(parentNode, node)) {
- parentNode.bounds = node.bounds
- }
- parentNode.layoutNodes.addAll(node.layoutNodes)
- release(node)
- }
- val nodeId = id
- parentNode.id = if (parentNode.id == 0L && nodeId != null) nodeId else parentNode.id
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun parse(group: Group): MutableInspectorNode {
- val node = newNode()
- node.id = getRenderNode(group)
- parsePosition(group, node)
- parseLayoutInfo(group, node)
- if (node.height <= 0 && node.width <= 0) {
- return markUnwanted(node)
- }
- if (!parseCallLocation(group, node) && group.name.isNullOrEmpty()) {
- return markUnwanted(node)
- }
- group.name?.let { node.name = it }
- if (unwantedGroup(node)) {
- return markUnwanted(node)
- }
- addParameters(group.parameters, node)
- return node
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun parsePosition(group: Group, node: MutableInspectorNode) {
- val box = group.box
- node.top = box.top
- node.left = box.left
- node.height = box.bottom - box.top
- node.width = box.right - box.left
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun parseLayoutInfo(group: Group, node: MutableInspectorNode) {
- val layoutInfo = (group as? NodeGroup)?.node as? LayoutInfo ?: return
- node.layoutNodes.add(layoutInfo)
- val box = group.box
- val size = box.size.toSize()
- val coordinates = layoutInfo.coordinates
- val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
- val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
- val bottomRight = toIntOffset(coordinates.localToWindow(Offset(size.width, size.height)))
- val bottomLeft = toIntOffset(coordinates.localToWindow(Offset(0f, size.height)))
- if (
- topLeft.x == box.left && topLeft.y == box.top &&
- topRight.x == box.right && topRight.y == box.top &&
- bottomRight.x == box.right && bottomRight.y == box.bottom &&
- bottomLeft.x == box.left && bottomLeft.y == box.bottom
- ) {
- return
- }
- node.bounds = intArrayOf(
- topLeft.x, topLeft.y,
- topRight.x, topRight.y,
- bottomRight.x, bottomRight.y,
- bottomLeft.x, bottomLeft.y
- )
- }
-
- private fun toIntOffset(offset: Offset): IntOffset =
- IntOffset(offset.x.roundToInt(), offset.y.roundToInt())
-
- private fun markUnwanted(node: MutableInspectorNode): MutableInspectorNode =
- node.apply { markUnwanted() }
-
- @OptIn(UiToolingDataApi::class)
- private fun parseCallLocation(group: Group, node: MutableInspectorNode): Boolean {
- val location = group.location ?: return false
- val fileName = location.sourceFile ?: return false
- node.fileName = fileName
- node.packageHash = location.packageHash
- node.lineNumber = location.lineNumber
- node.offset = location.offset
- node.length = location.length
- return true
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun getRenderNode(group: Group): Long =
- group.modifierInfo.asSequence()
- .map { it.extra }
- .filterIsInstance<GraphicLayerInfo>()
- .map { it.layerId }
- .firstOrNull() ?: 0
-
- @OptIn(UiToolingDataApi::class)
- private fun addParameters(parameters: List<ParameterInformation>, node: MutableInspectorNode) =
- parameters.forEach { addParameter(it, node) }
-
- @OptIn(UiToolingDataApi::class)
- private fun addParameter(parameter: ParameterInformation, node: MutableInspectorNode) {
- val castedValue = castValue(parameter)
- node.parameters.add(RawParameter(parameter.name, castedValue))
- }
-
- @OptIn(UiToolingDataApi::class)
- private fun castValue(parameter: ParameterInformation): Any? {
- val value = parameter.value ?: return null
- if (parameter.inlineClass == null) return value
- return inlineClassConverter.castParameterValue(parameter.inlineClass, value)
- }
-
- private fun unwantedGroup(node: MutableInspectorNode): Boolean =
- node.packageHash in systemPackages && (hideSystemNodes || node.name in unwantedCalls)
-
- private fun newNode(): MutableInspectorNode =
- if (cache.isNotEmpty()) cache.pop() else MutableInspectorNode()
-
- private fun newNode(copyFrom: InspectorNode): MutableInspectorNode =
- newNode().shallowCopy(copyFrom)
-
- private fun release(node: MutableInspectorNode) {
- node.reset()
- cache.add(node)
- }
-
- private fun buildAndRelease(node: MutableInspectorNode): InspectorNode {
- val result = node.build()
- release(node)
- return result
- }
-
- private fun sameBoundingRectangle(
- node1: MutableInspectorNode,
- node2: MutableInspectorNode
- ): Boolean =
- node1.left == node2.left &&
- node1.top == node2.top &&
- node1.width == node2.width &&
- node1.height == node2.height
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/NodeParameter.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/NodeParameter.kt
deleted file mode 100644
index 86bd7ad..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/NodeParameter.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-/**
- * Holds data representing a Composable parameter for the Layout Inspector.
- */
-class NodeParameter internal constructor(
- /**
- * The name of the parameter.
- */
- val name: String,
-
- /**
- * The type of the parameter.
- */
- val type: ParameterType,
-
- /**
- * The value of the parameter.
- */
- val value: Any?
-) {
- /**
- * Sub elements of the parameter.
- */
- val elements = mutableListOf<NodeParameter>()
-}
-
-/**
- * The type of a parameter.
- */
-enum class ParameterType {
- String,
- Boolean,
- Double,
- Float,
- Int32,
- Int64,
- Color,
- Resource,
- DimensionDp,
- DimensionSp,
- DimensionEm,
- Lambda,
- FunctionReference,
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt
deleted file mode 100644
index 0e935b5..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt
+++ /dev/null
@@ -1,481 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import android.util.Log
-import android.view.View
-import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.runtime.internal.ComposableLambda
-import androidx.compose.ui.AbsoluteAlignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.platform.InspectableValue
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.font.FontListFontFamily
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.font.ResourceFont
-import androidx.compose.ui.text.intl.Locale
-import androidx.compose.ui.text.intl.LocaleList
-import androidx.compose.ui.text.style.BaselineShift
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.tooling.inspector.ParameterType.DimensionDp
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.TextUnitType
-import java.lang.reflect.Field
-import kotlin.jvm.internal.FunctionReference
-import kotlin.jvm.internal.Lambda
-import kotlin.math.abs
-import kotlin.reflect.KClass
-import kotlin.reflect.KProperty1
-import kotlin.reflect.full.allSuperclasses
-import kotlin.reflect.full.declaredMemberProperties
-import kotlin.reflect.jvm.isAccessible
-import kotlin.reflect.jvm.javaField
-import kotlin.reflect.jvm.javaGetter
-import java.lang.reflect.Modifier as JavaModifier
-
-private const val MAX_RECURSIONS = 10
-private const val MAX_ITERABLE = 25
-
-/**
- * Factory of [NodeParameter]s.
- *
- * Each parameter value is converted to a user readable value.
- */
-internal class ParameterFactory(private val inlineClassConverter: InlineClassConverter) {
- /**
- * A map from known values to a user readable string representation.
- */
- private val valueLookup = mutableMapOf<Any, String>()
-
- /**
- * The classes we have loaded constants from.
- */
- private val valuesLoaded = mutableSetOf<Class<*>>()
-
- /**
- * Do not load constant names from instances of these classes.
- * We prefer showing the raw values of Color and Dimensions.
- */
- private val ignoredClasses = listOf(Color::class.java, Dp::class.java)
- private var creatorCache: ParameterCreator? = null
- private val kotlinReflectionSupported = try {
- Class.forName("kotlin.reflect.full.KClasses")
- true
- } catch (ex: Exception) {
- false
- }
-
- /**
- * Do not decompose instances or lookup constants from these package prefixes
- *
- * The following instances are known to contain self recursion:
- * - kotlinx.coroutines.flow.StateFlowImpl
- * - androidx.compose.ui.node.LayoutNode
- */
- private val ignoredPackagePrefixes = listOf(
- "android.", "java.", "javax.", "kotlinx.", "androidx.compose.ui.node."
- )
-
- var density = Density(1.0f)
-
- init {
- val textDecorationCombination = TextDecoration.combine(
- listOf(TextDecoration.LineThrough, TextDecoration.Underline)
- )
- valueLookup[textDecorationCombination] = "LineThrough+Underline"
- valueLookup[Color.Unspecified] = "Unspecified"
- valueLookup[RectangleShape] = "RectangleShape"
- valuesLoaded.add(Enum::class.java)
- valuesLoaded.add(Any::class.java)
-
- // AbsoluteAlignment is not found from an instance of BiasAbsoluteAlignment,
- // because Alignment has no file level class.
- loadConstantsFromEnclosedClasses(AbsoluteAlignment::class.java)
- }
-
- /**
- * Create a [NodeParameter] from the specified parameter [name] and [value].
- *
- * Attempt to convert the value to a user readable value.
- * For now: return null when a conversion is not possible/found.
- */
- fun create(node: InspectorNode, name: String, value: Any?): NodeParameter? {
- val creator = creatorCache ?: ParameterCreator()
- try {
- return creator.create(node, name, value)
- } finally {
- creatorCache = creator
- }
- }
-
- private fun loadConstantsFrom(javaClass: Class<*>) {
- if (valuesLoaded.contains(javaClass) ||
- ignoredPackagePrefixes.any { javaClass.name.startsWith(it) }
- ) {
- return
- }
- val related = generateSequence(javaClass) { it.superclass }.plus(javaClass.interfaces)
- related.forEach { aClass ->
- val topClass = generateSequence(aClass) { safeEnclosingClass(it) }.last()
- loadConstantsFromEnclosedClasses(topClass)
- findPackageLevelClass(topClass)?.let { loadConstantsFromStaticFinal(it) }
- }
- }
-
- private fun safeEnclosingClass(klass: Class<*>): Class<*>? = try {
- klass.enclosingClass
- } catch (_: Error) {
- // Exceptions seen on API 23...
- null
- }
-
- private fun findPackageLevelClass(javaClass: Class<*>): Class<*>? = try {
- // Note: This doesn't work when @file.JvmName is specified
- Class.forName("${javaClass.name}Kt")
- } catch (ex: Throwable) {
- null
- }
-
- private fun loadConstantsFromEnclosedClasses(javaClass: Class<*>) {
- if (valuesLoaded.contains(javaClass)) {
- return
- }
- loadConstantsFromObjectInstance(javaClass.kotlin)
- loadConstantsFromStaticFinal(javaClass)
- valuesLoaded.add(javaClass)
- javaClass.declaredClasses.forEach { loadConstantsFromEnclosedClasses(it) }
- }
-
- /**
- * Load all constants from companion objects and singletons
- *
- * Exclude: primary types and types of ignoredClasses, open and lateinit vals.
- */
- private fun loadConstantsFromObjectInstance(kClass: KClass<*>) {
- try {
- val instance = kClass.objectInstance ?: return
- kClass.declaredMemberProperties.asSequence()
- .filter { it.isFinal && !it.isLateinit }
- .mapNotNull { constantValueOf(it, instance)?.let { key -> Pair(key, it.name) } }
- .filter { !ignoredValue(it.first) }
- .toMap(valueLookup)
- } catch (_: Throwable) {
- // KT-16479 : kotlin reflection does currently not support packages and files.
- // We load top level values using Java reflection instead.
- // Ignore other reflection errors as well
- }
- }
-
- /**
- * Load all constants from top level values from Java.
- *
- * Exclude: primary types and types of ignoredClasses.
- * Since this is Java, inline types will also (unfortunately) be excluded.
- */
- private fun loadConstantsFromStaticFinal(javaClass: Class<*>) {
- try {
- javaClass.declaredMethods.asSequence()
- .filter {
- it.returnType != Void.TYPE &&
- JavaModifier.isStatic(it.modifiers) &&
- JavaModifier.isFinal(it.modifiers) &&
- !it.returnType.isPrimitive &&
- it.parameterTypes.isEmpty() &&
- it.name.startsWith("get")
- }
- .mapNotNull { javaClass.getDeclaredField(it.name.substring(3)) }
- .mapNotNull { constantValueOf(it)?.let { key -> Pair(key, it.name) } }
- .filter { !ignoredValue(it.first) }
- .toMap(valueLookup)
- } catch (_: ReflectiveOperationException) {
- // ignore reflection errors
- } catch (_: NoClassDefFoundError) {
- // ignore missing classes on lower level SDKs
- }
- }
-
- private fun constantValueOf(field: Field?): Any? = try {
- field?.isAccessible = true
- field?.get(null)
- } catch (_: ReflectiveOperationException) {
- // ignore reflection errors
- null
- }
-
- private fun constantValueOf(property: KProperty1<out Any, *>, instance: Any): Any? = try {
- val field = property.javaField
- field?.isAccessible = true
- inlineClassConverter.castParameterValue(inlineResultClass(property), field?.get(instance))
- } catch (_: ReflectiveOperationException) {
- // ignore reflection errors
- null
- }
-
- private fun inlineResultClass(property: KProperty1<out Any, *>): String? {
- // The Java getter name will be mangled if it contains parameters of an inline class.
- // The mangled part starts with a '-'.
- if (property.javaGetter?.name?.contains('-') == true) {
- return property.returnType.toString()
- }
- return null
- }
-
- private fun ignoredValue(value: Any?): Boolean =
- value == null ||
- ignoredClasses.any { ignored -> ignored.isInstance(value) } ||
- value::class.java.isPrimitive
-
- /**
- * Convenience class for building [NodeParameter]s.
- */
- private inner class ParameterCreator {
- private var node: InspectorNode? = null
- private var recursions = 0
-
- fun create(node: InspectorNode, name: String, value: Any?): NodeParameter? = try {
- this.node = node
- recursions = 0
- create(name, value)
- } finally {
- this.node = null
- }
-
- private fun create(name: String, value: Any?): NodeParameter? {
- if (value == null || recursions >= MAX_RECURSIONS) {
- return null
- }
- try {
- recursions++
- createFromConstant(name, value)?.let { return it }
- return when (value) {
- is AnnotatedString -> NodeParameter(name, ParameterType.String, value.text)
- is BaselineShift -> createFromBaselineShift(name, value)
- is Boolean -> NodeParameter(name, ParameterType.Boolean, value)
- is ComposableLambda -> createFromCLambda(name, value)
- is Color -> NodeParameter(name, ParameterType.Color, value.toArgb())
- is CornerSize -> createFromCornerSize(name, value)
- is Double -> NodeParameter(name, ParameterType.Double, value)
- is Dp -> NodeParameter(name, DimensionDp, value.value)
- is Enum<*> -> NodeParameter(name, ParameterType.String, value.toString())
- is Float -> NodeParameter(name, ParameterType.Float, value)
- is FunctionReference -> NodeParameter(
- name, ParameterType.FunctionReference, arrayOf<Any>(value, value.name)
- )
- is FontListFontFamily -> createFromFontListFamily(name, value)
- is FontWeight -> NodeParameter(name, ParameterType.Int32, value.weight)
- is Modifier -> createFromModifier(name, value)
- is InspectableValue -> createFromInspectableValue(name, value)
- is Int -> NodeParameter(name, ParameterType.Int32, value)
- is Iterable<*> -> createFromIterable(name, value)
- is Lambda<*> -> createFromLambda(name, value)
- is Locale -> NodeParameter(name, ParameterType.String, value.toString())
- is LocaleList ->
- NodeParameter(name, ParameterType.String, value.localeList.joinToString())
- is Long -> NodeParameter(name, ParameterType.Int64, value)
- is Offset -> createFromOffset(name, value)
- is Shadow -> createFromShadow(name, value)
- is SolidColor -> NodeParameter(name, ParameterType.Color, value.value.toArgb())
- is String -> NodeParameter(name, ParameterType.String, value)
- is TextUnit -> createFromTextUnit(name, value)
- is ImageVector -> createFromImageVector(name, value)
- is View -> NodeParameter(name, ParameterType.String, value.javaClass.simpleName)
- else -> createFromKotlinReflection(name, value)
- }
- } finally {
- recursions--
- }
- }
-
- private fun createFromBaselineShift(name: String, value: BaselineShift): NodeParameter {
- val converted = when (value.multiplier) {
- BaselineShift.None.multiplier -> "None"
- BaselineShift.Subscript.multiplier -> "Subscript"
- BaselineShift.Superscript.multiplier -> "Superscript"
- else -> return NodeParameter(name, ParameterType.Float, value.multiplier)
- }
- return NodeParameter(name, ParameterType.String, converted)
- }
-
- private fun createFromCLambda(name: String, value: ComposableLambda): NodeParameter? = try {
- val lambda = value.javaClass.getDeclaredField("_block")
- .apply { isAccessible = true }
- .get(value)
- NodeParameter(name, ParameterType.Lambda, arrayOf<Any?>(lambda))
- } catch (_: Throwable) {
- null
- }
-
- private fun createFromConstant(name: String, value: Any): NodeParameter? {
- if (!kotlinReflectionSupported) {
- return null
- }
- loadConstantsFrom(value.javaClass)
- return valueLookup[value]?.let { NodeParameter(name, ParameterType.String, it) }
- }
-
- private fun createFromCornerSize(name: String, value: CornerSize): NodeParameter {
- val size = Size(node!!.width.toFloat(), node!!.height.toFloat())
- val pixels = value.toPx(size, density)
- return NodeParameter(name, DimensionDp, with(density) { pixels.toDp().value })
- }
-
- // For now: select ResourceFontFont closest to W400 and Normal, and return the resId
- private fun createFromFontListFamily(
- name: String,
- value: FontListFontFamily
- ): NodeParameter? =
- findBestResourceFont(value)?.let {
- NodeParameter(name, ParameterType.Resource, it.resId)
- }
-
- private fun createFromKotlinReflection(name: String, value: Any): NodeParameter? {
- val kClass = value::class
- val qualifiedName = kClass.qualifiedName
- if (kClass.simpleName == null ||
- qualifiedName == null ||
- ignoredPackagePrefixes.any { qualifiedName.startsWith(it) } ||
- !kotlinReflectionSupported
- ) {
- // Exit without creating a parameter for:
- // - internal synthetic classes
- // - certain android packages
- // - if kotlin reflection library not available
- return null
- }
- val parameter = NodeParameter(name, ParameterType.String, kClass.simpleName)
- val properties = mutableMapOf<String, KProperty1<Any, *>>()
- try {
- sequenceOf(kClass).plus(kClass.allSuperclasses.asSequence())
- .flatMap { it.declaredMemberProperties.asSequence() }
- .filterIsInstance<KProperty1<Any, *>>()
- .associateByTo(properties) { it.name }
- } catch (ex: Throwable) {
- Log.w("Compose", "Could not decompose ${kClass.simpleName}")
- return parameter
- }
- properties.values.mapNotNullTo(parameter.elements) {
- create(it.name, valueOf(it, value))
- }
- return parameter
- }
-
- private fun valueOf(property: KProperty1<Any, *>, instance: Any): Any? = try {
- property.isAccessible = true
- // Bug in kotlin reflection API: if the type is a nullable inline type with a null
- // value, we get an IllegalArgumentException in this line:
- property.get(instance)
- } catch (ex: Throwable) {
- // TODO: Remove this warning since this is expected with nullable inline types
- Log.w("Compose", "Could not get value of ${property.name}")
- null
- }
-
- private fun createFromInspectableValue(
- name: String,
- value: InspectableValue
- ): NodeParameter {
- val tempValue = value.valueOverride ?: ""
- val parameterName = name.ifEmpty { value.nameFallback } ?: "element"
- val parameterValue = if (tempValue is InspectableValue) "" else tempValue
- val parameter = create(parameterName, parameterValue)
- ?: NodeParameter(parameterName, ParameterType.String, "")
- val elements = parameter.elements
- value.inspectableElements.mapNotNullTo(elements) { create(it.name, it.value) }
- return parameter
- }
-
- private fun createFromIterable(name: String, value: Iterable<*>): NodeParameter {
- val parameter = NodeParameter(name, ParameterType.String, "")
- val elements = parameter.elements
- value.asSequence()
- .mapNotNull { create(elements.size.toString(), it) }
- .takeWhile { elements.size < MAX_ITERABLE }
- .toCollection(elements)
- return parameter
- }
-
- private fun createFromLambda(name: String, value: Lambda<*>): NodeParameter =
- NodeParameter(name, ParameterType.Lambda, arrayOf<Any>(value))
-
- private fun createFromModifier(name: String, value: Modifier): NodeParameter? =
- when {
- name.isNotEmpty() -> {
- val parameter = NodeParameter(name, ParameterType.String, "")
- val elements = parameter.elements
- value.foldIn(elements) { acc, m ->
- create("", m)?.let { param -> acc.apply { add(param) } } ?: acc
- }
- parameter
- }
- value is InspectableValue -> createFromInspectableValue(name, value)
- else -> null
- }
-
- private fun createFromOffset(name: String, value: Offset): NodeParameter {
- val parameter = NodeParameter(name, ParameterType.String, Offset::class.java.simpleName)
- val elements = parameter.elements
- elements.add(NodeParameter("x", DimensionDp, with(density) { value.x.toDp().value }))
- elements.add(NodeParameter("y", DimensionDp, with(density) { value.y.toDp().value }))
- return parameter
- }
-
- // Special handling of blurRadius: convert to dp:
- private fun createFromShadow(name: String, value: Shadow): NodeParameter? {
- val parameter = createFromKotlinReflection(name, value) ?: return null
- val elements = parameter.elements
- val index = elements.indexOfFirst { it.name == "blurRadius" }
- if (index >= 0) {
- val blurRadius = with(density) { value.blurRadius.toDp().value }
- elements[index] = NodeParameter("blurRadius", DimensionDp, blurRadius)
- }
- return parameter
- }
-
- @Suppress("DEPRECATION")
- private fun createFromTextUnit(name: String, value: TextUnit): NodeParameter =
- when (value.type) {
- TextUnitType.Sp -> NodeParameter(name, ParameterType.DimensionSp, value.value)
- TextUnitType.Em -> NodeParameter(name, ParameterType.DimensionEm, value.value)
- TextUnitType.Unspecified ->
- NodeParameter(name, ParameterType.String, "Unspecified")
- }
-
- private fun createFromImageVector(name: String, value: ImageVector): NodeParameter =
- NodeParameter(name, ParameterType.String, value.name)
-
- /**
- * Select a resource font among the font in the family to represent the font
- *
- * Prefer the font closest to [FontWeight.Normal] and [FontStyle.Normal]
- */
- private fun findBestResourceFont(value: FontListFontFamily): ResourceFont? =
- value.fonts.asSequence().filterIsInstance<ResourceFont>().minByOrNull {
- abs(it.weight.weight - FontWeight.Normal.weight) + it.style.ordinal
- }
- }
-}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
index 335f16c..b15b091 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
@@ -20,7 +20,7 @@
import androidx.compose.runtime.Applier
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ComposeNode
+import androidx.compose.runtime.ReusableComposeNode
import androidx.compose.runtime.SkippableUpdater
import androidx.compose.runtime.currentComposer
import androidx.compose.ui.Modifier
@@ -71,7 +71,7 @@
) {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
- ComposeNode<ComposeUiNode, Applier<Any>>(
+ ReusableComposeNode<ComposeUiNode, Applier<Any>>(
factory = ComposeUiNode.Constructor,
update = {
set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
@@ -192,7 +192,7 @@
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
- ComposeNode<LayoutNode, Applier<Any>>(
+ ReusableComposeNode<LayoutNode, Applier<Any>>(
factory = LayoutNode.Constructor,
update = {
set(materialized, ComposeUiNode.SetModifier)
diff --git a/emoji2/emoji2/api/current.txt b/emoji2/emoji2/api/current.txt
index f69bb70..f723bb9 100644
--- a/emoji2/emoji2/api/current.txt
+++ b/emoji2/emoji2/api/current.txt
@@ -2,7 +2,7 @@
package androidx.emoji2.text {
public final class DefaultEmojiCompatConfig {
- method public static androidx.emoji2.text.EmojiCompat.Config? create(android.content.Context);
+ method public static androidx.emoji2.text.FontRequestEmojiCompatConfig? create(android.content.Context);
}
@AnyThread public class EmojiCompat {
diff --git a/emoji2/emoji2/api/public_plus_experimental_current.txt b/emoji2/emoji2/api/public_plus_experimental_current.txt
index f69bb70..f723bb9 100644
--- a/emoji2/emoji2/api/public_plus_experimental_current.txt
+++ b/emoji2/emoji2/api/public_plus_experimental_current.txt
@@ -2,7 +2,7 @@
package androidx.emoji2.text {
public final class DefaultEmojiCompatConfig {
- method public static androidx.emoji2.text.EmojiCompat.Config? create(android.content.Context);
+ method public static androidx.emoji2.text.FontRequestEmojiCompatConfig? create(android.content.Context);
}
@AnyThread public class EmojiCompat {
diff --git a/emoji2/emoji2/api/restricted_current.txt b/emoji2/emoji2/api/restricted_current.txt
index f69bb70..f723bb9 100644
--- a/emoji2/emoji2/api/restricted_current.txt
+++ b/emoji2/emoji2/api/restricted_current.txt
@@ -2,7 +2,7 @@
package androidx.emoji2.text {
public final class DefaultEmojiCompatConfig {
- method public static androidx.emoji2.text.EmojiCompat.Config? create(android.content.Context);
+ method public static androidx.emoji2.text.FontRequestEmojiCompatConfig? create(android.content.Context);
}
@AnyThread public class EmojiCompat {
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java
index a03db5a..1c9db25 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java
@@ -93,8 +93,9 @@
* could be found.
*/
@Nullable
- public static EmojiCompat.Config create(@NonNull Context context) {
- return new DefaultEmojiCompatConfigFactory(null).create(context);
+ public static FontRequestEmojiCompatConfig create(@NonNull Context context) {
+ return (FontRequestEmojiCompatConfig) new DefaultEmojiCompatConfigFactory(null)
+ .create(context);
}
/**
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
index b5714595..69fd6904 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
@@ -17,8 +17,17 @@
package androidx.emoji2.text;
import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.WorkerThread;
+import androidx.core.os.TraceCompat;
import androidx.startup.Initializer;
import java.util.Collections;
@@ -27,16 +36,21 @@
/**
* Initializer for configuring EmojiCompat with the system installed downloadable font provider.
*
+ * <p>This initializer will initialize EmojiCompat immediately then defer loading the font for a
+ * short delay to avoid delaying application startup. Typically, the font will be loaded shortly
+ * after the first screen of your application loads, which means users may see system emoji
+ * briefly prior to the compat font loading.</p>
+ *
* <p>This is the recommended configuration for all apps that don't need specialized configuration,
- * and don't need to control the thread that initialization runs on. For more information see
- * {@link androidx.emoji2.text.DefaultEmojiCompatConfig}.</p>
+ * and don't need to control the background thread that initialization runs on. For more information
+ * see {@link androidx.emoji2.text.DefaultEmojiCompatConfig}.</p>
*
* <p>In addition to the reasons listed in {@code DefaultEmojiCompatConfig} you may wish to disable
* this automatic configuration if you intend to call initialization from an existing background
* thread pool in your application.</p>
*
- * <p></p>This is enabled by default by including the `:emoji2:emoji2` gradle artifact. To disable
- * the default configuration (and allow manual configuration) add this to your manifest:</p>
+ * <p>This is enabled by default by including the {@code :emoji2:emoji2} gradle artifact. To
+ * disable the default configuration (and allow manual configuration) add this to your manifest:</p>
*
* <pre>
* <provider
@@ -52,6 +66,8 @@
* @see androidx.emoji2.text.DefaultEmojiCompatConfig
*/
public class EmojiCompatInitializer implements Initializer<Boolean> {
+ private static final long STARTUP_THREAD_CREATION_DELAY_MS = 500L;
+ private static final String S_INITIALIZER_THREAD_NAME = "EmojiCompatInitializer";
/**
* Initialize EmojiCompat with the app's context.
@@ -62,8 +78,18 @@
@NonNull
@Override
public Boolean create(@NonNull Context context) {
- // note: super create requires this be non-null, share if the configuration was successful
- // TODO(b/187328685): re-enable this after investigating startup performance
+ if (Build.VERSION.SDK_INT >= 19) {
+ final Handler mainHandler;
+ if (Build.VERSION.SDK_INT >= 28) {
+ mainHandler = Handler28Impl.createAsync(Looper.getMainLooper());
+ } else {
+ mainHandler = new Handler(Looper.getMainLooper());
+ }
+ EmojiCompat.init(new BackgroundDefaultConfig(context));
+ mainHandler.postDelayed(new LoadEmojiCompatRunnable(),
+ STARTUP_THREAD_CREATION_DELAY_MS);
+ return true;
+ }
return false;
}
@@ -75,4 +101,109 @@
public List<Class<? extends Initializer<?>>> dependencies() {
return Collections.emptyList();
}
+
+ static class LoadEmojiCompatRunnable implements Runnable {
+ @Override
+ public void run() {
+ try {
+ // this is main thread, so mark what we're doing (this trace includes thread
+ // start time in BackgroundLoadingLoader.load
+ TraceCompat.beginSection("EmojiCompat.EmojiCompatInitializer.run");
+ if (EmojiCompat.isConfigured()) {
+ EmojiCompat.get().load();
+ }
+ } finally {
+ TraceCompat.endSection();
+ }
+ }
+ }
+
+ @RequiresApi(19)
+ static class BackgroundDefaultConfig extends EmojiCompat.Config {
+ protected BackgroundDefaultConfig(Context context) {
+ super(new BackgroundDefaultLoader(context));
+ setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL);
+ }
+ }
+
+ @RequiresApi(19)
+ static class BackgroundDefaultLoader implements EmojiCompat.MetadataRepoLoader {
+ private final Context mContext;
+
+ BackgroundDefaultLoader(Context context) {
+ mContext = context.getApplicationContext();
+ }
+
+ @Nullable
+ private HandlerThread mThread;
+
+ @Override
+ public void load(@NonNull EmojiCompat.MetadataRepoLoaderCallback loaderCallback) {
+ Handler handler = getThreadHandler();
+ handler.post(() -> doLoad(loaderCallback, handler));
+ }
+
+ @WorkerThread
+ void doLoad(@NonNull EmojiCompat.MetadataRepoLoaderCallback loaderCallback,
+ @NonNull Handler handler) {
+ try {
+ FontRequestEmojiCompatConfig config = DefaultEmojiCompatConfig.create(mContext);
+ if (config == null) {
+ throw new RuntimeException("EmojiCompat font provider not available on this "
+ + "device.");
+ }
+ config.setHandler(handler);
+ config.getMetadataRepoLoader().load(new EmojiCompat.MetadataRepoLoaderCallback() {
+ @Override
+ public void onLoaded(@NonNull MetadataRepo metadataRepo) {
+ try {
+ // main thread is notified before returning, so we can quit now
+ loaderCallback.onLoaded(metadataRepo);
+ } finally {
+ quitHandlerThread();
+ }
+ }
+
+ @Override
+ public void onFailed(@Nullable Throwable throwable) {
+ try {
+ // main thread is notified before returning, so we can quit now
+ loaderCallback.onFailed(throwable);
+ } finally {
+ quitHandlerThread();
+ }
+ }
+ });
+ } catch (Throwable t) {
+ loaderCallback.onFailed(t);
+ quitHandlerThread();
+ }
+ }
+
+ void quitHandlerThread() {
+ if (mThread != null) {
+ mThread.quitSafely();
+ }
+ }
+
+ @NonNull
+ private Handler getThreadHandler() {
+ mThread = new HandlerThread(S_INITIALIZER_THREAD_NAME,
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mThread.start();
+ return new Handler(mThread.getLooper());
+ }
+ }
+
+ @RequiresApi(28)
+ private static class Handler28Impl {
+ private Handler28Impl() {
+ // Non-instantiable.
+ }
+
+ // avoid aligning with vsync when available (API 28+)
+ public static Handler createAsync(Looper looper) {
+ return Handler.createAsync(looper);
+ }
+ }
}
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
index 1e3006e..c261675 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
@@ -32,6 +32,7 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.graphics.TypefaceCompatUtil;
+import androidx.core.os.TraceCompat;
import androidx.core.provider.FontRequest;
import androidx.core.provider.FontsContractCompat;
import androidx.core.provider.FontsContractCompat.FontFamilyResult;
@@ -165,6 +166,10 @@
* given FontRequest.
*/
private static class FontRequestMetadataLoader implements EmojiCompat.MetadataRepoLoader {
+ private static final String S_TRACE_BUILD_TYPEFACE =
+ "EmojiCompat.FontRequestEmojiCompatConfig.buildTypeface";
+ private static final String S_TRACE_THREAD_CREATION =
+ "EmojiCompat.FontRequestEmojiCompatConfig.threadCreation";
private final @NonNull Context mContext;
private final @NonNull FontRequest mRequest;
private final @NonNull FontProviderHelper mFontProviderHelper;
@@ -209,11 +214,17 @@
public void load(@NonNull final EmojiCompat.MetadataRepoLoaderCallback loaderCallback) {
Preconditions.checkNotNull(loaderCallback, "LoaderCallback cannot be null");
synchronized (mLock) {
- if (mHandler == null) {
- // Developer didn't give a thread for fetching. Create our own one.
- mThread = new HandlerThread("emojiCompat", Process.THREAD_PRIORITY_BACKGROUND);
- mThread.start();
- mHandler = new Handler(mThread.getLooper());
+ try {
+ TraceCompat.beginSection(S_TRACE_THREAD_CREATION);
+ if (mHandler == null) {
+ // Developer didn't give a thread for fetching. Create our own one.
+ mThread = new HandlerThread("emojiCompat",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mThread.start();
+ mHandler = new Handler(mThread.getLooper());
+ }
+ } finally {
+ TraceCompat.endSection();
}
mHandler.post(new Runnable() {
@Override
@@ -312,13 +323,21 @@
throw new RuntimeException("fetchFonts result is not OK. (" + resultCode + ")");
}
- // TODO: Good to add new API to create Typeface from FD not to open FD twice.
- final Typeface typeface = mFontProviderHelper.buildTypeface(mContext, font);
- final ByteBuffer buffer = TypefaceCompatUtil.mmap(mContext, null, font.getUri());
- if (buffer == null) {
- throw new RuntimeException("Unable to open file.");
+ final MetadataRepo metadataRepo;
+ try {
+ TraceCompat.beginSection(S_TRACE_BUILD_TYPEFACE);
+ // TODO: Good to add new API to create Typeface from FD not to open FD twice.
+ final Typeface typeface = mFontProviderHelper.buildTypeface(mContext, font);
+ final ByteBuffer buffer = TypefaceCompatUtil.mmap(mContext, null,
+ font.getUri());
+ if (buffer == null) {
+ throw new RuntimeException("Unable to open file.");
+ }
+ metadataRepo = MetadataRepo.create(typeface, buffer);
+ } finally {
+ TraceCompat.endSection();
}
- mCallback.onLoaded(MetadataRepo.create(typeface, buffer));
+ mCallback.onLoaded(metadataRepo);
cleanUp();
} catch (Throwable t) {
mCallback.onFailed(t);
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
index bc31e26..a168667 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
@@ -24,6 +24,7 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
+import androidx.core.os.TraceCompat;
import androidx.core.util.Preconditions;
import androidx.emoji2.text.flatbuffer.MetadataList;
@@ -41,6 +42,7 @@
* The default children size of the root node.
*/
private static final int DEFAULT_ROOT_SIZE = 1024;
+ private static final String S_TRACE_CREATE_REPO = "EmojiCompat.MetadataRepo.create";
/**
* MetadataList that contains the emoji metadata.
@@ -87,7 +89,12 @@
@NonNull
@RestrictTo(RestrictTo.Scope.TESTS)
public static MetadataRepo create(@NonNull final Typeface typeface) {
- return new MetadataRepo(typeface, new MetadataList());
+ try {
+ TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+ return new MetadataRepo(typeface, new MetadataList());
+ } finally {
+ TraceCompat.endSection();
+ }
}
/**
@@ -100,7 +107,12 @@
@NonNull
public static MetadataRepo create(@NonNull final Typeface typeface,
@NonNull final InputStream inputStream) throws IOException {
- return new MetadataRepo(typeface, MetadataListReader.read(inputStream));
+ try {
+ TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+ return new MetadataRepo(typeface, MetadataListReader.read(inputStream));
+ } finally {
+ TraceCompat.endSection();
+ }
}
/**
@@ -113,7 +125,12 @@
@NonNull
public static MetadataRepo create(@NonNull final Typeface typeface,
@NonNull final ByteBuffer byteBuffer) throws IOException {
- return new MetadataRepo(typeface, MetadataListReader.read(byteBuffer));
+ try {
+ TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+ return new MetadataRepo(typeface, MetadataListReader.read(byteBuffer));
+ } finally {
+ TraceCompat.endSection();
+ }
}
/**
@@ -126,8 +143,14 @@
@NonNull
public static MetadataRepo create(@NonNull final AssetManager assetManager,
@NonNull final String assetPath) throws IOException {
- final Typeface typeface = Typeface.createFromAsset(assetManager, assetPath);
- return new MetadataRepo(typeface, MetadataListReader.read(assetManager, assetPath));
+ try {
+ TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+ final Typeface typeface = Typeface.createFromAsset(assetManager, assetPath);
+ return new MetadataRepo(typeface,
+ MetadataListReader.read(assetManager, assetPath));
+ } finally {
+ TraceCompat.endSection();
+ }
}
/**
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/README.md b/emoji2/integration-tests/init-disabled-macrobenchmark-target/README.md
new file mode 100644
index 0000000..95567d1
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/README.md
@@ -0,0 +1,2 @@
+A simple app with emoji2 startup initializer removed.
+
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/build.gradle b/emoji2/integration-tests/init-disabled-macrobenchmark-target/build.gradle
new file mode 100644
index 0000000..5f1098a
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/build.gradle
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("kotlin-android")
+}
+
+android {
+ buildTypes {
+ release {
+ minifyEnabled true
+ shrinkResources true
+ proguardFiles getDefaultProguardFile("proguard-android-optimize.txt")
+ }
+ }
+}
+
+dependencies {
+ implementation(KOTLIN_STDLIB)
+ implementation(CONSTRAINT_LAYOUT, { transitive = true })
+ implementation(project(":arch:core:core-runtime"))
+ implementation(project(":appcompat:appcompat"))
+ implementation(project(":startup:startup-runtime"))
+ implementation(MATERIAL)
+}
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..cffda4d
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/AndroidManifest.xml
@@ -0,0 +1,57 @@
+<!--
+ ~ Copyright 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.emoji2.integration.macrobenchmark.disabled.target">
+
+ <application
+ android:label="Emoji2 Init Enabled Macrobenchmark Target"
+ android:allowBackup="false"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.AppCompat"
+ tools:ignore="MissingApplicationIcon">
+
+ <!-- Profileable to enable macrobenchmark profiling -->
+ <!--suppress AndroidElementNotAllowed -->
+ <profileable android:shell="true"/>
+
+ <!--
+ Activities need to be exported so the macrobenchmark can discover them
+ under the new package visibility changes for Android 11.
+ -->
+ <activity
+ android:name=".SimpleTextActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="androidx.emoji2.integration.macrobenchmark.disabled.target.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ android:exported="false"
+ tools:node="merge">
+ <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
+ tools:node="remove" />
+ </provider>
+ </application>
+</manifest>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/disabled/target/SimpleTextActivity.kt b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/disabled/target/SimpleTextActivity.kt
new file mode 100644
index 0000000..a4ae6f5
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/disabled/target/SimpleTextActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 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.emoji2.integration.macrobenchmark.disabled.target
+
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+class SimpleTextActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ val notice = findViewById<TextView>(R.id.txtNotice)
+ notice.setText(R.string.app_notice)
+ }
+}
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/layout/activity_main.xml b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..6ec3889
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/txtNotice"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:textSize="50sp"
+ tools:text="🐻❄️ (disabled)" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
new file mode 100644
index 0000000..207eaaa
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <string name="app_notice">"🐻❄️" Appcompat emoji2 (disabled) test app.</string>
+</resources>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/build.gradle b/emoji2/integration-tests/init-disabled-macrobenchmark/build.gradle
new file mode 100644
index 0000000..1ef079d
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/build.gradle
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.Publish
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 28
+ }
+}
+
+dependencies {
+ androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+ androidTestImplementation(project(":internal-testutils-macrobenchmark"))
+ androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+}
+
+def installReleaseTarget = tasks.getByPath(
+ ":emoji2:integration-tests:init-disabled-macrobenchmark-target:installRelease"
+)
+// Define a task dependency so the app is installed before we run macro benchmarks.
+tasks.getByPath(":emoji2:integration-tests:init-disabled-macrobenchmark:connectedCheck")
+ .dependsOn(installReleaseTarget)
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/AndroidManifest.xml b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..67818b1
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.disabled.test"/>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/disabled/EmojiStartupBenchmark.kt b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/disabled/EmojiStartupBenchmark.kt
new file mode 100644
index 0000000..488b9d8
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/disabled/EmojiStartupBenchmark.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 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.emoji2.integration.macrobenchmark.disabled
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.testutils.measureStartup
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class EmojiStartupBenchmark {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Test
+ fun disabledstartup() {
+ benchmarkRule.measureStartup(
+ compilationMode = CompilationMode.None,
+ startupMode = StartupMode.COLD,
+ packageName = "androidx.emoji2.integration.macrobenchmark.disabled.target"
+ ) {
+ action = "androidx.emoji2.integration.macrobenchmark.disabled.target.MAIN"
+ }
+ }
+}
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-disabled-macrobenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0ca6a87
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.disabled" />
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/README.md b/emoji2/integration-tests/init-enabled-macrobenchmark-target/README.md
new file mode 100644
index 0000000..890837d
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/README.md
@@ -0,0 +1 @@
+A simple app with emoji2 startup initializer enabled.
\ No newline at end of file
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/build.gradle b/emoji2/integration-tests/init-enabled-macrobenchmark-target/build.gradle
new file mode 100644
index 0000000..9c0aa54
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("kotlin-android")
+}
+
+android {
+ buildTypes {
+ release {
+ minifyEnabled true
+ shrinkResources true
+ proguardFiles getDefaultProguardFile("proguard-android-optimize.txt")
+ }
+ }
+}
+
+dependencies {
+ implementation(KOTLIN_STDLIB)
+ implementation(CONSTRAINT_LAYOUT, { transitive = true })
+ implementation(project(":arch:core:core-runtime"))
+ implementation(project(":appcompat:appcompat"))
+ implementation(MATERIAL)
+}
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..eff5f4c
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/AndroidManifest.xml
@@ -0,0 +1,49 @@
+<!--
+ ~ Copyright 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.emoji2.integration.macrobenchmark.enabled.target">
+
+ <application
+ android:label="Emoji2 Init Enabled Macrobenchmark Target"
+ android:allowBackup="false"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.AppCompat"
+ tools:ignore="MissingApplicationIcon">
+
+ <!-- Profileable to enable macrobenchmark profiling -->
+ <!--suppress AndroidElementNotAllowed -->
+ <profileable android:shell="true"/>
+
+ <!--
+ Activities need to be exported so the macrobenchmark can discover them
+ under the new package visibility changes for Android 11.
+ -->
+ <activity
+ android:name=".SimpleTextActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="androidx.emoji2.integration.macrobenchmark.enabled.target.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/enabled/target/SimpleTextActivity.kt b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/enabled/target/SimpleTextActivity.kt
new file mode 100644
index 0000000..e46d648
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/enabled/target/SimpleTextActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 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.emoji2.integration.macrobenchmark.enabled.target
+
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+class SimpleTextActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ val notice = findViewById<TextView>(R.id.txtNotice)
+ notice.setText(R.string.app_notice)
+ }
+}
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/layout/activity_main.xml b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..c81807d
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/txtNotice"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:textSize="50sp"
+ tools:text="🐻❄️" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
new file mode 100644
index 0000000..11715d5
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <string name="app_notice">"🐻❄️" Appcompat emoji2 (enabled) test app.</string>
+</resources>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/build.gradle b/emoji2/integration-tests/init-enabled-macrobenchmark/build.gradle
new file mode 100644
index 0000000..c9ebc39
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/build.gradle
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.Publish
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 28
+ }
+}
+
+dependencies {
+ androidTestImplementation(project(":emoji2:emoji2"))
+ androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+ androidTestImplementation(project(":internal-testutils-macrobenchmark"))
+ androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+}
+
+def installReleaseTarget = tasks.getByPath(
+ ":emoji2:integration-tests:init-enabled-macrobenchmark-target:installRelease"
+)
+
+// Define a task dependency so the app is installed before we run macro benchmarks.
+tasks.getByPath(":emoji2:integration-tests:init-enabled-macrobenchmark:connectedCheck")
+ .dependsOn(installReleaseTarget)
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/AndroidManifest.xml b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..dbacc9c
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.enabled.test"/>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/enabled/EmojiStartupBenchmark.kt b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/enabled/EmojiStartupBenchmark.kt
new file mode 100644
index 0000000..61e06cb
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/enabled/EmojiStartupBenchmark.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 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.emoji2.integration.macrobenchmark.enabled
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.emoji2.text.DefaultEmojiCompatConfig
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.testutils.measureStartup
+import org.junit.Assume.assumeTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class EmojiStartupBenchmark {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Test
+ fun enabledstartup() {
+ // only run this test if the device can configure emoji2
+ assumeTrue(hasDiscoverableFontProviderOnDevice())
+ benchmarkRule.measureStartup(
+ compilationMode = CompilationMode.None,
+ startupMode = StartupMode.COLD,
+ packageName = "androidx.emoji2.integration.macrobenchmark.enabled.target"
+ ) {
+ action = "androidx.emoji2.integration.macrobenchmark.enabled.target.MAIN"
+ }
+ }
+
+ private fun hasDiscoverableFontProviderOnDevice(): Boolean {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ return DefaultEmojiCompatConfig.create(context) != null
+ }
+}
\ No newline at end of file
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-enabled-macrobenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..040c742
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.enabled" />
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt
index 4635777..3d6b8e2 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt
@@ -16,7 +16,10 @@
package androidx.fragment.app
+import android.app.Activity
import android.os.Bundle
+import android.os.Parcelable
+import androidx.activity.result.ActivityResult
import androidx.fragment.app.test.FragmentTestActivity
import androidx.fragment.test.R
import androidx.test.core.app.ActivityScenario
@@ -380,6 +383,54 @@
.isEqualTo("resultGood")
}
}
+
+ @Test
+ fun testReplaceResultWithParcelableOnRecreation() {
+ with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ var fm = withActivity {
+ setContentView(R.layout.simple_container)
+ supportFragmentManager
+ }
+ var fragment1 = ParcelableResultFragment()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1, "fragment1")
+ .commit()
+ executePendingTransactions()
+
+ val fragment2 = StrictFragment()
+
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions()
+
+ val resultBundle = Bundle()
+ val expectedResult = ActivityResult(Activity.RESULT_OK, null)
+ resultBundle.putParcelable("bundleKey", expectedResult)
+
+ fm.setFragmentResult("requestKey", resultBundle)
+
+ assertWithMessage("The result is not set")
+ .that(fragment1.actualResult)
+ .isNull()
+
+ recreate()
+
+ fm = withActivity { supportFragmentManager }
+
+ withActivity {
+ fm.popBackStackImmediate()
+ }
+
+ fragment1 = fm.findFragmentByTag("fragment1") as ParcelableResultFragment
+
+ assertWithMessage("The result is incorrect")
+ .that(fragment1.actualResult)
+ .isEqualTo(expectedResult)
+ }
+ }
}
class ResultFragment : StrictFragment() {
@@ -434,4 +485,18 @@
}
parentFragmentManager.setFragmentResult("requestKey", resultBundle)
}
-}
\ No newline at end of file
+}
+
+class ParcelableResultFragment : StrictFragment() {
+ var actualResult: Parcelable? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ parentFragmentManager.setFragmentResultListener(
+ "requestKey", this,
+ FragmentResultListener
+ { _, bundle -> actualResult = bundle.getParcelable<ActivityResult>("bundleKey") }
+ )
+ }
+}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleOwnerTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleOwnerTest.kt
new file mode 100644
index 0000000..67ae872
--- /dev/null
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleOwnerTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2021 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.fragment.app
+
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.lifecycle.HasDefaultViewModelProviderFactory
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class FragmentViewLifecycleOwnerTest {
+
+ /**
+ * Test representing a Non-Hilt case, in which the default factory is not overwritten at the
+ * Fragment level.
+ */
+ @Test
+ fun defaultFactoryNotOverwritten() {
+ with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ val fm = withActivity {
+ setContentView(R.layout.simple_container)
+ supportFragmentManager
+ }
+ val fragment = StrictViewFragment()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .commit()
+ executePendingTransactions()
+
+ val defaultFactory1 = (
+ fragment.viewLifecycleOwner as HasDefaultViewModelProviderFactory
+ ).defaultViewModelProviderFactory
+ val defaultFactory2 = (
+ fragment.viewLifecycleOwner as HasDefaultViewModelProviderFactory
+ ).defaultViewModelProviderFactory
+
+ // Assure that multiple call return the same default factory
+ assertThat(defaultFactory1).isSameInstanceAs(defaultFactory2)
+ assertThat(defaultFactory1).isNotSameInstanceAs(
+ fragment.defaultViewModelProviderFactory
+ )
+ }
+ }
+
+ /**
+ * Test representing a Hilt case, in which the default factory is overwritten at the
+ * Fragment level.
+ */
+ @Test
+ fun defaultFactoryOverwritten() {
+ with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ val fm = withActivity {
+ setContentView(R.layout.simple_container)
+ supportFragmentManager
+ }
+ val fragment = FragmentWithFactoryOverride()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .commit()
+ executePendingTransactions()
+
+ val defaultFactory = (
+ fragment.viewLifecycleOwner as HasDefaultViewModelProviderFactory
+ ).defaultViewModelProviderFactory
+
+ assertThat(defaultFactory).isInstanceOf(FakeViewModelProviderFactory::class.java)
+ }
+ }
+
+ private class TestViewModel : ViewModel()
+
+ class FakeViewModelProviderFactory : ViewModelProvider.Factory {
+ private var createCalled: Boolean = false
+ override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+ require(modelClass == TestViewModel::class.java)
+ createCalled = true
+ @Suppress("UNCHECKED_CAST")
+ return TestViewModel() as T
+ }
+ }
+
+ public class FragmentWithFactoryOverride : StrictViewFragment() {
+ public override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory =
+ FakeViewModelProviderFactory()
+ }
+}
\ No newline at end of file
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
index a66d652..42ad4dc 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
@@ -24,7 +24,7 @@
import androidx.lifecycle.ViewModelProvider
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
+import androidx.test.filters.LargeTest
import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -32,7 +32,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@MediumTest
+@LargeTest
@RunWith(AndroidJUnit4::class)
class SaveRestoreBackStackTest {
@@ -272,6 +272,7 @@
assertWithMessage("ViewModel should not be cleared after commit()")
.that(originalViewModel.cleared)
.isFalse()
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
fm.saveBackStack("replacement")
executePendingTransactions()
@@ -279,6 +280,7 @@
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isTrue()
+ assertThat(fm.backStackEntryCount).isEqualTo(0)
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
@@ -303,6 +305,7 @@
assertThat(stateSavedReplacement.savedState).isEqualTo("saved")
assertThat(stateSavedReplacement.unsavedState).isNull()
assertThat(stateSavedReplacement.viewModel).isSameInstanceAs(originalViewModel)
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
}
}
@@ -331,6 +334,7 @@
assertWithMessage("ViewModel should not be cleared after commit()")
.that(originalViewModel.cleared)
.isFalse()
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
fm.saveBackStack("replacement")
executePendingTransactions()
@@ -338,6 +342,7 @@
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isTrue()
+ assertThat(fm.backStackEntryCount).isEqualTo(0)
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
@@ -369,6 +374,63 @@
assertThat(stateSavedReplacement.savedState).isEqualTo("saved")
assertThat(stateSavedReplacement.unsavedState).isNull()
assertThat(stateSavedReplacement.viewModel).isSameInstanceAs(originalViewModel)
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun restoreBackStackWithoutExecutePendingTransactions() {
+ with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ val fm = withActivity {
+ supportFragmentManager
+ }
+ val fragmentBase = StrictFragment()
+ val fragmentReplacement = StateSaveFragment("saved", "unsaved")
+
+ fm.beginTransaction()
+ .add(R.id.content, fragmentBase)
+ .commit()
+ executePendingTransactions()
+
+ fm.beginTransaction()
+ .setReorderingAllowed(true)
+ .replace(R.id.content, fragmentReplacement)
+ .addToBackStack("replacement")
+ .commit()
+ executePendingTransactions()
+
+ val originalViewModel = fragmentReplacement.viewModel
+ assertWithMessage("ViewModel should not be cleared after commit()")
+ .that(originalViewModel.cleared)
+ .isFalse()
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
+
+ withActivity {
+ fm.saveBackStack("replacement")
+ // Immediately restore the back stack without calling executePendingTransactions
+ fm.restoreBackStack("replacement")
+ }
+ executePendingTransactions()
+
+ assertWithMessage("Saved Fragments should not go through onSaveInstanceState")
+ .that(fragmentReplacement.calledOnSaveInstanceState)
+ .isFalse()
+ assertWithMessage("Saved Fragments should not have been destroyed")
+ .that(fragmentReplacement.calledOnDestroy)
+ .isFalse()
+ assertWithMessage("ViewModel should not be cleared after saveBackStack()")
+ .that(originalViewModel.cleared)
+ .isFalse()
+
+ assertWithMessage("Fragment should still be returned by FragmentManager")
+ .that(fm.findFragmentById(R.id.content))
+ .isSameInstanceAs(fragmentReplacement)
+
+ // Assert that restored fragment has its saved state restored
+ assertThat(fragmentReplacement.savedState).isEqualTo("saved")
+ assertThat(fragmentReplacement.unsavedState).isEqualTo("unsaved")
+ assertThat(fragmentReplacement.viewModel).isSameInstanceAs(originalViewModel)
+ assertThat(fm.backStackEntryCount).isEqualTo(1)
}
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
index e691044..70ec336 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
@@ -142,6 +142,17 @@
mManager = manager;
}
+ BackStackRecord(@NonNull BackStackRecord bse) {
+ super(bse.mManager.getFragmentFactory(), bse.mManager.getHost() != null
+ ? bse.mManager.getHost().getContext().getClassLoader()
+ : null, bse);
+ mManager = bse.mManager;
+ mCommitted = bse.mCommitted;
+ mIndex = bse.mIndex;
+ mBeingSaved = bse.mBeingSaved;
+ }
+
+
@Override
public int getId() {
return mIndex;
@@ -281,15 +292,6 @@
}
}
- void runOnExecuteRunnables() {
- if (mExecuteRunnables != null) {
- for (int i = 0; i < mExecuteRunnables.size(); i++) {
- mExecuteRunnables.get(i).run();
- }
- mExecuteRunnables = null;
- }
- }
-
public void runOnCommitRunnables() {
if (mCommitRunnables != null) {
for (int i = 0; i < mCommitRunnables.size(); i++) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java
index c102873..06e1df3 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java
@@ -48,7 +48,14 @@
// These will populate the transactions we instantiate.
HashMap<String, Fragment> fragments = new HashMap<>(mFragments.size());
for (String fWho : mFragments) {
- // Retrieve any saved state, clearing it out for future calls
+ Fragment existingFragment = fm.getFragmentStore().findFragmentByWho(fWho);
+ if (existingFragment != null) {
+ // If the Fragment still exists, this means the saveBackStack()
+ // hasn't executed yet, so we can use the existing Fragment directly
+ fragments.put(existingFragment.mWho, existingFragment);
+ continue;
+ }
+ // Otherwise, retrieve any saved state, clearing it out for future calls
FragmentState fragmentState = fm.getFragmentStore().setSavedState(fWho, null);
if (fragmentState != null) {
Fragment fragment = fragmentState.instantiate(fm.getFragmentFactory(),
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index c33fffe..0a07f7c 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -289,7 +289,7 @@
@Nullable FragmentViewLifecycleOwner mViewLifecycleOwner;
MutableLiveData<LifecycleOwner> mViewLifecycleOwnerLiveData = new MutableLiveData<>();
- private ViewModelProvider.Factory mDefaultFactory;
+ ViewModelProvider.Factory mDefaultFactory;
SavedStateRegistryController mSavedStateRegistryController;
@@ -2939,7 +2939,7 @@
@Nullable Bundle savedInstanceState) {
mChildFragmentManager.noteStateNotSaved();
mPerformedCreateView = true;
- mViewLifecycleOwner = new FragmentViewLifecycleOwner(getViewModelStore());
+ mViewLifecycleOwner = new FragmentViewLifecycleOwner(this, getViewModelStore());
mView = onCreateView(inflater, container, savedInstanceState);
if (mView != null) {
// Initialize the view lifecycle
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 85a3a37..1c7ab0e 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -1758,11 +1758,6 @@
}
executeOps(records, isRecordPop, startIndex, endIndex);
- for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
- final BackStackRecord record = records.get(recordNum);
- record.runOnExecuteRunnables();
- }
-
// The last operation determines the overall direction, this ensures that operations
// such as push, push, pop, push are correctly considered a push
boolean isPop = isRecordPop.get(endIndex - 1);
@@ -1997,11 +1992,11 @@
}
List<BackStackRecord> backStackRecords = backStackState.instantiate(this);
+ boolean added = false;
for (BackStackRecord record : backStackRecords) {
- records.add(record);
- isRecordPop.add(false);
+ added = record.generateOps(records, isRecordPop) || added;
}
- return true;
+ return added;
}
boolean saveBackStackState(@NonNull ArrayList<BackStackRecord> records,
@@ -2095,20 +2090,17 @@
final BackStackState backStackState = new BackStackState(
fragments, backStackRecordStates);
for (int i = mBackStack.size() - 1; i >= index; i--) {
- final BackStackRecord record = mBackStack.remove(i);
+ BackStackRecord record = mBackStack.remove(i);
+
+ // Create a copy of the record to save
+ BackStackRecord copy = new BackStackRecord(record);
+ copy.collapseOps();
+ BackStackRecordState state = new BackStackRecordState(copy);
+ backStackRecordStates.set(i - index, state);
+
+ // And now mark the record as being saved to ensure that each
+ // fragment saves its state properly
record.mBeingSaved = true;
- // Get a callback when the BackStackRecord is actually finished
- final int currentIndex = i;
- record.addOnExecuteRunnable(new Runnable() {
- @Override
- public void run() {
- // First collapse the record to remove expanded ops and get it ready to save
- record.collapseOps();
- // Then save the state
- BackStackRecordState state = new BackStackRecordState(record);
- backStackRecordStates.set(currentIndex - index, state);
- }
- });
records.add(record);
isRecordPop.add(true);
}
@@ -2381,7 +2373,9 @@
ArrayList<String> savedResultKeys = fms.mResultKeys;
if (savedResultKeys != null) {
for (int i = 0; i < savedResultKeys.size(); i++) {
- mResults.put(savedResultKeys.get(i), fms.mResults.get(i));
+ Bundle savedResult = fms.mResults.get(i);
+ savedResult.setClassLoader(mHost.getContext().getClassLoader());
+ mResults.put(savedResultKeys.get(i), savedResult);
}
}
mLaunchedFragments = new ArrayDeque<>(fms.mLaunchedFragments);
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
index 1ed008c..65a30e5 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
@@ -98,6 +98,18 @@
this.mOldMaxState = fragment.mMaxState;
this.mCurrentMaxState = state;
}
+
+ Op(Op op) {
+ this.mCmd = op.mCmd;
+ this.mFragment = op.mFragment;
+ this.mFromExpandedOp = op.mFromExpandedOp;
+ this.mEnterAnim = op.mEnterAnim;
+ this.mExitAnim = op.mExitAnim;
+ this.mPopEnterAnim = op.mPopEnterAnim;
+ this.mPopExitAnim = op.mPopExitAnim;
+ this.mOldMaxState = op.mOldMaxState;
+ this.mCurrentMaxState = op.mCurrentMaxState;
+ }
}
private final FragmentFactory mFragmentFactory;
@@ -122,7 +134,6 @@
ArrayList<String> mSharedElementTargetNames;
boolean mReorderingAllowed = false;
- ArrayList<Runnable> mExecuteRunnables;
ArrayList<Runnable> mCommitRunnables;
/**
@@ -141,6 +152,35 @@
mClassLoader = classLoader;
}
+ FragmentTransaction(@NonNull FragmentFactory fragmentFactory,
+ @Nullable ClassLoader classLoader, @NonNull FragmentTransaction ft) {
+ this(fragmentFactory, classLoader);
+ for (Op op : ft.mOps) {
+ mOps.add(new Op(op));
+ }
+ mEnterAnim = ft.mEnterAnim;
+ mExitAnim = ft.mExitAnim;
+ mPopEnterAnim = ft.mPopEnterAnim;
+ mPopExitAnim = ft.mPopExitAnim;
+ mTransition = ft.mTransition;
+ mAddToBackStack = ft.mAddToBackStack;
+ mAllowAddToBackStack = ft.mAllowAddToBackStack;
+ mName = ft.mName;
+ mBreadCrumbShortTitleRes = ft.mBreadCrumbShortTitleRes;
+ mBreadCrumbShortTitleText = ft.mBreadCrumbShortTitleText;
+ mBreadCrumbTitleRes = ft.mBreadCrumbTitleRes;
+ mBreadCrumbTitleText = ft.mBreadCrumbTitleText;
+ if (ft.mSharedElementSourceNames != null) {
+ mSharedElementSourceNames = new ArrayList<>();
+ mSharedElementSourceNames.addAll(ft.mSharedElementSourceNames);
+ }
+ if (ft.mSharedElementTargetNames != null) {
+ mSharedElementTargetNames = new ArrayList<>();
+ mSharedElementTargetNames.addAll(ft.mSharedElementTargetNames);
+ }
+ mReorderingAllowed = ft.mReorderingAllowed;
+ }
+
void addOp(Op op) {
mOps.add(op);
op.mEnterAnim = mEnterAnim;
@@ -826,18 +866,6 @@
}
/**
- * Add a runnable that is run immediately after the transaction is executed.
- * This differs from the commit runnables in that it happens before any
- * fragments move to their expected state.
- */
- void addOnExecuteRunnable(@NonNull Runnable runnable) {
- if (mExecuteRunnables == null) {
- mExecuteRunnables = new ArrayList<>();
- }
- mExecuteRunnables.add(runnable);
- }
-
- /**
* Add a Runnable to this transaction that will be run after this transaction has
* been committed. If fragment transactions are {@link #setReorderingAllowed(boolean) optimized}
* this may be after other subsequent fragment operations have also taken place, or operations
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java
index 65f16a4..ef96a00 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java
@@ -16,25 +16,36 @@
package androidx.fragment.app;
+import android.app.Application;
+import android.content.Context;
+import android.content.ContextWrapper;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.lifecycle.HasDefaultViewModelProviderFactory;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.SavedStateViewModelFactory;
+import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.savedstate.SavedStateRegistry;
import androidx.savedstate.SavedStateRegistryController;
import androidx.savedstate.SavedStateRegistryOwner;
-class FragmentViewLifecycleOwner implements SavedStateRegistryOwner, ViewModelStoreOwner {
+class FragmentViewLifecycleOwner implements HasDefaultViewModelProviderFactory,
+ SavedStateRegistryOwner, ViewModelStoreOwner {
+ private final Fragment mFragment;
private final ViewModelStore mViewModelStore;
+ private ViewModelProvider.Factory mDefaultFactory;
+
private LifecycleRegistry mLifecycleRegistry = null;
private SavedStateRegistryController mSavedStateRegistryController = null;
- FragmentViewLifecycleOwner(@NonNull ViewModelStore viewModelStore) {
+ FragmentViewLifecycleOwner(@NonNull Fragment fragment, @NonNull ViewModelStore viewModelStore) {
+ mFragment = fragment;
mViewModelStore = viewModelStore;
}
@@ -77,6 +88,44 @@
mLifecycleRegistry.handleLifecycleEvent(event);
}
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The {@link Fragment#getArguments() Fragment's arguments} when this is first called will
+ * be used as the defaults to any {@link androidx.lifecycle.SavedStateHandle} passed to a
+ * view model created using this factory.</p>
+ */
+ @NonNull
+ @Override
+ public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
+ ViewModelProvider.Factory currentFactory =
+ mFragment.getDefaultViewModelProviderFactory();
+
+ if (!currentFactory.equals(mFragment.mDefaultFactory)) {
+ mDefaultFactory = currentFactory;
+ return currentFactory;
+ }
+
+ if (mDefaultFactory == null) {
+ Application application = null;
+ Context appContext = mFragment.requireContext().getApplicationContext();
+ while (appContext instanceof ContextWrapper) {
+ if (appContext instanceof Application) {
+ application = (Application) appContext;
+ break;
+ }
+ appContext = ((ContextWrapper) appContext).getBaseContext();
+ }
+
+ mDefaultFactory = new SavedStateViewModelFactory(
+ application,
+ this,
+ mFragment.getArguments());
+ }
+
+ return mDefaultFactory;
+ }
+
@NonNull
@Override
public SavedStateRegistry getSavedStateRegistry() {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 4d9b6bc..42c5121 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -59,7 +59,7 @@
dokkaGradlePlugin = { module = "org.jetbrains.dokka:dokka-android-gradle-plugin", version = "0.9.17-g014" }
espressoContrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "espresso" }
espressoCore = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
-espressoIdlingNet = { module = "androidx.test.espresso:espresso-idling-net", version.ref = "espresso" }
+espressoIdlingNet = { module = "androidx.test.espresso.idling:idling-net", version.ref = "espresso" }
espressoIdlingResource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "espresso" }
espressoIntents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso" }
espressoWeb = { module = "androidx.test.espresso:espresso-web", version.ref = "espresso" }
diff --git a/health/health-services-client/api/api_lint.ignore b/health/health-services-client/api/api_lint.ignore
new file mode 100644
index 0000000..1a6bb40
--- /dev/null
+++ b/health/health-services-client/api/api_lint.ignore
@@ -0,0 +1,9 @@
+// Baseline format: 1.0
+ExecutorRegistration: androidx.health.services.client.ExerciseClient#clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener):
+ Registration methods should have overload that accepts delivery Executor: `clearUpdateListener`
+ExecutorRegistration: androidx.health.services.client.PassiveMonitoringClient#registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType>, android.app.PendingIntent, androidx.health.services.client.PassiveMonitoringCallback):
+ Registration methods should have overload that accepts delivery Executor: `registerDataCallback`
+
+
+MissingGetterMatchingBuilder: androidx.health.services.client.data.ExerciseConfig.Builder#setAutoPauseAndResume(boolean):
+ androidx.health.services.client.data.ExerciseConfig does not declare a `isAutoPauseAndResume()` method matching method androidx.health.services.client.data.ExerciseConfig.Builder.setAutoPauseAndResume(boolean)
diff --git a/health/health-services-client/api/current.txt b/health/health-services-client/api/current.txt
index e6f50d0..6abe37c 100644
--- a/health/health-services-client/api/current.txt
+++ b/health/health-services-client/api/current.txt
@@ -1 +1,869 @@
// Signature format: 4.0
+package androidx.health.services.client {
+
+ public interface ExerciseClient {
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExercise(androidx.health.services.client.data.ExerciseGoal exerciseGoal);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfo();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLap();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExercise(boolean enabled);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExercise(androidx.health.services.client.data.ExerciseConfig configuration);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> capabilities;
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> currentExerciseInfo;
+ }
+
+ public interface ExerciseUpdateListener {
+ method public void onExerciseUpdate(androidx.health.services.client.data.ExerciseUpdate update);
+ method public void onLapSummary(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+ }
+
+ public final class HealthServices {
+ method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+ field public static final androidx.health.services.client.HealthServices INSTANCE;
+ }
+
+ public interface HealthServicesClient {
+ method public androidx.health.services.client.ExerciseClient getExerciseClient();
+ method public androidx.health.services.client.MeasureClient getMeasureClient();
+ method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+ property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+ property public abstract androidx.health.services.client.MeasureClient measureClient;
+ property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+ }
+
+ public interface MeasureCallback {
+ method public void onAvailabilityChanged(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Availability availability);
+ method public void onData(java.util.List<androidx.health.services.client.data.DataPoint> data);
+ }
+
+ public interface MeasureClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> capabilities;
+ }
+
+ public interface PassiveMonitoringCallback {
+ method public void onPassiveActivityState(androidx.health.services.client.data.PassiveActivityState state);
+ }
+
+ public interface PassiveMonitoringClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent, androidx.health.services.client.PassiveMonitoringCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerEventCallback(androidx.health.services.client.data.event.Event event, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterDataCallback();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterEventCallback(androidx.health.services.client.data.event.Event event);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> capabilities;
+ }
+
+}
+
+package androidx.health.services.client.data {
+
+ public final class AchievedExerciseGoal implements android.os.Parcelable {
+ ctor public AchievedExerciseGoal(androidx.health.services.client.data.ExerciseGoal goal);
+ method public androidx.health.services.client.data.ExerciseGoal component1();
+ method public androidx.health.services.client.data.AchievedExerciseGoal copy(androidx.health.services.client.data.ExerciseGoal goal);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseGoal getGoal();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseGoal goal;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AchievedExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.AchievedExerciseGoal.Companion Companion;
+ }
+
+ public static final class AchievedExerciseGoal.Companion {
+ }
+
+ public final class AutoExerciseConfig implements android.os.Parcelable {
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent, optional android.os.Bundle exerciseParams);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect);
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> component1();
+ method public android.app.PendingIntent? component2();
+ method public android.os.Bundle component3();
+ method public androidx.health.services.client.data.AutoExerciseConfig copy(java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, android.app.PendingIntent? launchIntent, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public android.os.Bundle getExerciseParams();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getExercisesToDetect();
+ method public android.app.PendingIntent? getLaunchIntent();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final android.os.Bundle exerciseParams;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> exercisesToDetect;
+ property public final android.app.PendingIntent? launchIntent;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AutoExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.AutoExerciseConfig.Companion Companion;
+ }
+
+ public static final class AutoExerciseConfig.Companion {
+ }
+
+ public enum Availability {
+ method public static final androidx.health.services.client.data.Availability? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.Availability ACQUIRING;
+ enum_constant public static final androidx.health.services.client.data.Availability AVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNAVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNKNOWN;
+ field public static final androidx.health.services.client.data.Availability.Companion Companion;
+ }
+
+ public static final class Availability.Companion {
+ method public androidx.health.services.client.data.Availability? fromId(int id);
+ }
+
+ public final class Capabilities implements android.os.Parcelable {
+ ctor public Capabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> component1();
+ method public androidx.health.services.client.data.Capabilities copy(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+ method public androidx.health.services.client.data.ExerciseCapabilities getExerciseCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> getExerciseTypeToExerciseCapabilities();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+ property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Capabilities> CREATOR;
+ field public static final androidx.health.services.client.data.Capabilities.Companion Companion;
+ }
+
+ public static final class Capabilities.Companion {
+ }
+
+ public enum ComparisonType {
+ method public static final androidx.health.services.client.data.ComparisonType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+ field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+ }
+
+ public static final class ComparisonType.Companion {
+ method public androidx.health.services.client.data.ComparisonType? fromId(int id);
+ }
+
+ public final class DataPoint implements android.os.Parcelable {
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public java.time.Duration component3();
+ method public java.time.Duration component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.DataPoint copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public java.time.Duration getEndDurationFromBoot();
+ method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+ method public android.os.Bundle getMetadata();
+ method public java.time.Duration getStartDurationFromBoot();
+ method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+ method public androidx.health.services.client.data.Value getValue();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final java.time.Duration endDurationFromBoot;
+ property public final android.os.Bundle metadata;
+ property public final java.time.Duration startDurationFromBoot;
+ property public final androidx.health.services.client.data.Value value;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataPoint> CREATOR;
+ field public static final androidx.health.services.client.data.DataPoint.Companion Companion;
+ }
+
+ public static final class DataPoint.Companion {
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ }
+
+ @Keep public final class DataPoints {
+ method public static androidx.health.services.client.data.DataPoint aggregateCalories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateDistance(double distance, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSteps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSwimmingStrokes(long swimmingStrokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint altitude(double meters, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averagePace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averageSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint calories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint distance(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint elevation(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint floors(double floors, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method @Keep public static java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints(android.content.Intent intent);
+ method public static boolean getPermissionsGranted(android.content.Intent intent);
+ method public static androidx.health.services.client.data.DataPoint heartRate(double bpm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, double altitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint maxSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint pace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static void putDataPoints(android.content.Intent intent, java.util.Collection<androidx.health.services.client.data.DataPoint> dataPoints);
+ method public static void putPermissionsGranted(android.content.Intent intent, boolean granted);
+ method public static androidx.health.services.client.data.DataPoint speed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint spo2(double percent, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint steps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint stepsPerMinute(long stepsPerMinute, java.time.Duration startDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint swimmingStrokes(long strokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ field public static final androidx.health.services.client.data.DataPoints INSTANCE;
+ field public static final int LOCATION_DATA_POINT_ALTITUDE_INDEX = 2; // 0x2
+ field public static final int LOCATION_DATA_POINT_LATITUDE_INDEX = 0; // 0x0
+ field public static final int LOCATION_DATA_POINT_LONGITUDE_INDEX = 1; // 0x1
+ }
+
+ public final class DataType implements android.os.Parcelable {
+ ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public String component1();
+ method public androidx.health.services.client.data.DataType.TimeType component2();
+ method public int component3();
+ method public androidx.health.services.client.data.DataType copy(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public int describeContents();
+ method public int getFormat();
+ method public String getName();
+ method public androidx.health.services.client.data.DataType.TimeType getTimeType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final int format;
+ property public final String name;
+ property public final androidx.health.services.client.data.DataType.TimeType timeType;
+ field public static final androidx.health.services.client.data.DataType ABSOLUTE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType ACTIVE_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_CALORIES_EXPENDED;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLOORS;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_RUNNING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_SWIMMING_STROKE_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_WALKING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_PACE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_SPEED;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataType> CREATOR;
+ field public static final androidx.health.services.client.data.DataType.Companion Companion;
+ field public static final androidx.health.services.client.data.DataType DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType DISTANCE;
+ field public static final androidx.health.services.client.data.DataType ELEVATION;
+ field public static final androidx.health.services.client.data.DataType FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType FLOORS;
+ field public static final androidx.health.services.client.data.DataType HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType LOCATION;
+ field public static final androidx.health.services.client.data.DataType MAX_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType MAX_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType MAX_PACE;
+ field public static final androidx.health.services.client.data.DataType MAX_SPEED;
+ field public static final androidx.health.services.client.data.DataType MIN_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType PACE;
+ field public static final androidx.health.services.client.data.DataType REP_COUNT;
+ field public static final androidx.health.services.client.data.DataType RESTING_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType RUNNING_STEPS;
+ field public static final androidx.health.services.client.data.DataType SPEED;
+ field public static final androidx.health.services.client.data.DataType SPO2;
+ field public static final androidx.health.services.client.data.DataType STEPS;
+ field public static final androidx.health.services.client.data.DataType STEPS_PER_MINUTE;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_LAP_COUNT;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_STROKES;
+ field public static final androidx.health.services.client.data.DataType TOTAL_CALORIES;
+ field public static final androidx.health.services.client.data.DataType VO2;
+ field public static final androidx.health.services.client.data.DataType VO2_MAX;
+ field public static final androidx.health.services.client.data.DataType WALKING_STEPS;
+ }
+
+ public static final class DataType.Companion {
+ }
+
+ public enum DataType.TimeType {
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+ }
+
+ public final class DataTypeCondition implements android.os.Parcelable {
+ ctor public DataTypeCondition(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public androidx.health.services.client.data.ComparisonType component3();
+ method public androidx.health.services.client.data.DataTypeCondition copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ComparisonType getComparisonType();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public androidx.health.services.client.data.Value getThreshold();
+ method public boolean isSatisfied(androidx.health.services.client.data.DataPoint dataPoint);
+ method public boolean isThresholdSatisfied(androidx.health.services.client.data.Value value);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ComparisonType comparisonType;
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final androidx.health.services.client.data.Value threshold;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataTypeCondition> CREATOR;
+ field public static final androidx.health.services.client.data.DataTypeCondition.Companion Companion;
+ }
+
+ public static final class DataTypeCondition.Companion {
+ }
+
+ public final class DataTypes {
+ method public static androidx.health.services.client.data.DataType? getAggregateTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static java.util.Set<androidx.health.services.client.data.DataType> getAggregatedDataTypesFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getAverageTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getMaxTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAggregateType(androidx.health.services.client.data.DataType aggregateType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAverageType(androidx.health.services.client.data.DataType averageType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromMaxType(androidx.health.services.client.data.DataType maxType);
+ method public static boolean isAggregateDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isRawType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalAverageDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalMaxDataType(androidx.health.services.client.data.DataType dataType);
+ field public static final androidx.health.services.client.data.DataTypes INSTANCE;
+ }
+
+ public final class ExerciseCapabilities implements android.os.Parcelable {
+ ctor public ExerciseCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component2();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component3();
+ method public boolean component4();
+ method public boolean component5();
+ method public androidx.health.services.client.data.ExerciseCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypes();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+ method public boolean getSupportsAutoPauseAndResume();
+ method public boolean getSupportsLaps();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+ property public final boolean supportsAutoPauseAndResume;
+ property public final boolean supportsLaps;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseCapabilities.Companion Companion;
+ }
+
+ public static final class ExerciseCapabilities.Companion {
+ }
+
+ public final class ExerciseConfig implements android.os.Parcelable {
+ ctor protected ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public static androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ method public androidx.health.services.client.data.ExerciseType component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public boolean component3();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.ExerciseConfig copy(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public boolean getAutoPauseAndResume();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getDataTypes();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> getExerciseGoals();
+ method public android.os.Bundle getExerciseParams();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final boolean autoPauseAndResume;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> dataTypes;
+ property public final java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals;
+ property public final android.os.Bundle exerciseParams;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+ }
+
+ public static final class ExerciseConfig.Builder {
+ ctor public ExerciseConfig.Builder();
+ method public androidx.health.services.client.data.ExerciseConfig build();
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setAutoPauseAndResume(boolean autoPauseAndResume);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<androidx.health.services.client.data.DataType> dataTypes);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseType(androidx.health.services.client.data.ExerciseType exerciseType);
+ }
+
+ public static final class ExerciseConfig.Companion {
+ method public androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ }
+
+ public final class ExerciseGoal implements android.os.Parcelable {
+ ctor protected ExerciseGoal(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, optional androidx.health.services.client.data.Value? period);
+ method public androidx.health.services.client.data.ExerciseGoalType component1();
+ method public androidx.health.services.client.data.DataTypeCondition component2();
+ method public androidx.health.services.client.data.Value? component3();
+ method public androidx.health.services.client.data.ExerciseGoal copy(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.Value? period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public static androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+ method public androidx.health.services.client.data.Value? getPeriod();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+ property public final androidx.health.services.client.data.Value? period;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+ }
+
+ public static final class ExerciseGoal.Companion {
+ method public androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ }
+
+ public enum ExerciseGoalType {
+ method public static final androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+ field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+ }
+
+ public static final class ExerciseGoalType.Companion {
+ method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ }
+
+ public final class ExerciseInfo implements android.os.Parcelable {
+ ctor public ExerciseInfo(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public androidx.health.services.client.data.ExerciseTrackedStatus component1();
+ method public androidx.health.services.client.data.ExerciseType component2();
+ method public androidx.health.services.client.data.ExerciseInfo copy(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseTrackedStatus getExerciseTrackedStatus();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseInfo> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseInfo.Companion Companion;
+ }
+
+ public static final class ExerciseInfo.Companion {
+ }
+
+ public final class ExerciseLapSummary implements android.os.Parcelable {
+ ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int component1();
+ method public java.time.Instant component2();
+ method public java.time.Instant component3();
+ method public java.time.Duration component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.ExerciseLapSummary copy(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public int getLapCount();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getLapMetrics();
+ method public java.time.Instant getStartTime();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final int lapCount;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics;
+ property public final java.time.Instant startTime;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseLapSummary> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseLapSummary.Companion Companion;
+ }
+
+ public static final class ExerciseLapSummary.Companion {
+ }
+
+ public enum ExerciseState {
+ method public static final androidx.health.services.client.data.ExerciseState? fromId(int id);
+ method public final int getId();
+ method public final boolean isEnded();
+ method public final boolean isPaused();
+ method public final boolean isResuming();
+ property public final int id;
+ property public final boolean isEnded;
+ property public final boolean isPaused;
+ property public final boolean isResuming;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+ field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+ }
+
+ public static final class ExerciseState.Companion {
+ method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+ }
+
+ public enum ExerciseTrackedStatus {
+ method public static final androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus NO_EXERCISE_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OTHER_APP_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OWNED_EXERCISE_IN_PROGRESS;
+ field public static final androidx.health.services.client.data.ExerciseTrackedStatus.Companion Companion;
+ }
+
+ public static final class ExerciseTrackedStatus.Companion {
+ method public androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ }
+
+ public enum ExerciseType {
+ method public static final androidx.health.services.client.data.ExerciseType fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_SIT_UP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOXING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BURPEE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRICKET;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DANCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_FRONT_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_LATERAL_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_TWO_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FENCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GOLF;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GYNMASTICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LUNGE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PADDLING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PILATES;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PLANK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUGBY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SAILING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKIING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOCCER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUASH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUAT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SURFING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WALKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_INDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_OUTDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType YOGA;
+ field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+ }
+
+ public static final class ExerciseType.Companion {
+ method public androidx.health.services.client.data.ExerciseType fromId(int id);
+ }
+
+ public final class ExerciseUpdate implements android.os.Parcelable {
+ ctor public ExerciseUpdate(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public androidx.health.services.client.data.ExerciseState component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> component4();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> component5();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> component6();
+ method public androidx.health.services.client.data.ExerciseConfig? component7();
+ method public androidx.health.services.client.data.AutoExerciseConfig? component8();
+ method public androidx.health.services.client.data.ExerciseType? component9();
+ method public androidx.health.services.client.data.ExerciseUpdate copy(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public androidx.health.services.client.data.AutoExerciseConfig? getAutoExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseType? getAutoExerciseDetected();
+ method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType getExerciseSessionType();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> getLatestAchievedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> getLatestMetrics();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+ method public java.time.Instant getStartTime();
+ method public androidx.health.services.client.data.ExerciseState getState();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig;
+ property public final androidx.health.services.client.data.ExerciseType? autoExerciseDetected;
+ property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+ property public final java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics;
+ property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+ property public final java.time.Instant startTime;
+ property public final androidx.health.services.client.data.ExerciseState state;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseUpdate> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+ }
+
+ public static final class ExerciseUpdate.Companion {
+ }
+
+ public enum ExerciseUpdate.ExerciseSessionType {
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType AUTO_EXERCISE_DETECTION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType MANUALLY_STARTED_EXERCISE;
+ }
+
+ public final class MeasureCapabilities implements android.os.Parcelable {
+ ctor public MeasureCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public androidx.health.services.client.data.MeasureCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesMeasure();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MeasureCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.MeasureCapabilities.Companion Companion;
+ }
+
+ public static final class MeasureCapabilities.Companion {
+ }
+
+ public final class MilestoneMarkerSummary implements android.os.Parcelable {
+ ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public java.time.Instant component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public androidx.health.services.client.data.AchievedExerciseGoal component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.MilestoneMarkerSummary copy(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public int describeContents();
+ method public androidx.health.services.client.data.AchievedExerciseGoal getAchievedGoal();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public java.time.Instant getStartTime();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getSummaryMetrics();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.AchievedExerciseGoal achievedGoal;
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final java.time.Instant startTime;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MilestoneMarkerSummary> CREATOR;
+ field public static final androidx.health.services.client.data.MilestoneMarkerSummary.Companion Companion;
+ }
+
+ public static final class MilestoneMarkerSummary.Companion {
+ }
+
+ public final class PassiveActivityState implements android.os.Parcelable {
+ ctor public PassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> component1();
+ method public androidx.health.services.client.data.UserActivityState component2();
+ method public androidx.health.services.client.data.ExerciseType? component3();
+ method public java.time.Instant component4();
+ method public androidx.health.services.client.data.PassiveActivityState copy(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public int describeContents();
+ method public static androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints();
+ method public androidx.health.services.client.data.ExerciseType? getExerciseType();
+ method public java.time.Instant getStateChangeTime();
+ method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+ method public void putToIntent(android.content.Intent intent);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<androidx.health.services.client.data.DataPoint> dataPoints;
+ property public final androidx.health.services.client.data.ExerciseType? exerciseType;
+ property public final java.time.Instant stateChangeTime;
+ property public final androidx.health.services.client.data.UserActivityState userActivityState;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveActivityState> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveActivityState.Companion Companion;
+ }
+
+ public static final class PassiveActivityState.Companion {
+ method public androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ }
+
+ public final class PassiveMonitoringCapabilities implements android.os.Parcelable {
+ ctor public PassiveMonitoringCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public androidx.health.services.client.data.PassiveMonitoringCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesEvents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesPassiveMonitoring();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveMonitoringCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveMonitoringCapabilities.Companion Companion;
+ }
+
+ public static final class PassiveMonitoringCapabilities.Companion {
+ }
+
+ public enum UserActivityState {
+ method public static final androidx.health.services.client.data.UserActivityState? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_INACTIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+ field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+ }
+
+ public static final class UserActivityState.Companion {
+ method public androidx.health.services.client.data.UserActivityState? fromId(int id);
+ }
+
+ public final class Value implements android.os.Parcelable {
+ method public boolean asBoolean();
+ method public double asDouble();
+ method public double[] asDoubleArray();
+ method public long asLong();
+ method public int component1();
+ method public java.util.List<java.lang.Double> component2();
+ method public long component3();
+ method public androidx.health.services.client.data.Value copy(int format, java.util.List<java.lang.Double> doubleList, long longValue);
+ method public int describeContents();
+ method public java.util.List<java.lang.Double> getDoubleList();
+ method public int getFormat();
+ method public long getLongValue();
+ method public boolean isBoolean();
+ method public boolean isDouble();
+ method public boolean isDoubleArray();
+ method public boolean isLong();
+ method public static androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public static androidx.health.services.client.data.Value ofDouble(double value);
+ method public static androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public static androidx.health.services.client.data.Value ofLong(long value);
+ method public static androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<java.lang.Double> doubleList;
+ property public final int format;
+ property public final boolean isBoolean;
+ property public final boolean isDouble;
+ property public final boolean isDoubleArray;
+ property public final boolean isLong;
+ property public final long longValue;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Value> CREATOR;
+ field public static final androidx.health.services.client.data.Value.Companion Companion;
+ field public static final int FORMAT_BOOLEAN = 4; // 0x4
+ field public static final int FORMAT_DOUBLE = 1; // 0x1
+ field public static final int FORMAT_DOUBLE_ARRAY = 3; // 0x3
+ field public static final int FORMAT_LONG = 2; // 0x2
+ }
+
+ public static final class Value.Companion {
+ method public androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public androidx.health.services.client.data.Value ofDouble(double value);
+ method public androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public androidx.health.services.client.data.Value ofLong(long value);
+ method public androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ }
+
+}
+
+package androidx.health.services.client.data.event {
+
+ public final class Event implements android.os.Parcelable {
+ ctor public Event(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public androidx.health.services.client.data.DataTypeCondition component1();
+ method public androidx.health.services.client.data.event.Event.TriggerType component2();
+ method public androidx.health.services.client.data.event.Event copy(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.event.Event.TriggerType getTriggerType();
+ method public boolean isTriggered(androidx.health.services.client.data.DataPoint dataPoint);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.event.Event.TriggerType triggerType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.event.Event> CREATOR;
+ field public static final androidx.health.services.client.data.event.Event.Companion Companion;
+ }
+
+ public static final class Event.Companion {
+ }
+
+ public enum Event.TriggerType {
+ method public static final androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType ONCE;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType REPEATED;
+ field public static final androidx.health.services.client.data.event.Event.TriggerType.Companion Companion;
+ }
+
+ public static final class Event.TriggerType.Companion {
+ method public androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ }
+
+}
+
diff --git a/health/health-services-client/api/public_plus_experimental_current.txt b/health/health-services-client/api/public_plus_experimental_current.txt
index e6f50d0..6abe37c 100644
--- a/health/health-services-client/api/public_plus_experimental_current.txt
+++ b/health/health-services-client/api/public_plus_experimental_current.txt
@@ -1 +1,869 @@
// Signature format: 4.0
+package androidx.health.services.client {
+
+ public interface ExerciseClient {
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExercise(androidx.health.services.client.data.ExerciseGoal exerciseGoal);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfo();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLap();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExercise(boolean enabled);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExercise(androidx.health.services.client.data.ExerciseConfig configuration);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> capabilities;
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> currentExerciseInfo;
+ }
+
+ public interface ExerciseUpdateListener {
+ method public void onExerciseUpdate(androidx.health.services.client.data.ExerciseUpdate update);
+ method public void onLapSummary(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+ }
+
+ public final class HealthServices {
+ method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+ field public static final androidx.health.services.client.HealthServices INSTANCE;
+ }
+
+ public interface HealthServicesClient {
+ method public androidx.health.services.client.ExerciseClient getExerciseClient();
+ method public androidx.health.services.client.MeasureClient getMeasureClient();
+ method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+ property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+ property public abstract androidx.health.services.client.MeasureClient measureClient;
+ property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+ }
+
+ public interface MeasureCallback {
+ method public void onAvailabilityChanged(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Availability availability);
+ method public void onData(java.util.List<androidx.health.services.client.data.DataPoint> data);
+ }
+
+ public interface MeasureClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> capabilities;
+ }
+
+ public interface PassiveMonitoringCallback {
+ method public void onPassiveActivityState(androidx.health.services.client.data.PassiveActivityState state);
+ }
+
+ public interface PassiveMonitoringClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent, androidx.health.services.client.PassiveMonitoringCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerEventCallback(androidx.health.services.client.data.event.Event event, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterDataCallback();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterEventCallback(androidx.health.services.client.data.event.Event event);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> capabilities;
+ }
+
+}
+
+package androidx.health.services.client.data {
+
+ public final class AchievedExerciseGoal implements android.os.Parcelable {
+ ctor public AchievedExerciseGoal(androidx.health.services.client.data.ExerciseGoal goal);
+ method public androidx.health.services.client.data.ExerciseGoal component1();
+ method public androidx.health.services.client.data.AchievedExerciseGoal copy(androidx.health.services.client.data.ExerciseGoal goal);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseGoal getGoal();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseGoal goal;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AchievedExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.AchievedExerciseGoal.Companion Companion;
+ }
+
+ public static final class AchievedExerciseGoal.Companion {
+ }
+
+ public final class AutoExerciseConfig implements android.os.Parcelable {
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent, optional android.os.Bundle exerciseParams);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect);
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> component1();
+ method public android.app.PendingIntent? component2();
+ method public android.os.Bundle component3();
+ method public androidx.health.services.client.data.AutoExerciseConfig copy(java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, android.app.PendingIntent? launchIntent, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public android.os.Bundle getExerciseParams();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getExercisesToDetect();
+ method public android.app.PendingIntent? getLaunchIntent();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final android.os.Bundle exerciseParams;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> exercisesToDetect;
+ property public final android.app.PendingIntent? launchIntent;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AutoExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.AutoExerciseConfig.Companion Companion;
+ }
+
+ public static final class AutoExerciseConfig.Companion {
+ }
+
+ public enum Availability {
+ method public static final androidx.health.services.client.data.Availability? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.Availability ACQUIRING;
+ enum_constant public static final androidx.health.services.client.data.Availability AVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNAVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNKNOWN;
+ field public static final androidx.health.services.client.data.Availability.Companion Companion;
+ }
+
+ public static final class Availability.Companion {
+ method public androidx.health.services.client.data.Availability? fromId(int id);
+ }
+
+ public final class Capabilities implements android.os.Parcelable {
+ ctor public Capabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> component1();
+ method public androidx.health.services.client.data.Capabilities copy(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+ method public androidx.health.services.client.data.ExerciseCapabilities getExerciseCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> getExerciseTypeToExerciseCapabilities();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+ property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Capabilities> CREATOR;
+ field public static final androidx.health.services.client.data.Capabilities.Companion Companion;
+ }
+
+ public static final class Capabilities.Companion {
+ }
+
+ public enum ComparisonType {
+ method public static final androidx.health.services.client.data.ComparisonType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+ field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+ }
+
+ public static final class ComparisonType.Companion {
+ method public androidx.health.services.client.data.ComparisonType? fromId(int id);
+ }
+
+ public final class DataPoint implements android.os.Parcelable {
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public java.time.Duration component3();
+ method public java.time.Duration component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.DataPoint copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public java.time.Duration getEndDurationFromBoot();
+ method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+ method public android.os.Bundle getMetadata();
+ method public java.time.Duration getStartDurationFromBoot();
+ method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+ method public androidx.health.services.client.data.Value getValue();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final java.time.Duration endDurationFromBoot;
+ property public final android.os.Bundle metadata;
+ property public final java.time.Duration startDurationFromBoot;
+ property public final androidx.health.services.client.data.Value value;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataPoint> CREATOR;
+ field public static final androidx.health.services.client.data.DataPoint.Companion Companion;
+ }
+
+ public static final class DataPoint.Companion {
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ }
+
+ @Keep public final class DataPoints {
+ method public static androidx.health.services.client.data.DataPoint aggregateCalories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateDistance(double distance, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSteps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSwimmingStrokes(long swimmingStrokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint altitude(double meters, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averagePace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averageSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint calories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint distance(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint elevation(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint floors(double floors, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method @Keep public static java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints(android.content.Intent intent);
+ method public static boolean getPermissionsGranted(android.content.Intent intent);
+ method public static androidx.health.services.client.data.DataPoint heartRate(double bpm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, double altitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint maxSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint pace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static void putDataPoints(android.content.Intent intent, java.util.Collection<androidx.health.services.client.data.DataPoint> dataPoints);
+ method public static void putPermissionsGranted(android.content.Intent intent, boolean granted);
+ method public static androidx.health.services.client.data.DataPoint speed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint spo2(double percent, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint steps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint stepsPerMinute(long stepsPerMinute, java.time.Duration startDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint swimmingStrokes(long strokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ field public static final androidx.health.services.client.data.DataPoints INSTANCE;
+ field public static final int LOCATION_DATA_POINT_ALTITUDE_INDEX = 2; // 0x2
+ field public static final int LOCATION_DATA_POINT_LATITUDE_INDEX = 0; // 0x0
+ field public static final int LOCATION_DATA_POINT_LONGITUDE_INDEX = 1; // 0x1
+ }
+
+ public final class DataType implements android.os.Parcelable {
+ ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public String component1();
+ method public androidx.health.services.client.data.DataType.TimeType component2();
+ method public int component3();
+ method public androidx.health.services.client.data.DataType copy(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public int describeContents();
+ method public int getFormat();
+ method public String getName();
+ method public androidx.health.services.client.data.DataType.TimeType getTimeType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final int format;
+ property public final String name;
+ property public final androidx.health.services.client.data.DataType.TimeType timeType;
+ field public static final androidx.health.services.client.data.DataType ABSOLUTE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType ACTIVE_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_CALORIES_EXPENDED;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLOORS;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_RUNNING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_SWIMMING_STROKE_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_WALKING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_PACE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_SPEED;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataType> CREATOR;
+ field public static final androidx.health.services.client.data.DataType.Companion Companion;
+ field public static final androidx.health.services.client.data.DataType DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType DISTANCE;
+ field public static final androidx.health.services.client.data.DataType ELEVATION;
+ field public static final androidx.health.services.client.data.DataType FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType FLOORS;
+ field public static final androidx.health.services.client.data.DataType HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType LOCATION;
+ field public static final androidx.health.services.client.data.DataType MAX_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType MAX_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType MAX_PACE;
+ field public static final androidx.health.services.client.data.DataType MAX_SPEED;
+ field public static final androidx.health.services.client.data.DataType MIN_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType PACE;
+ field public static final androidx.health.services.client.data.DataType REP_COUNT;
+ field public static final androidx.health.services.client.data.DataType RESTING_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType RUNNING_STEPS;
+ field public static final androidx.health.services.client.data.DataType SPEED;
+ field public static final androidx.health.services.client.data.DataType SPO2;
+ field public static final androidx.health.services.client.data.DataType STEPS;
+ field public static final androidx.health.services.client.data.DataType STEPS_PER_MINUTE;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_LAP_COUNT;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_STROKES;
+ field public static final androidx.health.services.client.data.DataType TOTAL_CALORIES;
+ field public static final androidx.health.services.client.data.DataType VO2;
+ field public static final androidx.health.services.client.data.DataType VO2_MAX;
+ field public static final androidx.health.services.client.data.DataType WALKING_STEPS;
+ }
+
+ public static final class DataType.Companion {
+ }
+
+ public enum DataType.TimeType {
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+ }
+
+ public final class DataTypeCondition implements android.os.Parcelable {
+ ctor public DataTypeCondition(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public androidx.health.services.client.data.ComparisonType component3();
+ method public androidx.health.services.client.data.DataTypeCondition copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ComparisonType getComparisonType();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public androidx.health.services.client.data.Value getThreshold();
+ method public boolean isSatisfied(androidx.health.services.client.data.DataPoint dataPoint);
+ method public boolean isThresholdSatisfied(androidx.health.services.client.data.Value value);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ComparisonType comparisonType;
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final androidx.health.services.client.data.Value threshold;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataTypeCondition> CREATOR;
+ field public static final androidx.health.services.client.data.DataTypeCondition.Companion Companion;
+ }
+
+ public static final class DataTypeCondition.Companion {
+ }
+
+ public final class DataTypes {
+ method public static androidx.health.services.client.data.DataType? getAggregateTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static java.util.Set<androidx.health.services.client.data.DataType> getAggregatedDataTypesFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getAverageTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getMaxTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAggregateType(androidx.health.services.client.data.DataType aggregateType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAverageType(androidx.health.services.client.data.DataType averageType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromMaxType(androidx.health.services.client.data.DataType maxType);
+ method public static boolean isAggregateDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isRawType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalAverageDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalMaxDataType(androidx.health.services.client.data.DataType dataType);
+ field public static final androidx.health.services.client.data.DataTypes INSTANCE;
+ }
+
+ public final class ExerciseCapabilities implements android.os.Parcelable {
+ ctor public ExerciseCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component2();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component3();
+ method public boolean component4();
+ method public boolean component5();
+ method public androidx.health.services.client.data.ExerciseCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypes();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+ method public boolean getSupportsAutoPauseAndResume();
+ method public boolean getSupportsLaps();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+ property public final boolean supportsAutoPauseAndResume;
+ property public final boolean supportsLaps;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseCapabilities.Companion Companion;
+ }
+
+ public static final class ExerciseCapabilities.Companion {
+ }
+
+ public final class ExerciseConfig implements android.os.Parcelable {
+ ctor protected ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public static androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ method public androidx.health.services.client.data.ExerciseType component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public boolean component3();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.ExerciseConfig copy(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public boolean getAutoPauseAndResume();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getDataTypes();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> getExerciseGoals();
+ method public android.os.Bundle getExerciseParams();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final boolean autoPauseAndResume;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> dataTypes;
+ property public final java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals;
+ property public final android.os.Bundle exerciseParams;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+ }
+
+ public static final class ExerciseConfig.Builder {
+ ctor public ExerciseConfig.Builder();
+ method public androidx.health.services.client.data.ExerciseConfig build();
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setAutoPauseAndResume(boolean autoPauseAndResume);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<androidx.health.services.client.data.DataType> dataTypes);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseType(androidx.health.services.client.data.ExerciseType exerciseType);
+ }
+
+ public static final class ExerciseConfig.Companion {
+ method public androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ }
+
+ public final class ExerciseGoal implements android.os.Parcelable {
+ ctor protected ExerciseGoal(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, optional androidx.health.services.client.data.Value? period);
+ method public androidx.health.services.client.data.ExerciseGoalType component1();
+ method public androidx.health.services.client.data.DataTypeCondition component2();
+ method public androidx.health.services.client.data.Value? component3();
+ method public androidx.health.services.client.data.ExerciseGoal copy(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.Value? period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public static androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+ method public androidx.health.services.client.data.Value? getPeriod();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+ property public final androidx.health.services.client.data.Value? period;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+ }
+
+ public static final class ExerciseGoal.Companion {
+ method public androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ }
+
+ public enum ExerciseGoalType {
+ method public static final androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+ field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+ }
+
+ public static final class ExerciseGoalType.Companion {
+ method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ }
+
+ public final class ExerciseInfo implements android.os.Parcelable {
+ ctor public ExerciseInfo(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public androidx.health.services.client.data.ExerciseTrackedStatus component1();
+ method public androidx.health.services.client.data.ExerciseType component2();
+ method public androidx.health.services.client.data.ExerciseInfo copy(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseTrackedStatus getExerciseTrackedStatus();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseInfo> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseInfo.Companion Companion;
+ }
+
+ public static final class ExerciseInfo.Companion {
+ }
+
+ public final class ExerciseLapSummary implements android.os.Parcelable {
+ ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int component1();
+ method public java.time.Instant component2();
+ method public java.time.Instant component3();
+ method public java.time.Duration component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.ExerciseLapSummary copy(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public int getLapCount();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getLapMetrics();
+ method public java.time.Instant getStartTime();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final int lapCount;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics;
+ property public final java.time.Instant startTime;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseLapSummary> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseLapSummary.Companion Companion;
+ }
+
+ public static final class ExerciseLapSummary.Companion {
+ }
+
+ public enum ExerciseState {
+ method public static final androidx.health.services.client.data.ExerciseState? fromId(int id);
+ method public final int getId();
+ method public final boolean isEnded();
+ method public final boolean isPaused();
+ method public final boolean isResuming();
+ property public final int id;
+ property public final boolean isEnded;
+ property public final boolean isPaused;
+ property public final boolean isResuming;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+ field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+ }
+
+ public static final class ExerciseState.Companion {
+ method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+ }
+
+ public enum ExerciseTrackedStatus {
+ method public static final androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus NO_EXERCISE_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OTHER_APP_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OWNED_EXERCISE_IN_PROGRESS;
+ field public static final androidx.health.services.client.data.ExerciseTrackedStatus.Companion Companion;
+ }
+
+ public static final class ExerciseTrackedStatus.Companion {
+ method public androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ }
+
+ public enum ExerciseType {
+ method public static final androidx.health.services.client.data.ExerciseType fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_SIT_UP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOXING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BURPEE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRICKET;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DANCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_FRONT_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_LATERAL_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_TWO_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FENCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GOLF;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GYNMASTICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LUNGE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PADDLING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PILATES;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PLANK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUGBY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SAILING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKIING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOCCER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUASH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUAT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SURFING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WALKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_INDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_OUTDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType YOGA;
+ field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+ }
+
+ public static final class ExerciseType.Companion {
+ method public androidx.health.services.client.data.ExerciseType fromId(int id);
+ }
+
+ public final class ExerciseUpdate implements android.os.Parcelable {
+ ctor public ExerciseUpdate(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public androidx.health.services.client.data.ExerciseState component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> component4();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> component5();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> component6();
+ method public androidx.health.services.client.data.ExerciseConfig? component7();
+ method public androidx.health.services.client.data.AutoExerciseConfig? component8();
+ method public androidx.health.services.client.data.ExerciseType? component9();
+ method public androidx.health.services.client.data.ExerciseUpdate copy(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public androidx.health.services.client.data.AutoExerciseConfig? getAutoExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseType? getAutoExerciseDetected();
+ method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType getExerciseSessionType();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> getLatestAchievedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> getLatestMetrics();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+ method public java.time.Instant getStartTime();
+ method public androidx.health.services.client.data.ExerciseState getState();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig;
+ property public final androidx.health.services.client.data.ExerciseType? autoExerciseDetected;
+ property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+ property public final java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics;
+ property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+ property public final java.time.Instant startTime;
+ property public final androidx.health.services.client.data.ExerciseState state;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseUpdate> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+ }
+
+ public static final class ExerciseUpdate.Companion {
+ }
+
+ public enum ExerciseUpdate.ExerciseSessionType {
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType AUTO_EXERCISE_DETECTION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType MANUALLY_STARTED_EXERCISE;
+ }
+
+ public final class MeasureCapabilities implements android.os.Parcelable {
+ ctor public MeasureCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public androidx.health.services.client.data.MeasureCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesMeasure();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MeasureCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.MeasureCapabilities.Companion Companion;
+ }
+
+ public static final class MeasureCapabilities.Companion {
+ }
+
+ public final class MilestoneMarkerSummary implements android.os.Parcelable {
+ ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public java.time.Instant component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public androidx.health.services.client.data.AchievedExerciseGoal component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.MilestoneMarkerSummary copy(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public int describeContents();
+ method public androidx.health.services.client.data.AchievedExerciseGoal getAchievedGoal();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public java.time.Instant getStartTime();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getSummaryMetrics();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.AchievedExerciseGoal achievedGoal;
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final java.time.Instant startTime;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MilestoneMarkerSummary> CREATOR;
+ field public static final androidx.health.services.client.data.MilestoneMarkerSummary.Companion Companion;
+ }
+
+ public static final class MilestoneMarkerSummary.Companion {
+ }
+
+ public final class PassiveActivityState implements android.os.Parcelable {
+ ctor public PassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> component1();
+ method public androidx.health.services.client.data.UserActivityState component2();
+ method public androidx.health.services.client.data.ExerciseType? component3();
+ method public java.time.Instant component4();
+ method public androidx.health.services.client.data.PassiveActivityState copy(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public int describeContents();
+ method public static androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints();
+ method public androidx.health.services.client.data.ExerciseType? getExerciseType();
+ method public java.time.Instant getStateChangeTime();
+ method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+ method public void putToIntent(android.content.Intent intent);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<androidx.health.services.client.data.DataPoint> dataPoints;
+ property public final androidx.health.services.client.data.ExerciseType? exerciseType;
+ property public final java.time.Instant stateChangeTime;
+ property public final androidx.health.services.client.data.UserActivityState userActivityState;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveActivityState> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveActivityState.Companion Companion;
+ }
+
+ public static final class PassiveActivityState.Companion {
+ method public androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ }
+
+ public final class PassiveMonitoringCapabilities implements android.os.Parcelable {
+ ctor public PassiveMonitoringCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public androidx.health.services.client.data.PassiveMonitoringCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesEvents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesPassiveMonitoring();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveMonitoringCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveMonitoringCapabilities.Companion Companion;
+ }
+
+ public static final class PassiveMonitoringCapabilities.Companion {
+ }
+
+ public enum UserActivityState {
+ method public static final androidx.health.services.client.data.UserActivityState? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_INACTIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+ field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+ }
+
+ public static final class UserActivityState.Companion {
+ method public androidx.health.services.client.data.UserActivityState? fromId(int id);
+ }
+
+ public final class Value implements android.os.Parcelable {
+ method public boolean asBoolean();
+ method public double asDouble();
+ method public double[] asDoubleArray();
+ method public long asLong();
+ method public int component1();
+ method public java.util.List<java.lang.Double> component2();
+ method public long component3();
+ method public androidx.health.services.client.data.Value copy(int format, java.util.List<java.lang.Double> doubleList, long longValue);
+ method public int describeContents();
+ method public java.util.List<java.lang.Double> getDoubleList();
+ method public int getFormat();
+ method public long getLongValue();
+ method public boolean isBoolean();
+ method public boolean isDouble();
+ method public boolean isDoubleArray();
+ method public boolean isLong();
+ method public static androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public static androidx.health.services.client.data.Value ofDouble(double value);
+ method public static androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public static androidx.health.services.client.data.Value ofLong(long value);
+ method public static androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<java.lang.Double> doubleList;
+ property public final int format;
+ property public final boolean isBoolean;
+ property public final boolean isDouble;
+ property public final boolean isDoubleArray;
+ property public final boolean isLong;
+ property public final long longValue;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Value> CREATOR;
+ field public static final androidx.health.services.client.data.Value.Companion Companion;
+ field public static final int FORMAT_BOOLEAN = 4; // 0x4
+ field public static final int FORMAT_DOUBLE = 1; // 0x1
+ field public static final int FORMAT_DOUBLE_ARRAY = 3; // 0x3
+ field public static final int FORMAT_LONG = 2; // 0x2
+ }
+
+ public static final class Value.Companion {
+ method public androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public androidx.health.services.client.data.Value ofDouble(double value);
+ method public androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public androidx.health.services.client.data.Value ofLong(long value);
+ method public androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ }
+
+}
+
+package androidx.health.services.client.data.event {
+
+ public final class Event implements android.os.Parcelable {
+ ctor public Event(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public androidx.health.services.client.data.DataTypeCondition component1();
+ method public androidx.health.services.client.data.event.Event.TriggerType component2();
+ method public androidx.health.services.client.data.event.Event copy(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.event.Event.TriggerType getTriggerType();
+ method public boolean isTriggered(androidx.health.services.client.data.DataPoint dataPoint);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.event.Event.TriggerType triggerType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.event.Event> CREATOR;
+ field public static final androidx.health.services.client.data.event.Event.Companion Companion;
+ }
+
+ public static final class Event.Companion {
+ }
+
+ public enum Event.TriggerType {
+ method public static final androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType ONCE;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType REPEATED;
+ field public static final androidx.health.services.client.data.event.Event.TriggerType.Companion Companion;
+ }
+
+ public static final class Event.TriggerType.Companion {
+ method public androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ }
+
+}
+
diff --git a/health/health-services-client/api/restricted_current.txt b/health/health-services-client/api/restricted_current.txt
index e6f50d0..6abe37c 100644
--- a/health/health-services-client/api/restricted_current.txt
+++ b/health/health-services-client/api/restricted_current.txt
@@ -1 +1,869 @@
// Signature format: 4.0
+package androidx.health.services.client {
+
+ public interface ExerciseClient {
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExercise(androidx.health.services.client.data.ExerciseGoal exerciseGoal);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfo();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLap();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExercise(boolean enabled);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExercise();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExercise(androidx.health.services.client.data.ExerciseConfig configuration);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> capabilities;
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> currentExerciseInfo;
+ }
+
+ public interface ExerciseUpdateListener {
+ method public void onExerciseUpdate(androidx.health.services.client.data.ExerciseUpdate update);
+ method public void onLapSummary(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+ }
+
+ public final class HealthServices {
+ method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+ field public static final androidx.health.services.client.HealthServices INSTANCE;
+ }
+
+ public interface HealthServicesClient {
+ method public androidx.health.services.client.ExerciseClient getExerciseClient();
+ method public androidx.health.services.client.MeasureClient getMeasureClient();
+ method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+ property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+ property public abstract androidx.health.services.client.MeasureClient measureClient;
+ property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+ }
+
+ public interface MeasureCallback {
+ method public void onAvailabilityChanged(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Availability availability);
+ method public void onData(java.util.List<androidx.health.services.client.data.DataPoint> data);
+ }
+
+ public interface MeasureClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback, java.util.concurrent.Executor executor);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> capabilities;
+ }
+
+ public interface PassiveMonitoringCallback {
+ method public void onPassiveActivityState(androidx.health.services.client.data.PassiveActivityState state);
+ }
+
+ public interface PassiveMonitoringClient {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilities();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent, androidx.health.services.client.PassiveMonitoringCallback callback);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerEventCallback(androidx.health.services.client.data.event.Event event, android.app.PendingIntent callbackIntent);
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterDataCallback();
+ method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterEventCallback(androidx.health.services.client.data.event.Event event);
+ property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> capabilities;
+ }
+
+}
+
+package androidx.health.services.client.data {
+
+ public final class AchievedExerciseGoal implements android.os.Parcelable {
+ ctor public AchievedExerciseGoal(androidx.health.services.client.data.ExerciseGoal goal);
+ method public androidx.health.services.client.data.ExerciseGoal component1();
+ method public androidx.health.services.client.data.AchievedExerciseGoal copy(androidx.health.services.client.data.ExerciseGoal goal);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseGoal getGoal();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseGoal goal;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AchievedExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.AchievedExerciseGoal.Companion Companion;
+ }
+
+ public static final class AchievedExerciseGoal.Companion {
+ }
+
+ public final class AutoExerciseConfig implements android.os.Parcelable {
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent, optional android.os.Bundle exerciseParams);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent);
+ ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect);
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> component1();
+ method public android.app.PendingIntent? component2();
+ method public android.os.Bundle component3();
+ method public androidx.health.services.client.data.AutoExerciseConfig copy(java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, android.app.PendingIntent? launchIntent, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public android.os.Bundle getExerciseParams();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getExercisesToDetect();
+ method public android.app.PendingIntent? getLaunchIntent();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final android.os.Bundle exerciseParams;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> exercisesToDetect;
+ property public final android.app.PendingIntent? launchIntent;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AutoExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.AutoExerciseConfig.Companion Companion;
+ }
+
+ public static final class AutoExerciseConfig.Companion {
+ }
+
+ public enum Availability {
+ method public static final androidx.health.services.client.data.Availability? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.Availability ACQUIRING;
+ enum_constant public static final androidx.health.services.client.data.Availability AVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNAVAILABLE;
+ enum_constant public static final androidx.health.services.client.data.Availability UNKNOWN;
+ field public static final androidx.health.services.client.data.Availability.Companion Companion;
+ }
+
+ public static final class Availability.Companion {
+ method public androidx.health.services.client.data.Availability? fromId(int id);
+ }
+
+ public final class Capabilities implements android.os.Parcelable {
+ ctor public Capabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> component1();
+ method public androidx.health.services.client.data.Capabilities copy(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+ method public androidx.health.services.client.data.ExerciseCapabilities getExerciseCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+ method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> getExerciseTypeToExerciseCapabilities();
+ method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+ property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities;
+ property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Capabilities> CREATOR;
+ field public static final androidx.health.services.client.data.Capabilities.Companion Companion;
+ }
+
+ public static final class Capabilities.Companion {
+ }
+
+ public enum ComparisonType {
+ method public static final androidx.health.services.client.data.ComparisonType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+ enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+ field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+ }
+
+ public static final class ComparisonType.Companion {
+ method public androidx.health.services.client.data.ComparisonType? fromId(int id);
+ }
+
+ public final class DataPoint implements android.os.Parcelable {
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public java.time.Duration component3();
+ method public java.time.Duration component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.DataPoint copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public java.time.Duration getEndDurationFromBoot();
+ method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+ method public android.os.Bundle getMetadata();
+ method public java.time.Duration getStartDurationFromBoot();
+ method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+ method public androidx.health.services.client.data.Value getValue();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final java.time.Duration endDurationFromBoot;
+ property public final android.os.Bundle metadata;
+ property public final java.time.Duration startDurationFromBoot;
+ property public final androidx.health.services.client.data.Value value;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataPoint> CREATOR;
+ field public static final androidx.health.services.client.data.DataPoint.Companion Companion;
+ }
+
+ public static final class DataPoint.Companion {
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+ method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+ }
+
+ @Keep public final class DataPoints {
+ method public static androidx.health.services.client.data.DataPoint aggregateCalories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateDistance(double distance, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSteps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint aggregateSwimmingStrokes(long swimmingStrokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint altitude(double meters, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averagePace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint averageSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint calories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint distance(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint elevation(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint floors(double floors, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method @Keep public static java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints(android.content.Intent intent);
+ method public static boolean getPermissionsGranted(android.content.Intent intent);
+ method public static androidx.health.services.client.data.DataPoint heartRate(double bpm, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, double altitude, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint maxSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint pace(double millisPerKm, java.time.Duration durationFromBoot);
+ method public static void putDataPoints(android.content.Intent intent, java.util.Collection<androidx.health.services.client.data.DataPoint> dataPoints);
+ method public static void putPermissionsGranted(android.content.Intent intent, boolean granted);
+ method public static androidx.health.services.client.data.DataPoint speed(double metersPerSecond, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint spo2(double percent, java.time.Duration durationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint steps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint stepsPerMinute(long stepsPerMinute, java.time.Duration startDurationFromBoot);
+ method public static androidx.health.services.client.data.DataPoint swimmingStrokes(long strokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+ field public static final androidx.health.services.client.data.DataPoints INSTANCE;
+ field public static final int LOCATION_DATA_POINT_ALTITUDE_INDEX = 2; // 0x2
+ field public static final int LOCATION_DATA_POINT_LATITUDE_INDEX = 0; // 0x0
+ field public static final int LOCATION_DATA_POINT_LONGITUDE_INDEX = 1; // 0x1
+ }
+
+ public final class DataType implements android.os.Parcelable {
+ ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public String component1();
+ method public androidx.health.services.client.data.DataType.TimeType component2();
+ method public int component3();
+ method public androidx.health.services.client.data.DataType copy(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+ method public int describeContents();
+ method public int getFormat();
+ method public String getName();
+ method public androidx.health.services.client.data.DataType.TimeType getTimeType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final int format;
+ property public final String name;
+ property public final androidx.health.services.client.data.DataType.TimeType timeType;
+ field public static final androidx.health.services.client.data.DataType ABSOLUTE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType ACTIVE_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_CALORIES_EXPENDED;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_ELEVATION;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_FLOORS;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_RUNNING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_SWIMMING_STROKE_COUNT;
+ field public static final androidx.health.services.client.data.DataType AGGREGATE_WALKING_STEP_COUNT;
+ field public static final androidx.health.services.client.data.DataType ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_PACE;
+ field public static final androidx.health.services.client.data.DataType AVERAGE_SPEED;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataType> CREATOR;
+ field public static final androidx.health.services.client.data.DataType.Companion Companion;
+ field public static final androidx.health.services.client.data.DataType DECLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType DECLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType DISTANCE;
+ field public static final androidx.health.services.client.data.DataType ELEVATION;
+ field public static final androidx.health.services.client.data.DataType FLAT_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType FLAT_TIME;
+ field public static final androidx.health.services.client.data.DataType FLOORS;
+ field public static final androidx.health.services.client.data.DataType HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType INCLINE_DISTANCE;
+ field public static final androidx.health.services.client.data.DataType INCLINE_TIME;
+ field public static final androidx.health.services.client.data.DataType LOCATION;
+ field public static final androidx.health.services.client.data.DataType MAX_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType MAX_HEART_RATE_BPM;
+ field public static final androidx.health.services.client.data.DataType MAX_PACE;
+ field public static final androidx.health.services.client.data.DataType MAX_SPEED;
+ field public static final androidx.health.services.client.data.DataType MIN_ALTITUDE;
+ field public static final androidx.health.services.client.data.DataType PACE;
+ field public static final androidx.health.services.client.data.DataType REP_COUNT;
+ field public static final androidx.health.services.client.data.DataType RESTING_EXERCISE_DURATION;
+ field public static final androidx.health.services.client.data.DataType RUNNING_STEPS;
+ field public static final androidx.health.services.client.data.DataType SPEED;
+ field public static final androidx.health.services.client.data.DataType SPO2;
+ field public static final androidx.health.services.client.data.DataType STEPS;
+ field public static final androidx.health.services.client.data.DataType STEPS_PER_MINUTE;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_LAP_COUNT;
+ field public static final androidx.health.services.client.data.DataType SWIMMING_STROKES;
+ field public static final androidx.health.services.client.data.DataType TOTAL_CALORIES;
+ field public static final androidx.health.services.client.data.DataType VO2;
+ field public static final androidx.health.services.client.data.DataType VO2_MAX;
+ field public static final androidx.health.services.client.data.DataType WALKING_STEPS;
+ }
+
+ public static final class DataType.Companion {
+ }
+
+ public enum DataType.TimeType {
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+ enum_constant public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+ }
+
+ public final class DataTypeCondition implements android.os.Parcelable {
+ ctor public DataTypeCondition(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public androidx.health.services.client.data.DataType component1();
+ method public androidx.health.services.client.data.Value component2();
+ method public androidx.health.services.client.data.ComparisonType component3();
+ method public androidx.health.services.client.data.DataTypeCondition copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ComparisonType getComparisonType();
+ method public androidx.health.services.client.data.DataType getDataType();
+ method public androidx.health.services.client.data.Value getThreshold();
+ method public boolean isSatisfied(androidx.health.services.client.data.DataPoint dataPoint);
+ method public boolean isThresholdSatisfied(androidx.health.services.client.data.Value value);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ComparisonType comparisonType;
+ property public final androidx.health.services.client.data.DataType dataType;
+ property public final androidx.health.services.client.data.Value threshold;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataTypeCondition> CREATOR;
+ field public static final androidx.health.services.client.data.DataTypeCondition.Companion Companion;
+ }
+
+ public static final class DataTypeCondition.Companion {
+ }
+
+ public final class DataTypes {
+ method public static androidx.health.services.client.data.DataType? getAggregateTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static java.util.Set<androidx.health.services.client.data.DataType> getAggregatedDataTypesFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getAverageTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getMaxTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAggregateType(androidx.health.services.client.data.DataType aggregateType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromAverageType(androidx.health.services.client.data.DataType averageType);
+ method public static androidx.health.services.client.data.DataType? getRawTypeFromMaxType(androidx.health.services.client.data.DataType maxType);
+ method public static boolean isAggregateDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isRawType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalAverageDataType(androidx.health.services.client.data.DataType dataType);
+ method public static boolean isStatisticalMaxDataType(androidx.health.services.client.data.DataType dataType);
+ field public static final androidx.health.services.client.data.DataTypes INSTANCE;
+ }
+
+ public final class ExerciseCapabilities implements android.os.Parcelable {
+ ctor public ExerciseCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component2();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component3();
+ method public boolean component4();
+ method public boolean component5();
+ method public androidx.health.services.client.data.ExerciseCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypes();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+ method public boolean getSupportsAutoPauseAndResume();
+ method public boolean getSupportsLaps();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+ property public final boolean supportsAutoPauseAndResume;
+ property public final boolean supportsLaps;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseCapabilities.Companion Companion;
+ }
+
+ public static final class ExerciseCapabilities.Companion {
+ }
+
+ public final class ExerciseConfig implements android.os.Parcelable {
+ ctor protected ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public static androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ method public androidx.health.services.client.data.ExerciseType component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public boolean component3();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> component4();
+ method public android.os.Bundle component5();
+ method public androidx.health.services.client.data.ExerciseConfig copy(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+ method public int describeContents();
+ method public boolean getAutoPauseAndResume();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getDataTypes();
+ method public java.util.List<androidx.health.services.client.data.ExerciseGoal> getExerciseGoals();
+ method public android.os.Bundle getExerciseParams();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final boolean autoPauseAndResume;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> dataTypes;
+ property public final java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals;
+ property public final android.os.Bundle exerciseParams;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseConfig> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+ }
+
+ public static final class ExerciseConfig.Builder {
+ ctor public ExerciseConfig.Builder();
+ method public androidx.health.services.client.data.ExerciseConfig build();
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setAutoPauseAndResume(boolean autoPauseAndResume);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<androidx.health.services.client.data.DataType> dataTypes);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+ method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseType(androidx.health.services.client.data.ExerciseType exerciseType);
+ }
+
+ public static final class ExerciseConfig.Companion {
+ method public androidx.health.services.client.data.ExerciseConfig.Builder builder();
+ }
+
+ public final class ExerciseGoal implements android.os.Parcelable {
+ ctor protected ExerciseGoal(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, optional androidx.health.services.client.data.Value? period);
+ method public androidx.health.services.client.data.ExerciseGoalType component1();
+ method public androidx.health.services.client.data.DataTypeCondition component2();
+ method public androidx.health.services.client.data.Value? component3();
+ method public androidx.health.services.client.data.ExerciseGoal copy(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.Value? period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public static androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public static androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+ method public androidx.health.services.client.data.Value? getPeriod();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+ property public final androidx.health.services.client.data.Value? period;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+ }
+
+ public static final class ExerciseGoal.Companion {
+ method public androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+ method public androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+ method public androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+ }
+
+ public enum ExerciseGoalType {
+ method public static final androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+ field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+ }
+
+ public static final class ExerciseGoalType.Companion {
+ method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+ }
+
+ public final class ExerciseInfo implements android.os.Parcelable {
+ ctor public ExerciseInfo(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public androidx.health.services.client.data.ExerciseTrackedStatus component1();
+ method public androidx.health.services.client.data.ExerciseType component2();
+ method public androidx.health.services.client.data.ExerciseInfo copy(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.ExerciseTrackedStatus getExerciseTrackedStatus();
+ method public androidx.health.services.client.data.ExerciseType getExerciseType();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus;
+ property public final androidx.health.services.client.data.ExerciseType exerciseType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseInfo> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseInfo.Companion Companion;
+ }
+
+ public static final class ExerciseInfo.Companion {
+ }
+
+ public final class ExerciseLapSummary implements android.os.Parcelable {
+ ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int component1();
+ method public java.time.Instant component2();
+ method public java.time.Instant component3();
+ method public java.time.Duration component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.ExerciseLapSummary copy(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public int getLapCount();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getLapMetrics();
+ method public java.time.Instant getStartTime();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final int lapCount;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics;
+ property public final java.time.Instant startTime;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseLapSummary> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseLapSummary.Companion Companion;
+ }
+
+ public static final class ExerciseLapSummary.Companion {
+ }
+
+ public enum ExerciseState {
+ method public static final androidx.health.services.client.data.ExerciseState? fromId(int id);
+ method public final int getId();
+ method public final boolean isEnded();
+ method public final boolean isPaused();
+ method public final boolean isResuming();
+ property public final int id;
+ property public final boolean isEnded;
+ property public final boolean isPaused;
+ property public final boolean isResuming;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+ field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+ }
+
+ public static final class ExerciseState.Companion {
+ method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+ }
+
+ public enum ExerciseTrackedStatus {
+ method public static final androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus NO_EXERCISE_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OTHER_APP_IN_PROGRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OWNED_EXERCISE_IN_PROGRESS;
+ field public static final androidx.health.services.client.data.ExerciseTrackedStatus.Companion Companion;
+ }
+
+ public static final class ExerciseTrackedStatus.Companion {
+ method public androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+ }
+
+ public enum ExerciseType {
+ method public static final androidx.health.services.client.data.ExerciseType fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_SIT_UP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BOXING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType BURPEE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRICKET;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DANCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_FRONT_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_LATERAL_RAISE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_TWO_ARM;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FENCING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GOLF;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType GYNMASTICS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType HIKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType LUNGE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PADDLING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PILATES;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType PLANK;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUGBY;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SAILING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKATING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SKIING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOCCER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUASH;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SQUAT;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SURFING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType TENNIS;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WALKING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_INDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_OUTDOOR;
+ enum_constant public static final androidx.health.services.client.data.ExerciseType YOGA;
+ field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+ }
+
+ public static final class ExerciseType.Companion {
+ method public androidx.health.services.client.data.ExerciseType fromId(int id);
+ }
+
+ public final class ExerciseUpdate implements android.os.Parcelable {
+ ctor public ExerciseUpdate(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public androidx.health.services.client.data.ExerciseState component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> component4();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> component5();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> component6();
+ method public androidx.health.services.client.data.ExerciseConfig? component7();
+ method public androidx.health.services.client.data.AutoExerciseConfig? component8();
+ method public androidx.health.services.client.data.ExerciseType? component9();
+ method public androidx.health.services.client.data.ExerciseUpdate copy(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+ method public int describeContents();
+ method public java.time.Duration getActiveDuration();
+ method public androidx.health.services.client.data.AutoExerciseConfig? getAutoExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseType? getAutoExerciseDetected();
+ method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+ method public androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType getExerciseSessionType();
+ method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> getLatestAchievedGoals();
+ method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> getLatestMetrics();
+ method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+ method public java.time.Instant getStartTime();
+ method public androidx.health.services.client.data.ExerciseState getState();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.time.Duration activeDuration;
+ property public final androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig;
+ property public final androidx.health.services.client.data.ExerciseType? autoExerciseDetected;
+ property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+ property public final java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics;
+ property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+ property public final java.time.Instant startTime;
+ property public final androidx.health.services.client.data.ExerciseState state;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseUpdate> CREATOR;
+ field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+ }
+
+ public static final class ExerciseUpdate.Companion {
+ }
+
+ public enum ExerciseUpdate.ExerciseSessionType {
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType AUTO_EXERCISE_DETECTION;
+ enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType MANUALLY_STARTED_EXERCISE;
+ }
+
+ public final class MeasureCapabilities implements android.os.Parcelable {
+ ctor public MeasureCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public androidx.health.services.client.data.MeasureCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesMeasure();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MeasureCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.MeasureCapabilities.Companion Companion;
+ }
+
+ public static final class MeasureCapabilities.Companion {
+ }
+
+ public final class MilestoneMarkerSummary implements android.os.Parcelable {
+ ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public java.time.Instant component1();
+ method public java.time.Instant component2();
+ method public java.time.Duration component3();
+ method public androidx.health.services.client.data.AchievedExerciseGoal component4();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+ method public androidx.health.services.client.data.MilestoneMarkerSummary copy(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+ method public int describeContents();
+ method public androidx.health.services.client.data.AchievedExerciseGoal getAchievedGoal();
+ method public java.time.Duration getActiveDuration();
+ method public java.time.Instant getEndTime();
+ method public java.time.Instant getStartTime();
+ method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getSummaryMetrics();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.AchievedExerciseGoal achievedGoal;
+ property public final java.time.Duration activeDuration;
+ property public final java.time.Instant endTime;
+ property public final java.time.Instant startTime;
+ property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MilestoneMarkerSummary> CREATOR;
+ field public static final androidx.health.services.client.data.MilestoneMarkerSummary.Companion Companion;
+ }
+
+ public static final class MilestoneMarkerSummary.Companion {
+ }
+
+ public final class PassiveActivityState implements android.os.Parcelable {
+ ctor public PassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> component1();
+ method public androidx.health.services.client.data.UserActivityState component2();
+ method public androidx.health.services.client.data.ExerciseType? component3();
+ method public java.time.Instant component4();
+ method public androidx.health.services.client.data.PassiveActivityState copy(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public static androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public int describeContents();
+ method public static androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ method public java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints();
+ method public androidx.health.services.client.data.ExerciseType? getExerciseType();
+ method public java.time.Instant getStateChangeTime();
+ method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+ method public void putToIntent(android.content.Intent intent);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<androidx.health.services.client.data.DataPoint> dataPoints;
+ property public final androidx.health.services.client.data.ExerciseType? exerciseType;
+ property public final java.time.Instant stateChangeTime;
+ property public final androidx.health.services.client.data.UserActivityState userActivityState;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveActivityState> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveActivityState.Companion Companion;
+ }
+
+ public static final class PassiveActivityState.Companion {
+ method public androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+ method public androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+ }
+
+ public final class PassiveMonitoringCapabilities implements android.os.Parcelable {
+ ctor public PassiveMonitoringCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+ method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+ method public androidx.health.services.client.data.PassiveMonitoringCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+ method public int describeContents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesEvents();
+ method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesPassiveMonitoring();
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents;
+ property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveMonitoringCapabilities> CREATOR;
+ field public static final androidx.health.services.client.data.PassiveMonitoringCapabilities.Companion Companion;
+ }
+
+ public static final class PassiveMonitoringCapabilities.Companion {
+ }
+
+ public enum UserActivityState {
+ method public static final androidx.health.services.client.data.UserActivityState? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_INACTIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+ enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+ field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+ }
+
+ public static final class UserActivityState.Companion {
+ method public androidx.health.services.client.data.UserActivityState? fromId(int id);
+ }
+
+ public final class Value implements android.os.Parcelable {
+ method public boolean asBoolean();
+ method public double asDouble();
+ method public double[] asDoubleArray();
+ method public long asLong();
+ method public int component1();
+ method public java.util.List<java.lang.Double> component2();
+ method public long component3();
+ method public androidx.health.services.client.data.Value copy(int format, java.util.List<java.lang.Double> doubleList, long longValue);
+ method public int describeContents();
+ method public java.util.List<java.lang.Double> getDoubleList();
+ method public int getFormat();
+ method public long getLongValue();
+ method public boolean isBoolean();
+ method public boolean isDouble();
+ method public boolean isDoubleArray();
+ method public boolean isLong();
+ method public static androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public static androidx.health.services.client.data.Value ofDouble(double value);
+ method public static androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public static androidx.health.services.client.data.Value ofLong(long value);
+ method public static androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final java.util.List<java.lang.Double> doubleList;
+ property public final int format;
+ property public final boolean isBoolean;
+ property public final boolean isDouble;
+ property public final boolean isDoubleArray;
+ property public final boolean isLong;
+ property public final long longValue;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Value> CREATOR;
+ field public static final androidx.health.services.client.data.Value.Companion Companion;
+ field public static final int FORMAT_BOOLEAN = 4; // 0x4
+ field public static final int FORMAT_DOUBLE = 1; // 0x1
+ field public static final int FORMAT_DOUBLE_ARRAY = 3; // 0x3
+ field public static final int FORMAT_LONG = 2; // 0x2
+ }
+
+ public static final class Value.Companion {
+ method public androidx.health.services.client.data.Value ofBoolean(boolean value);
+ method public androidx.health.services.client.data.Value ofDouble(double value);
+ method public androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+ method public androidx.health.services.client.data.Value ofLong(long value);
+ method public androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+ }
+
+}
+
+package androidx.health.services.client.data.event {
+
+ public final class Event implements android.os.Parcelable {
+ ctor public Event(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public androidx.health.services.client.data.DataTypeCondition component1();
+ method public androidx.health.services.client.data.event.Event.TriggerType component2();
+ method public androidx.health.services.client.data.event.Event copy(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+ method public int describeContents();
+ method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+ method public androidx.health.services.client.data.event.Event.TriggerType getTriggerType();
+ method public boolean isTriggered(androidx.health.services.client.data.DataPoint dataPoint);
+ method public void writeToParcel(android.os.Parcel dest, int flags);
+ property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+ property public final androidx.health.services.client.data.event.Event.TriggerType triggerType;
+ field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.event.Event> CREATOR;
+ field public static final androidx.health.services.client.data.event.Event.Companion Companion;
+ }
+
+ public static final class Event.Companion {
+ }
+
+ public enum Event.TriggerType {
+ method public static final androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ method public final int getId();
+ property public final int id;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType ONCE;
+ enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType REPEATED;
+ field public static final androidx.health.services.client.data.event.Event.TriggerType.Companion Companion;
+ }
+
+ public static final class Event.TriggerType.Companion {
+ method public androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+ }
+
+}
+
diff --git a/health/health-services-client/build.gradle b/health/health-services-client/build.gradle
index cd0be24..248825c 100644
--- a/health/health-services-client/build.gradle
+++ b/health/health-services-client/build.gradle
@@ -26,7 +26,19 @@
dependencies {
api(KOTLIN_STDLIB)
- // Add dependencies here
+ api("androidx.annotation:annotation:1.1.0")
+ implementation(GUAVA_LISTENABLE_FUTURE)
+ implementation(GUAVA_ANDROID)
+ implementation("androidx.core:core-ktx:1.5.0-alpha04")
+}
+
+android {
+ defaultConfig {
+ minSdkVersion 26
+ }
+ buildFeatures {
+ aidl = true
+ }
}
androidx {
diff --git a/health/health-services-client/lint-baseline.xml b/health/health-services-client/lint-baseline.xml
new file mode 100644
index 0000000..11eec21
--- /dev/null
+++ b/health/health-services-client/lint-baseline.xml
@@ -0,0 +1,1291 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
+
+ <issue
+ id="BanKeepAnnotation"
+ message="Uses @Keep annotation"
+ errorLine1="@Keep"
+ errorLine2="~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/DataPoints.kt"
+ line="27"
+ column="1"/>
+ </issue>
+
+ <issue
+ id="BanKeepAnnotation"
+ message="Uses @Keep annotation"
+ errorLine1=" @Keep"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/DataPoints.kt"
+ line="56"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class AchievedExerciseGoal("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt"
+ line="23"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class AutoExerciseConfig"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt"
+ line="26"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class AutoPauseAndResumeConfigRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt"
+ line="27"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class AvailabilityResponse("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt"
+ line="29"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class BackgroundRegistrationRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class BackgroundUnregistrationRequest(val packageName: String) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt"
+ line="27"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class Capabilities("
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Capabilities.kt"
+ line="24"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class CapabilitiesRequest(val packageName: String) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt"
+ line="27"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class CapabilitiesResponse("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class DataPoint"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/DataPoint.kt"
+ line="31"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class DataPointsResponse(val dataPoints: List<DataPoint>) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class DataType("
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/DataType.kt"
+ line="32"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class DataTypeCondition("
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/DataTypeCondition.kt"
+ line="23"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class Event("
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/event/Event.kt"
+ line="25"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class EventRequest(val packageName: String, val event: Event) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/EventRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseCapabilities("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+ line="23"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseConfig"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseConfig.kt"
+ line="26"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseGoal"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseGoal.kt"
+ line="26"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseGoalRequest(val packageName: String, val exerciseGoal: ExerciseGoal) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseInfo("
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseInfo.kt"
+ line="23"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseInfoResponse(val exerciseInfo: ExerciseInfo) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseLapSummary("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt"
+ line="25"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseLapSummaryResponse(val exerciseLapSummary: ExerciseLapSummary) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseUpdate("
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt"
+ line="26"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class ExerciseUpdateResponse(val exerciseUpdate: ExerciseUpdate) : Parcelable {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class MeasureCapabilities("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt"
+ line="25"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class MeasureCapabilitiesResponse("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class MeasureRegistrationRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class MeasureUnregistrationRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class MilestoneMarkerSummary("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt"
+ line="27"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class PassiveActivityState("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/PassiveActivityState.kt"
+ line="34"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class PassiveActivityStateResponse(val passiveActivityState: PassiveActivityState) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class PassiveMonitoringCapabilities("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt"
+ line="26"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class PassiveMonitoringCapabilitiesResponse("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class StartExerciseRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt"
+ line="28"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanParcelableUsage"
+ message="Class implements android.os.Parcelable"
+ errorLine1="public data class Value"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Value.kt"
+ line="24"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="BanSynchronizedMethods"
+ message="Use of synchronized methods is not recommended"
+ errorLine1=" @Synchronized"
+ errorLine2=" ^">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt"
+ line="55"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="BanSynchronizedMethods"
+ message="Use of synchronized methods is not recommended"
+ errorLine1=" @Synchronized"
+ errorLine2=" ^">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt"
+ line="63"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="BanSynchronizedMethods"
+ message="Use of synchronized methods is not recommended"
+ errorLine1=" @Synchronized"
+ errorLine2=" ^">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt"
+ line="63"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="BanSynchronizedMethods"
+ message="Use of synchronized methods is not recommended"
+ errorLine1=" @Synchronized"
+ errorLine2=" ^">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt"
+ line="86"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="BanSynchronizedMethods"
+ message="Use of synchronized methods is not recommended"
+ errorLine1=" private synchronized void handleRetriableDisconnection(Throwable throwable) {"
+ errorLine2=" ^">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="155"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `getExerciseToExerciseCapabilityMap` of class `Companion` requires synthetic accessor"
+ errorLine1=" getExerciseToExerciseCapabilityMap(parcel)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Capabilities.kt"
+ line="79"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `createQueueOperation` of class `Client` requires synthetic accessor"
+ errorLine1=" createQueueOperation("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="133"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `writeSupportedDataTypes` of class `Companion` requires synthetic accessor"
+ errorLine1=" writeSupportedDataTypes(supportedGoals, dest, flags)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+ line="36"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `writeSupportedDataTypes` of class `Companion` requires synthetic accessor"
+ errorLine1=" writeSupportedDataTypes(supportedMilestones, dest, flags)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+ line="37"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `readSupportedDataTypes` of class `Companion` requires synthetic accessor"
+ errorLine1=" val supportedGoals = readSupportedDataTypes(source) ?: return null"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+ line="51"
+ column="42"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `readSupportedDataTypes` of class `Companion` requires synthetic accessor"
+ errorLine1=" val supportedMilestones = readSupportedDataTypes(source) ?: return null"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+ line="52"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` method `initialize` of class `Companion` requires synthetic accessor"
+ errorLine1=" private val IDS = initialize()"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/ExerciseType.kt"
+ line="114"
+ column="27"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` constructor of class `ExerciseUpdateListenerStub` requires synthetic accessor"
+ errorLine1=" return listeners.getOrPut(listener) { ExerciseUpdateListenerStub(listener, executor) }"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt"
+ line="60"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` constructor of class `MeasureCallbackStub` requires synthetic accessor"
+ errorLine1=" measureCallbackStub = MeasureCallbackStub(callbackKey, measureCallback)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt"
+ line="79"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` constructor of class `Value` requires synthetic accessor"
+ errorLine1=" return Value(format, listOf(), parcel.readLong())"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Value.kt"
+ line="142"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` constructor of class `Value` requires synthetic accessor"
+ errorLine1=" return Value(format, listOf(parcel.readDouble()), /* longValue= */ 0)"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Value.kt"
+ line="144"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="SyntheticAccessor"
+ message="Access to `private` constructor of class `Value` requires synthetic accessor"
+ errorLine1=" return Value(format, doubleArray.toList(), /* longValue= */ 0)"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/data/Value.kt"
+ line="148"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="LambdaLast"
+ message="Functional interface parameters (such as parameter 1, "operation", in androidx.health.services.client.impl.ipc.Client.executeWithVersionCheck) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions"
+ errorLine1=" ServiceOperation<R> operation, int minApiVersion) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="115"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public BaseQueueOperation(ConnectionConfiguration connectionConfiguration) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="37"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void execute(IBinder binder) throws RemoteException {}"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="42"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void setException(Throwable exception) {}"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="45"
+ column="30"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public QueueOperation trackExecution(ExecutionTracker tracker) {"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="48"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public QueueOperation trackExecution(ExecutionTracker tracker) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="48"
+ column="42"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ConnectionConfiguration getConnectionConfiguration() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+ line="54"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" Integer readVersion(IBinder binder) throws RemoteException;"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="53"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" Integer readVersion(IBinder binder) throws RemoteException;"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="53"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ClientConfiguration clientConfiguration,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="65"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ConnectionManager connectionManager,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="66"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" VersionGetter versionGetter) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="67"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected <R> ListenableFuture<R> execute(ServiceOperation<R> operation) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="107"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected <R> ListenableFuture<R> execute(ServiceOperation<R> operation) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="107"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected <R> ListenableFuture<R> executeWithVersionCheck("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="114"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ServiceOperation<R> operation, int minApiVersion) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="115"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected <R> ListenableFuture<R> registerListener("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="173"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ListenerKey listenerKey, ServiceOperation<R> registerListenerOperation) {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="174"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ListenerKey listenerKey, ServiceOperation<R> registerListenerOperation) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="174"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected <R> ListenableFuture<R> unregisterListener("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="193"
+ column="19"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ListenerKey listenerKey, ServiceOperation<R> unregisterListenerOperation) {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="194"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ListenerKey listenerKey, ServiceOperation<R> unregisterListenerOperation) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="194"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" protected Exception getApiVersionCheckFailureException(int currentVersion, int minApiVersion) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+ line="203"
+ column="15"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="34"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="34"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="34"
+ column="81"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public String getServicePackageName() {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="41"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public String getBindAction() {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="46"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public String getApiClientName() {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+ line="51"
+ column="12"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" String packageName,"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+ line="38"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" String clientName,"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+ line="39"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" String bindAction,"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+ line="40"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" QueueOperation refreshVersionOperation) {"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+ line="41"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ConnectionManager(Context context, Looper looper) {"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="52"
+ column="30"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ConnectionManager(Context context, Looper looper) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="52"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void scheduleForExecution(QueueOperation operation) {"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="62"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void registerListener(ListenerKey listenerKey, QueueOperation registerOperation) {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="73"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void registerListener(ListenerKey listenerKey, QueueOperation registerOperation) {"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="73"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterOperation) {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="86"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterOperation) {"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="86"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onConnected(ServiceConnection connection) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="94"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onDisconnected(ServiceConnection connection, long reconnectDelayMs) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="99"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public boolean handleMessage(Message msg) {"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+ line="110"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void track(SettableFuture<?> future) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java"
+ line="39"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void cancelPendingFutures(Throwable throwable) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java"
+ line="45"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void track(SettableFuture<?> future);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java"
+ line="33"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void cancelPendingFutures(Throwable throwable);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java"
+ line="36"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public ListenerKey(Object listenerKey) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java"
+ line="32"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void execute(IBinder binder) throws RemoteException;"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+ line="38"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void setException(Throwable exception);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+ line="41"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" QueueOperation trackExecution(ExecutionTracker tracker);"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+ line="48"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" QueueOperation trackExecution(ExecutionTracker tracker);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+ line="48"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" ConnectionConfiguration getConnectionConfiguration();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+ line="51"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void onConnected(ServiceConnection connection);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="61"
+ column="26"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void onDisconnected(ServiceConnection connection, long reconnectDelayMs);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="69"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onServiceConnected(ComponentName componentName, IBinder binder) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="287"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onServiceConnected(ComponentName componentName, IBinder binder) {"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="287"
+ column="65"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onServiceDisconnected(ComponentName componentName) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="320"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onBindingDied(ComponentName name) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="326"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" public void onNullBinding(ComponentName name) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+ line="332"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void execute(IBinder binder, SettableFuture<R> resultFuture) throws RemoteException;"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java"
+ line="44"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="UnknownNullness"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ errorLine1=" void execute(IBinder binder, SettableFuture<R> resultFuture) throws RemoteException;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java"
+ line="44"
+ column="34"/>
+ </issue>
+
+</issues>
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl
new file mode 100644
index 0000000..6a08a9a
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.IExerciseUpdateListener;
+import androidx.health.services.client.impl.internal.IExerciseInfoCallback;
+import androidx.health.services.client.impl.internal.IStatusCallback;
+import androidx.health.services.client.impl.request.AutoPauseAndResumeConfigRequest;
+import androidx.health.services.client.impl.request.CapabilitiesRequest;
+import androidx.health.services.client.impl.request.ExerciseGoalRequest;
+import androidx.health.services.client.impl.request.StartExerciseRequest;
+import androidx.health.services.client.impl.response.CapabilitiesResponse;
+
+/**
+ * Interface to make ipc calls for health services exercise api.
+ *
+ * The next method added to the interface should use ID: 13
+ * (this id needs to be incremented for each added method)
+ *
+ * @hide
+ */
+interface IExerciseApiService {
+ /**
+ * API version of the AIDL interface. Should be incremented every time a new
+ * method is added.
+ *
+ */
+ const int API_VERSION = 1;
+
+ /**
+ * Handles a given request to start an exercise.
+ */
+ void startExercise(in StartExerciseRequest startExerciseRequest, IStatusCallback statusCallback) = 5;
+
+ /**
+ * Method to pause the active exercise for the calling app.
+ */
+ void pauseExercise(in String packageName, IStatusCallback statusCallback) = 0;
+
+ /**
+ * Method to resume the active exercise for the calling app.
+ */
+ void resumeExercise(in String packageName, IStatusCallback statusCallback) = 1;
+
+ /**
+ * Method to end the active exercise for the calling app.
+ */
+ void endExercise(in String packageName, IStatusCallback statusCallback) = 2;
+
+ /**
+ * Method to end the current lap in the active exercise for the calling app.
+ */
+ void markLap(in String packageName, IStatusCallback statusCallback) = 9;
+
+ /**
+ * Returns version of this AIDL interface.
+ *
+ * <p> Can be used by client to detect version of the API on the service
+ * side. Returned version should be always > 0.
+ */
+ int getApiVersion() = 3;
+
+ /**
+ * Returns the current exercise info.
+ */
+ void getCurrentExerciseInfo(in String packageName, IExerciseInfoCallback exerciseInfoCallback) = 12;
+
+ /**
+ * Sets the listener for the current exercise state.
+ */
+ void setUpdateListener(in String packageName, in IExerciseUpdateListener listener, IStatusCallback statusCallback) = 6;
+
+ /**
+ * Clears the listener set using {@link #setUpdateListener}.
+ */
+ void clearUpdateListener(in String packageName, in IExerciseUpdateListener listener, IStatusCallback statusCallback) = 7;
+
+ /**
+ * Adds an exercise goal for an active exercise.
+ *
+ * <p>An exercise goal is a one-time goal, such as achieving a target total step count.
+ *
+ * <p>Goals apply to only active exercises owned by the client, and will be invalidated once the
+ * exercise is complete. A goal can be added only after an exercise has been started.
+ */
+ void addGoalToActiveExercise(in ExerciseGoalRequest request, IStatusCallback statusCallback) = 8;
+
+ /**
+ * Sets whether auto-pause should be enabled
+ */
+ void overrideAutoPauseAndResumeForActiveExercise(in AutoPauseAndResumeConfigRequest request, IStatusCallback statusCallback) = 10;
+
+ /**
+ * Method to get capabilities.
+ */
+ CapabilitiesResponse getCapabilities(in CapabilitiesRequest request) = 11;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseUpdateListener.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseUpdateListener.aidl
new file mode 100644
index 0000000..df6420bd
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseUpdateListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.response.ExerciseLapSummaryResponse;
+import androidx.health.services.client.impl.response.ExerciseUpdateResponse;
+
+/**
+ * Interface to get exercise updates.
+ *
+ * @hide
+ */
+oneway interface IExerciseUpdateListener {
+ /** Called when there is an update of exercise state or metrics. */
+ void onExerciseUpdate(in ExerciseUpdateResponse update) = 0;
+
+ /** Called when a lap has been marked. */
+ void onLapSummary(in ExerciseLapSummaryResponse summaryResponse) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IHealthServicesApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IHealthServicesApiService.aidl
new file mode 100644
index 0000000..ddccc54
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IHealthServicesApiService.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl;
+
+/**
+ * Interface to make ipc calls for health services api.
+ *
+ * @hide
+ */
+interface IHealthServicesApiService {
+ /**
+ * API version of the AIDL interface. Should be incremented every time a new
+ * method is added.
+ */
+ const int API_VERSION = 1;
+
+ /**
+ * Returns version of this AIDL interface.
+ *
+ * <p> Can be used by client to detect version of the API on the service
+ * side. Returned version should be always > 0.
+ */
+ int getApiVersion() = 1;
+}
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureApiService.aidl
new file mode 100644
index 0000000..2710467
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureApiService.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.IMeasureCallback;
+import androidx.health.services.client.impl.internal.IStatusCallback;
+import androidx.health.services.client.impl.request.CapabilitiesRequest;
+import androidx.health.services.client.impl.request.MeasureRegistrationRequest;
+import androidx.health.services.client.impl.request.MeasureUnregistrationRequest;
+import androidx.health.services.client.impl.response.MeasureCapabilitiesResponse;
+
+/**
+ * Interface to make ipc calls for health services api.
+ *
+ * @hide
+ */
+interface IMeasureApiService {
+ /**
+ * API version of the AIDL interface. Should be incremented every time a new
+ * method is added.
+ */
+ const int API_VERSION = 1;
+
+ /**
+ * Method to register measure listener.
+ */
+ void registerCallback(in MeasureRegistrationRequest request, in IMeasureCallback callback, in IStatusCallback statusCallback) = 0;
+
+ /**
+ * Method to unregister measure listener.
+ */
+ void unregisterCallback(in MeasureUnregistrationRequest request, in IMeasureCallback callback, in IStatusCallback statusCallback) = 1;
+
+ /**
+ * Returns version of this AIDL interface.
+ *
+ * <p> Can be used by client to detect version of the API on the service
+ * side. Returned version should be always > 0.
+ */
+ int getApiVersion() = 2;
+
+ /** Method to get capabilities. */
+ MeasureCapabilitiesResponse getCapabilities(in CapabilitiesRequest request) = 3;
+}
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureCallback.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureCallback.aidl
new file mode 100644
index 0000000..ff7f983
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.response.AvailabilityResponse;
+import androidx.health.services.client.impl.response.DataPointsResponse;
+
+/**
+ * Interface to get callback for measure api.
+ *
+ * @hide
+ */
+oneway interface IMeasureCallback {
+ void onAvailabilityChanged(in AvailabilityResponse response) = 0;
+
+ void onData(in DataPointsResponse response) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringApiService.aidl
new file mode 100644
index 0000000..d6be3f0
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringApiService.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl;
+
+import android.app.PendingIntent;
+import androidx.health.services.client.impl.IPassiveMonitoringCallback;
+import androidx.health.services.client.impl.internal.IStatusCallback;
+import androidx.health.services.client.impl.request.BackgroundRegistrationRequest;
+import androidx.health.services.client.impl.request.CapabilitiesRequest;
+import androidx.health.services.client.impl.request.EventRequest;
+import androidx.health.services.client.impl.response.PassiveMonitoringCapabilitiesResponse;
+
+/** @hide */
+interface IPassiveMonitoringApiService {
+ /**
+ * API version of the AIDL interface. Should be incremented every time a new
+ * method is added.
+ */
+ const int API_VERSION = 1;
+
+ /**
+ * Method to subscribe to an event with corresponding callback intent.
+ */
+ void registerEventCallback(in EventRequest request, in PendingIntent intent, in IStatusCallback statusCallback) = 0;
+
+ /**
+ * Method to subscribe to a set of data types with corresponding callback
+ * intent and an optional callback.
+ *
+ * <p>If a callback is present and is active, updates are provided via the callback. Otherwise,
+ * the provided PendingIntent gets the updates.
+ */
+ void registerDataCallback(in BackgroundRegistrationRequest request, in PendingIntent fallbackIntent, in IPassiveMonitoringCallback callback, in IStatusCallback statusCallback) = 1;
+
+ /**
+ * Method to subscribe to a set of data types with corresponding callback intent.
+ */
+ void unregisterDataCallback(in String packageName, in IStatusCallback statusCallback) = 3;
+
+ /**
+ * Method to subscribe to a set of data types with corresponding callback intent.
+ */
+ void unregisterEventCallback(in EventRequest request, in IStatusCallback statusCallback) = 4;
+
+ /**
+ * Returns version of this AIDL interface.
+ *
+ * <p> Can be used by client to detect version of the API on the service
+ * side. Returned version should be always > 0.
+ */
+ int getApiVersion() = 2;
+
+ /** Method to get capabilities. */
+ PassiveMonitoringCapabilitiesResponse getCapabilities(in CapabilitiesRequest request) = 5;
+}
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringCallback.aidl
similarity index 61%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringCallback.aidl
index 2aa2c9c..2eafb48 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringCallback.aidl
@@ -14,8 +14,16 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl;
+import androidx.health.services.client.impl.response.PassiveActivityStateResponse;
+
+/**
+ * Interface to get passive monitoring updates.
+ *
+ * @hide
+ */
+oneway interface IPassiveMonitoringCallback {
+ /** Called when a new passive activity state response is available. */
+ void onPassiveActivityState(in PassiveActivityStateResponse response) = 0;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IExerciseInfoCallback.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IExerciseInfoCallback.aidl
new file mode 100644
index 0000000..cfc56ca
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IExerciseInfoCallback.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.internal;
+
+import androidx.health.services.client.impl.response.ExerciseInfoResponse;
+
+/**
+ * Callback for an operation that returns an ExerciseInfo on successful
+ * completion.
+ *
+ * @hide
+ */
+oneway interface IExerciseInfoCallback {
+ /**
+ * Method invoked when the operation is a success and exercise info is
+ * successfully obtained.
+ */
+ void onExerciseInfo(in ExerciseInfoResponse response) = 0;
+
+ /**
+ * Method invoked when the operation is a failure.
+ */
+ void onFailure(String message) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IStatusCallback.aidl
similarity index 61%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IStatusCallback.aidl
index 2aa2c9c..2a58b96 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IStatusCallback.aidl
@@ -14,8 +14,21 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.internal;
+/**
+ * Generic callback for an operation that returns a status on completion.
+ *
+ * @hide
+ */
+oneway interface IStatusCallback {
+ /**
+ * Method invoked when the operation is a success.
+ */
+ void onSuccess() = 0;
+
+ /**
+ * Method invoked when the operation is a failure.
+ */
+ void onFailure(String msg) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.aidl
similarity index 84%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.aidl
index 2aa2c9c..3f7984c 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.request;
+/** @hide */
+parcelable AutoPauseAndResumeConfigRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.aidl
similarity index 84%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.aidl
index 2aa2c9c..7493c72 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.request;
+/** @hide */
+parcelable BackgroundRegistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.aidl
similarity index 84%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.aidl
index 2aa2c9c..8264c63 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.request;
+/** @hide */
+parcelable BackgroundUnregistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/CapabilitiesRequest.aidl
similarity index 85%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/CapabilitiesRequest.aidl
index 2aa2c9c..b71b20a 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/CapabilitiesRequest.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.request;
+/** @hide */
+parcelable CapabilitiesRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/EventRequest.aidl
similarity index 86%
rename from health/health-services-client/src/main/androidx/health/package-info.java
rename to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/EventRequest.aidl
index 2aa2c9c..1b59985 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/EventRequest.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.request;
+/** @hide */
+parcelable EventRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/ExerciseGoalRequest.aidl
similarity index 85%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/ExerciseGoalRequest.aidl
index 2aa2c9c..d8a9fbb 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/ExerciseGoalRequest.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.request;
+/** @hide */
+parcelable ExerciseGoalRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureRegistrationRequest.aidl
similarity index 84%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureRegistrationRequest.aidl
index 2aa2c9c..96acaec 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureRegistrationRequest.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.request;
+/** @hide */
+parcelable MeasureRegistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.aidl
similarity index 84%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.aidl
index 2aa2c9c..fb2f060 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.request;
+/** @hide */
+parcelable MeasureUnregistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/StartExerciseRequest.aidl
similarity index 85%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/StartExerciseRequest.aidl
index 2aa2c9c..dd5e594 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/StartExerciseRequest.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.request;
+/** @hide */
+parcelable StartExerciseRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseCapabilitiesResponse.aidl
similarity index 84%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseCapabilitiesResponse.aidl
index 2aa2c9c..2641359 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseCapabilitiesResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable AutoExerciseCapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseDetectionStateResponse.aidl
similarity index 83%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseDetectionStateResponse.aidl
index 2aa2c9c..4b38fe1 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseDetectionStateResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable AutoExerciseDetectionStateResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AvailabilityResponse.aidl
similarity index 85%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AvailabilityResponse.aidl
index 2aa2c9c..902c1ff 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AvailabilityResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable AvailabilityResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/CapabilitiesResponse.aidl
similarity index 85%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/CapabilitiesResponse.aidl
index 2aa2c9c..c48b0d0 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/CapabilitiesResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable CapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/DataPointsResponse.aidl
similarity index 85%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/DataPointsResponse.aidl
index 2aa2c9c..31ab81c 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/DataPointsResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable DataPointsResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseInfoResponse.aidl
similarity index 85%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseInfoResponse.aidl
index 2aa2c9c..8d429af 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseInfoResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable ExerciseInfoResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.aidl
similarity index 84%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.aidl
index 2aa2c9c..55dd959 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable ExerciseLapSummaryResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseUpdateResponse.aidl
similarity index 85%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseUpdateResponse.aidl
index 2aa2c9c..d10ab6c 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseUpdateResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable ExerciseUpdateResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/HeartRateAlertParamsResponse.aidl
similarity index 84%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/HeartRateAlertParamsResponse.aidl
index 2aa2c9c..6464078 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/HeartRateAlertParamsResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable HeartRateAlertParamsResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.aidl
similarity index 84%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.aidl
index 2aa2c9c..d9755f1 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable MeasureCapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveActivityStateResponse.aidl
similarity index 84%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveActivityStateResponse.aidl
index 2aa2c9c..5e3b130 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveActivityStateResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable PassiveActivityStateResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.aidl
similarity index 83%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.aidl
index 2aa2c9c..8a5ab5f 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.aidl
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.impl.response;
+/** @hide */
+parcelable PassiveMonitoringCapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
new file mode 100644
index 0000000..42a190e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 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.health.services.client
+
+import androidx.health.services.client.data.Capabilities
+import androidx.health.services.client.data.ExerciseConfig
+import androidx.health.services.client.data.ExerciseGoal
+import androidx.health.services.client.data.ExerciseInfo
+import androidx.health.services.client.data.ExerciseUpdate
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/** Client which provides a way to subscribe to the health data of a device during an exercise. */
+public interface ExerciseClient {
+ /**
+ * Starts a new exercise.
+ *
+ * Once started, WHS will begin collecting data associated with the exercise.
+ *
+ * Since WHS only allows a single active exercise at a time, this will terminate any active
+ * exercise currently in progress before starting the new one.
+ *
+ * @return a [ListenableFuture] that completes once the exercise has been started.
+ */
+ public fun startExercise(configuration: ExerciseConfig): ListenableFuture<Void>
+
+ /**
+ * Pauses the current exercise, if it is currently started.
+ *
+ * While the exercise is paused active time and cumulative metrics such as distance will not
+ * accumulate. Instantaneous measurements such as speed and heart rate will continue to update
+ * if requested in the [ExerciseConfig].
+ *
+ * If the exercise remains paused for a long period of time WHS will reduce or suspend access to
+ * sensors and GPS in order to conserve battery. Should this happen, access will automatically
+ * resume when the exercise is resumed.
+ *
+ * If the exercise is already paused then this method has no effect. If the exercise has ended
+ * then the returned future will fail.
+ *
+ * @return a [ListenableFuture] that completes once the exercise has been paused.
+ */
+ public fun pauseExercise(): ListenableFuture<Void>
+
+ /**
+ * Resumes the current exercise, if it is currently paused.
+ *
+ * Once resumed active time and cumulative metrics such as distance will resume accumulating.
+ *
+ * If the exercise has been started but is not currently paused this method has no effect. If
+ * the exercise has ended then the returned future will fail.
+ *
+ * @return a [ListenableFuture] that completes once the exercise has been resumed.
+ */
+ public fun resumeExercise(): ListenableFuture<Void>
+
+ /**
+ * Ends the current exercise, if it has been started. If the exercise has ended then this future
+ * will fail.
+ *
+ * No additional metrics will be produced for the exercise and any on device persisted data
+ * about the exercise will be deleted after the summary has been sent back.
+ */
+ public fun endExercise(): ListenableFuture<Void>
+
+ /**
+ * Ends the current lap, calls [ExerciseStateListener.onLapSummary] with data spanning the
+ * marked lap and starts a new lap. If the exercise supports laps this method can be called at
+ * any point after an exercise has been started and before it has been ended regardless of the
+ * exercise status.
+ *
+ * The metrics in the lap summary will start from either the start time of the exercise or the
+ * last time a lap was marked to the time this method is being called.
+ *
+ * If there's no exercise being tracked or if the exercise does not support laps then this
+ * future will fail.
+ */
+ public fun markLap(): ListenableFuture<Void>
+
+ /** Returns the [ExerciseInfo]. */
+ public val currentExerciseInfo: ListenableFuture<ExerciseInfo>
+
+ /**
+ * Sets the listener for the current [ExerciseUpdate].
+ *
+ * This listener won't be called until an exercise is in progress. It will also only receive
+ * updates from exercises tracked in this app.
+ *
+ * If an exercise is in progress, the [ExerciseUpdateListener] is immediately called with the
+ * associated [ExerciseUpdate], and subsequently whenever the state is updated or an event is
+ * triggered.
+ *
+ * Calls to the listener will be executed on the main application thread. To control where to
+ * execute the listener, see the overload taking an [Executor]. To remove the listener use
+ * [clearUpdateListener].
+ */
+ public fun setUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void>
+
+ /**
+ * Calls to the listener will be executed using the specified [Executor]. To execute the
+ * listener on the main application thread use the overload without the [Executor].
+ */
+ public fun setUpdateListener(
+ listener: ExerciseUpdateListener,
+ executor: Executor
+ ): ListenableFuture<Void>
+
+ /**
+ * Clears the listener set using [setUpdateListener].
+ *
+ * If the listener wasn't set, the returned [ListenableFuture] will fail.
+ */
+ public fun clearUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void>
+
+ /**
+ * Adds an [ExerciseGoal] for an active exercise.
+ *
+ * An [ExerciseGoal] is a one-time goal, such as achieving a target total step count.
+ *
+ * Goals apply to only active exercises owned by the client, and will be invalidated once the
+ * exercise is complete.
+ *
+ * @return a [ListenableFuture] that completes once the exercise goal has been added. This
+ * returned [ListenableFuture] fails if the exercise is not active.
+ */
+ public fun addGoalToActiveExercise(exerciseGoal: ExerciseGoal): ListenableFuture<Void>
+
+ /**
+ * Enables or disables the auto pause/resume for the current exercise.
+ *
+ * @param enabled a boolean to indicate if should be enabled or disabled
+ */
+ public fun overrideAutoPauseAndResumeForActiveExercise(enabled: Boolean): ListenableFuture<Void>
+
+ /** Returns the [Capabilities] of this client for the device. */
+ public val capabilities: ListenableFuture<Capabilities>
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateListener.kt b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateListener.kt
new file mode 100644
index 0000000..d622d43
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateListener.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 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.health.services.client
+
+import androidx.health.services.client.data.ExerciseLapSummary
+import androidx.health.services.client.data.ExerciseState
+import androidx.health.services.client.data.ExerciseUpdate
+
+/** Listener that is called when the state of the current exercise is updated. */
+// TODO(b/179756577): Add onExerciseEnd(ExerciseSummary) method.
+public interface ExerciseUpdateListener {
+ /** Called during an ACTIVE exercise or on any changes in [ExerciseState]. */
+ public fun onExerciseUpdate(update: ExerciseUpdate)
+
+ /** Called during an ACTIVE exercise once a lap has been marked. */
+ public fun onLapSummary(lapSummary: ExerciseLapSummary)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/HealthServices.kt b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServices.kt
new file mode 100644
index 0000000..8fba5b9
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServices.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 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.health.services.client
+
+import android.content.Context
+import androidx.health.services.client.impl.ServiceBackedHealthServicesClient
+
+/** Entry point for all Health Services APIs. */
+public object HealthServices {
+ /** Returns an instance of [HealthServicesClient]. */
+ @JvmStatic
+ public fun getClient(context: Context): HealthServicesClient {
+ return ServiceBackedHealthServicesClient(context)
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/HealthServicesClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServicesClient.kt
new file mode 100644
index 0000000..82c3d41
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServicesClient.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.health.services.client
+
+/**
+ * Client which provides a way to subscribe to the health data of a device, in the background or in
+ * the foreground.
+ */
+public interface HealthServicesClient {
+ /** Returns a [ExerciseClient]. */
+ public val exerciseClient: ExerciseClient
+
+ /** Returns a [PassiveMonitoringClient]. */
+ public val passiveMonitoringClient: PassiveMonitoringClient
+
+ /** Returns a [MeasureClient]. */
+ public val measureClient: MeasureClient
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/MeasureCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureCallback.kt
new file mode 100644
index 0000000..7706b42
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureCallback.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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.health.services.client
+
+import androidx.health.services.client.data.Availability
+import androidx.health.services.client.data.DataPoint
+import androidx.health.services.client.data.DataType
+
+/** Callback for [MeasureClient.registerCallback]. */
+public interface MeasureCallback {
+ /** Called when the availability of a [DataType] changes. */
+ public fun onAvailabilityChanged(dataType: DataType, availability: Availability)
+
+ /** Called when new data is available. Data can be batched in a list of [DataPoint]. */
+ public fun onData(data: List<@JvmSuppressWildcards DataPoint>)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/MeasureClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureClient.kt
new file mode 100644
index 0000000..782ae0d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureClient.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.health.services.client
+
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.MeasureCapabilities
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/**
+ * Client which provides a way to make measurements of health data on a device.
+ *
+ * This is optimized for apps to register live callbacks on data which may be sampled at a faster
+ * rate; this is not meant to be used for long-lived subscriptions to data (for this, consider using
+ * [ExerciseClient] or [PassiveMonitoringClient] depending on your use case).
+ *
+ * Existing subscriptions made with the [PassiveMonitoringClient] are also expected to get the data
+ * generated by this client.
+ */
+public interface MeasureClient {
+ /**
+ * Registers the app for live measurement of the specified [DataType].
+ *
+ * The callback will be called on the main application thread. To move calls to an alternative
+ * thread use [registerCallback].
+ *
+ * Even if data is registered for live capture, it can still be sent out in batches depending on
+ * the application processor state.
+ *
+ * Registering a [DataType] for live measurement capture is expected to increase the sample rate
+ * on the associated sensor(s); this is typically used for one-off measurements. Do not use this
+ * method for background capture or workout tracking.
+ *
+ * The callback will continue to be called until the app is killed or [unregisterCallback] is
+ * called.
+ *
+ * If the same [callback] is already registered for the given [DataType], this operation is a
+ * no-op.
+ */
+ public fun registerCallback(
+ dataType: DataType,
+ callback: MeasureCallback
+ ): ListenableFuture<Void>
+
+ /** Same as [registerCallback], except the [callback] is called on the given [Executor]. */
+ public fun registerCallback(
+ dataType: DataType,
+ callback: MeasureCallback,
+ executor: Executor
+ ): ListenableFuture<Void>
+
+ /** Unregisters the given [MeasureCallback] for updates of the given [DataType]. */
+ public fun unregisterCallback(
+ dataType: DataType,
+ callback: MeasureCallback
+ ): ListenableFuture<Void>
+
+ /** Returns the [MeasureCapabilities] of this client for the device. */
+ public val capabilities: ListenableFuture<MeasureCapabilities>
+}
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringCallback.kt
similarity index 65%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringCallback.kt
index 2aa2c9c..2463188 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringCallback.kt
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client
+import androidx.health.services.client.data.PassiveActivityState
+
+/** A callback for receiving passive monitoring updates. */
+public interface PassiveMonitoringCallback {
+ /** Called when new state is available. */
+ public fun onPassiveActivityState(state: PassiveActivityState)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringClient.kt
new file mode 100644
index 0000000..23793f7
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringClient.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 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.health.services.client
+
+import android.app.PendingIntent
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.PassiveMonitoringCapabilities
+import androidx.health.services.client.data.event.Event
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Client which provides a means to passively monitor data without requiring an ongoing workout.
+ *
+ * The lifetimes of registrations made through this client are independent of the lifetime of the
+ * subscribing app. These registrations are therefore suitable for notifying of ongoing measurements
+ * or triggered events, regardless of whether or not the subscribing app is currently running, in
+ * the foreground or engaged in a workout.
+ */
+public interface PassiveMonitoringClient {
+ /**
+ * Subscribes for updates on a set of data types to be periodically delivered to the app.
+ *
+ * Data will be batched. Higher frequency updates are available through [ExerciseClient] or
+ * [MeasureClient].
+ *
+ * The provided [PendingIntent] will be invoked periodically with the collected data.
+ *
+ * Subscribing apps are responsible for ensuring they can receive the [callbackIntent] by e.g.
+ * declaring a suitable [android.content.BroadcastReceiver] in their app manifest.
+ *
+ * This registration is unique per subscribing app. Subsequent registrations will replace the
+ * previous registration, if one had been made.
+ */
+ public fun registerDataCallback(
+ dataTypes: Set<@JvmSuppressWildcards DataType>,
+ callbackIntent: PendingIntent
+ ): ListenableFuture<Void>
+
+ /**
+ * Subscribes an intent callback (the same way as [PassiveMonitoringClient.registerDataCallback]
+ * ) and a [PassiveMonitoringCallback] for updates on a set of data types periodically.
+ *
+ * The provided [callback] will take priority in receiving updates as long the app is alive and
+ * the callback can be successfully notified. Otherwise, updates will be delivered to the
+ * [callbackIntent].
+ *
+ * This registration is unique per subscribing app. Subsequent registrations will replace the
+ * previous registration, if one had been made.
+ */
+ public fun registerDataCallback(
+ dataTypes: Set<@JvmSuppressWildcards DataType>,
+ callbackIntent: PendingIntent,
+ callback: PassiveMonitoringCallback
+ ): ListenableFuture<Void>
+
+ /**
+ * Unregisters the subscription made by [PassiveMonitoringClient.registerDataCallback].
+ *
+ * The associated [PendingIntent] will be called one last time with any remaining buffered data.
+ */
+ public fun unregisterDataCallback(): ListenableFuture<Void>
+
+ /**
+ * Registers for notification of the [event] being triggered.
+ *
+ * The provided [PendingIntent] will be sent whenever [event] is triggered.
+ *
+ * Subscribing apps are responsible for ensuring they can receive the [callbackIntent] by e.g.
+ * declaring a suitable [android.content.BroadcastReceiver] in their app manifest.
+ *
+ * Registration of multiple events is possible except where there already exists an event that
+ * is equal, as per the definition of [Event.equals], in which case the existing registration
+ * for that event will be replaced.
+ */
+ public fun registerEventCallback(
+ event: Event,
+ callbackIntent: PendingIntent
+ ): ListenableFuture<Void>
+
+ /** Unregisters the subscription for the given [Event]. */
+ public fun unregisterEventCallback(event: Event): ListenableFuture<Void>
+
+ /** Returns the [PassiveMonitoringCapabilities] of this client for the device. */
+ public val capabilities: ListenableFuture<PassiveMonitoringCapabilities>
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt
new file mode 100644
index 0000000..6463d58
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** Defines an achieved [ExerciseGoal]. */
+public data class AchievedExerciseGoal(
+ /** [ExerciseGoal] that has been achieved. */
+ // TODO(b/181235444): do we need to deliver the DataPoint to the user again here, given
+ // that they will have already gotten it in the ExerciseState? And, what other data do we need
+ // to
+ // tag along an achieved ExerciseGoal?
+ val goal: ExerciseGoal,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(goal, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<AchievedExerciseGoal> =
+ object : Parcelable.Creator<AchievedExerciseGoal> {
+ override fun createFromParcel(source: Parcel): AchievedExerciseGoal? {
+ val goal =
+ source.readParcelable<ExerciseGoal>(ExerciseGoal::class.java.classLoader)
+ ?: return null
+ return AchievedExerciseGoal(goal)
+ }
+
+ override fun newArray(size: Int): Array<AchievedExerciseGoal?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt
new file mode 100644
index 0000000..710346f
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.app.PendingIntent
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import java.util.Objects
+
+/** Configs for the automatic exercise detection. */
+public data class AutoExerciseConfig
+@JvmOverloads
+constructor(
+ /**
+ * Set of [ExerciseType] for WHS to detect from. If left empty, all possible types are used].
+ */
+ val exercisesToDetect: Set<ExerciseType> = emptySet(),
+ /**
+ * A [PendingIntent] that WHS will use to post state / data changes to the app if the app has no
+ * active [androidx.health.services.client.ExerciseUpdateListener] registered at the moment. The
+ * launchIntent will be fed with the updated exercise states collected by the automatic exercise
+ * detection for the app to consume as soon as it re-starts.
+ */
+ val launchIntent: PendingIntent? = null,
+
+ /** See [ExerciseConfig.exerciseParams]. */
+ val exerciseParams: Bundle = Bundle(),
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(exercisesToDetect.size)
+ dest.writeIntArray(exercisesToDetect.map { it.id }.toIntArray())
+ dest.writeParcelable(launchIntent, flags)
+ dest.writeBundle(exerciseParams)
+ }
+
+ // TODO(b/180612514): Bundle doesn't have equals, so we need to override the data class default.
+ override fun equals(other: Any?): Boolean {
+ if (other === this) {
+ return true
+ }
+ if (other is AutoExerciseConfig) {
+ return (
+ launchIntent == other.launchIntent &&
+ BundlesUtil.equals(exerciseParams, other.exerciseParams) &&
+ exercisesToDetect == other.exercisesToDetect
+ )
+ }
+ return false
+ }
+
+ // TODO(b/180612514): Bundle doesn't have hashCode, so we need to override the data class
+ // default.
+ override fun hashCode(): Int {
+ return Objects.hash(launchIntent, BundlesUtil.hashCode(exerciseParams), exercisesToDetect)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<AutoExerciseConfig> =
+ object : Parcelable.Creator<AutoExerciseConfig> {
+ override fun createFromParcel(source: Parcel): AutoExerciseConfig? {
+ val exercisesIntArray = IntArray(source.readInt())
+ source.readIntArray(exercisesIntArray)
+ val launchIntent =
+ source.readParcelable<PendingIntent>(PendingIntent::class.java.classLoader)
+ val exerciseParams =
+ source.readBundle(AutoExerciseConfig::class.java.classLoader) ?: return null
+
+ return AutoExerciseConfig(
+ exercisesIntArray.map { ExerciseType.fromId(it) }.toSet(),
+ launchIntent,
+ exerciseParams
+ )
+ }
+
+ override fun newArray(size: Int): Array<AutoExerciseConfig?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt
similarity index 63%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt
index 2aa2c9c..71a56d8 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt
@@ -14,8 +14,16 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.data
+/** Availability of a [DataType]. */
+public enum class Availability(public val id: Int) {
+ UNKNOWN(0),
+ AVAILABLE(1),
+ ACQUIRING(2),
+ UNAVAILABLE(3);
+
+ public companion object {
+ @JvmStatic public fun fromId(id: Int): Availability? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/BundlesUtil.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/BundlesUtil.kt
new file mode 100644
index 0000000..f20e608
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/BundlesUtil.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Bundle
+import java.util.Objects
+
+/** Utility methods for working with Bundles. */
+internal object BundlesUtil {
+
+ /**
+ * Compares two Bundles recursively and returns `true` if they are equal.
+ *
+ * Equality in this case means that both bundles contain the same set of keys and their
+ * corresponding values are all equal (using the [Object.equals] method).
+ */
+ @JvmStatic
+ fun equals(a: Bundle?, b: Bundle?): Boolean {
+ if (a == b) {
+ return true
+ } else if (a == null || b == null) {
+ return false
+ } else if (a.size() != b.size()) {
+ return false
+ }
+ for (key in a.keySet()) {
+ val aValue = a[key]
+ val bValue = b[key]
+ if (aValue is Bundle && bValue is Bundle) {
+ if (!equals(aValue as Bundle?, bValue as Bundle?)) {
+ return false
+ }
+ } else if (aValue == null) {
+ if (bValue != null || !b.containsKey(key)) {
+ return false
+ }
+ } else if (!Objects.deepEquals(aValue, bValue)) {
+ return false
+ }
+ }
+ return true
+ }
+
+ /** Calculates a hashCode for a Bundle, examining all keys and values. */
+ @JvmStatic
+ fun hashCode(b: Bundle?): Int {
+ if (b == null) {
+ return 0
+ }
+ val keySet = b.keySet()
+ val hashCodes = IntArray(keySet.size * 2)
+ var i = 0
+ for (key in keySet) {
+ hashCodes[i++] = Objects.hashCode(key)
+ val value = b[key]
+ val valueHashCode: Int =
+ if (value is Bundle) {
+ hashCode(value as Bundle?)
+ } else {
+ Objects.hashCode(value)
+ }
+ hashCodes[i++] = valueHashCode
+ }
+ return hashCodes.contentHashCode()
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/Capabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/Capabilities.kt
new file mode 100644
index 0000000..3c19682
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/Capabilities.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseType.Companion.fromId
+
+/** A place holder class that represents the capabilities of WHS client on the device. */
+public data class Capabilities(
+ /** Mapping for each supported [ExerciseType] to its [ExerciseCapabilities] on this device. */
+ val exerciseTypeToExerciseCapabilities: Map<ExerciseType, ExerciseCapabilities>,
+) : Parcelable {
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ writeExerciseTypeToExerciseCapabilities(dest, flags)
+ }
+
+ /** Set of supported [ExerciseType] s on this device. */
+ public val supportedExerciseTypes: Set<ExerciseType>
+ get() = exerciseTypeToExerciseCapabilities.keys
+
+ /**
+ * Returns the supported [ExerciseCapabilities] for a requested [ExerciseType].
+ *
+ * @throws IllegalArgumentException if the supplied [exercise] isn't supported
+ */
+ public fun getExerciseCapabilities(exercise: ExerciseType): ExerciseCapabilities {
+ return exerciseTypeToExerciseCapabilities[exercise]
+ ?: throw IllegalArgumentException(
+ String.format("%s exercise type is not supported", exercise)
+ )
+ }
+
+ /** Returns the set of [ExerciseType] s that support auto pause and resume on this device. */
+ public val autoPauseAndResumeEnabledExercises: Set<ExerciseType>
+ get() {
+ return exerciseTypeToExerciseCapabilities
+ .entries
+ .filter { it.value.supportsAutoPauseAndResume }
+ .map { it.key }
+ .toSet()
+ }
+
+ private fun writeExerciseTypeToExerciseCapabilities(dest: Parcel, flags: Int) {
+ dest.writeInt(exerciseTypeToExerciseCapabilities.size)
+ for ((key1, value) in exerciseTypeToExerciseCapabilities) {
+ val key = key1.id
+ dest.writeInt(key)
+ dest.writeParcelable(value, flags)
+ }
+ }
+
+ public companion object {
+
+ @JvmField
+ public val CREATOR: Parcelable.Creator<Capabilities> =
+ object : Parcelable.Creator<Capabilities> {
+ override fun createFromParcel(parcel: Parcel): Capabilities {
+ val exerciseTypeToExerciseCapabilitiesFromParcel =
+ getExerciseToExerciseCapabilityMap(parcel)
+ return Capabilities(
+ exerciseTypeToExerciseCapabilitiesFromParcel,
+ )
+ }
+
+ override fun newArray(size: Int): Array<Capabilities?> = arrayOfNulls(size)
+ }
+
+ private fun readDataTypeSet(parcel: Parcel): Set<DataType> {
+ return parcel.createTypedArray(DataType.CREATOR)!!.toSet()
+ }
+
+ private fun writeDataTypeSet(out: Parcel, flags: Int, dataTypes: Set<DataType>) {
+ out.writeTypedArray(dataTypes.toTypedArray(), flags)
+ }
+
+ private fun getExerciseToExerciseCapabilityMap(
+ parcel: Parcel
+ ): Map<ExerciseType, ExerciseCapabilities> {
+ val map = HashMap<ExerciseType, ExerciseCapabilities>()
+ val mapSize = parcel.readInt()
+ for (i in 0 until mapSize) {
+ val key = fromId(parcel.readInt())
+ val value =
+ parcel.readParcelable<Parcelable>(
+ ExerciseCapabilities::class.java.classLoader
+ ) as
+ ExerciseCapabilities
+ map[key] = value
+ }
+ return map
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt
new file mode 100644
index 0000000..37e2288
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+/** For determining when a threshold has been met or exceeded in a [MetricCondition]. */
+public enum class ComparisonType(public val id: Int) {
+ // TODO(b/175064823): investigate adding EQUAL comparison type
+ GREATER_THAN(1),
+ GREATER_THAN_OR_EQUAL(2),
+ LESS_THAN(3),
+ LESS_THAN_OR_EQUAL(4);
+
+ public companion object {
+ @JvmStatic
+ public fun fromId(id: Int): ComparisonType? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoint.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoint.kt
new file mode 100644
index 0000000..5e52737
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoint.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+import java.util.Objects
+
+/**
+ * A data point containing a [value] of type [dataType] from either a single point in time:
+ * [DataType.TimeType.SAMPLE], or a range in time: [DataType.TimeType.INTERVAL].
+ */
+@Suppress("DataClassPrivateConstructor")
+public data class DataPoint
+internal constructor(
+ val dataType: DataType,
+ val value: Value,
+
+ /**
+ * Elapsed start time of this [DataPoint].
+ *
+ * This represents the time at which this [DataPoint] originated, as a [Duration] since boot
+ * time. This is not exposed as a timestamp as the clock may drift between when the data is
+ * generated and when it is read out. Use [getStartInstant] to get the start time of this
+ * [DataPoint] as an [Instant].
+ */
+ val startDurationFromBoot: Duration,
+
+ /**
+ * Elapsed end time of this [DataPoint].
+ *
+ * This represents the time at which this [DataPoint] ends, as a [Duration] since boot time.
+ * This is not exposed as a timestamp as the clock may drift between when the data is generated
+ * and when it is read out. Use [getStartInstant] to get the start time of this [DataPoint] as
+ * an [Instant].
+ *
+ * For instantaneous data points, this is equal to [startDurationFromBoot].
+ */
+ val endDurationFromBoot: Duration = startDurationFromBoot,
+
+ /** Returns any provided metadata of this [DataPoint]. */
+ val metadata: Bundle = Bundle(),
+) : Parcelable {
+
+ init {
+ require(dataType.format == value.format) {
+ "DataType and Value format must match, but got ${dataType.format} and ${value.format}"
+ }
+ }
+
+ /**
+ * Returns the start [Instant] of this [DataPoint], knowing the time at which the system booted.
+ *
+ * @param bootInstant the [Instant] at which the system booted, this can be computed by
+ * `Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime()) `
+ */
+ public fun getStartInstant(bootInstant: Instant): Instant {
+ return bootInstant.plus(startDurationFromBoot)
+ }
+
+ /**
+ * Returns the end [Instant] of this [DataPoint], knowing the time at which the system booted.
+ *
+ * @param bootInstant the [Instant] at which the system booted, this can be computed by
+ * `Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())`
+ */
+ public fun getEndInstant(bootInstant: Instant): Instant {
+ return bootInstant.plus(endDurationFromBoot)
+ }
+
+ // TODO(b/180612514): Bundle doesn't have equals, so we need to override the data class default.
+ override fun equals(other: Any?): Boolean {
+ if (other === this) {
+ return true
+ }
+ if (other is DataPoint) {
+ return dataType == other.dataType &&
+ value == other.value &&
+ startDurationFromBoot == other.startDurationFromBoot &&
+ endDurationFromBoot == other.endDurationFromBoot &&
+ BundlesUtil.equals(metadata, other.metadata)
+ }
+ return false
+ }
+
+ // TODO(b/180612514): Bundle doesn't have hashCode, so we need to override the data class
+ // default.
+ override fun hashCode(): Int {
+ return Objects.hash(
+ dataType,
+ value,
+ startDurationFromBoot,
+ endDurationFromBoot,
+ BundlesUtil.hashCode(metadata)
+ )
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeParcelable(value, flags)
+ dest.writeLong(startDurationFromBoot.toNanos())
+ dest.writeLong(endDurationFromBoot.toNanos())
+ dest.writeBundle(metadata)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<DataPoint> =
+ object : Parcelable.Creator<DataPoint> {
+ override fun createFromParcel(parcel: Parcel): DataPoint? {
+ val dataType: DataType =
+ parcel.readParcelable(DataType::class.java.classLoader) ?: return null
+ val value: Value =
+ parcel.readParcelable(Value::class.java.classLoader) ?: return null
+ val startDurationFromBoot = Duration.ofNanos(parcel.readLong())
+ val endDurationFromBoot = Duration.ofNanos(parcel.readLong())
+ val metadata: Bundle? = parcel.readBundle(Bundle::class.java.classLoader)
+
+ return when (dataType.timeType) {
+ DataType.TimeType.INTERVAL ->
+ createInterval(
+ dataType,
+ value,
+ startDurationFromBoot,
+ endDurationFromBoot,
+ metadata ?: Bundle()
+ )
+ DataType.TimeType.SAMPLE -> {
+ require(endDurationFromBoot.compareTo(startDurationFromBoot) == 0) {
+ "DataType [$dataType] has SAMPLE type, but" +
+ " start[$startDurationFromBoot]/end[$endDurationFromBoot]" +
+ " duration from boot are not the same"
+ }
+ createSample(
+ dataType,
+ value,
+ startDurationFromBoot,
+ metadata ?: Bundle()
+ )
+ }
+ }
+ }
+
+ override fun newArray(size: Int): Array<DataPoint?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ /**
+ * Returns a [DataPoint] representing the [value] of type [dataType] from
+ * [startDurationFromBoot] to [endDurationFromBoot].
+ *
+ * @throws IllegalArgumentException if the [DataType.TimeType] of the associated [DataType]
+ * is not [DataType.TimeType.INTERVAL], or if data is malformed
+ */
+ @JvmStatic
+ @JvmOverloads
+ public fun createInterval(
+ dataType: DataType,
+ value: Value,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration,
+ metadata: Bundle = Bundle()
+ ): DataPoint {
+ require(DataType.TimeType.INTERVAL == dataType.timeType) {
+ "DataType $dataType must be of interval type to be created with an interval"
+ }
+
+ require(endDurationFromBoot >= startDurationFromBoot) {
+ "End timestamp mustn't be earlier than start timestamp, but got" +
+ " $startDurationFromBoot and $endDurationFromBoot"
+ }
+
+ return DataPoint(dataType, value, startDurationFromBoot, endDurationFromBoot, metadata)
+ }
+
+ /**
+ * Returns a [DataPoint] representing the [value] of type [dataType] at [durationFromBoot].
+ *
+ * @throws IllegalArgumentException if the [DataType.TimeType] of the associated [DataType]
+ * is not [DataType.TimeType.SAMPLE], or if data is malformed
+ */
+ @JvmStatic
+ @JvmOverloads
+ public fun createSample(
+ dataType: DataType,
+ value: Value,
+ durationFromBoot: Duration,
+ metadata: Bundle = Bundle()
+ ): DataPoint {
+ require(DataType.TimeType.SAMPLE == dataType.timeType) {
+ "DataType $dataType must be of sample type to be created with a single timestamp"
+ }
+
+ return DataPoint(
+ dataType,
+ value,
+ durationFromBoot,
+ endDurationFromBoot = durationFromBoot,
+ metadata
+ )
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoints.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoints.kt
new file mode 100644
index 0000000..1168035
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoints.kt
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.content.Intent
+import androidx.annotation.Keep
+import java.time.Duration
+import java.util.ArrayList
+
+/** Helper class to facilitate working with [DataPoint] s. */
+// TODO(b/177504986): Remove all @Keep annotations once we figure out why this class gets stripped
+// away by proguard.
+@Keep
+public object DataPoints {
+ /**
+ * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
+ * this index represents the latitude.
+ */
+ public const val LOCATION_DATA_POINT_LATITUDE_INDEX: Int = 0
+
+ /**
+ * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
+ * this index represents the longitude.
+ */
+ public const val LOCATION_DATA_POINT_LONGITUDE_INDEX: Int = 1
+
+ /**
+ * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
+ * this index represents the altitude. This is an optional index and there is no guarantee that
+ * this index will be present.
+ */
+ public const val LOCATION_DATA_POINT_ALTITUDE_INDEX: Int = 2
+
+ /** Name of intent extra containing the data points set on pending intent. */
+ private const val EXTRA_DATA_POINTS: String = "whs.data_points_list"
+
+ /** Name of intent extra containing whether permissions are granted or not. */
+ private const val EXTRA_PERMISSIONS_GRANTED: String = "whs.data_points_has_permissions"
+
+ /** Retrieves the [DataPoint] s that are contained in the given [Intent], if any. */
+ @JvmStatic
+ @Keep
+ public fun getDataPoints(intent: Intent): List<DataPoint> =
+ intent.getParcelableArrayListExtra(EXTRA_DATA_POINTS) ?: listOf()
+
+ /** Puts the given [DataPoint] s in the given [Intent]. */
+ @JvmStatic
+ public fun putDataPoints(intent: Intent, dataPoints: Collection<DataPoint>) {
+ val copy = ArrayList(dataPoints)
+ intent.putParcelableArrayListExtra(EXTRA_DATA_POINTS, copy)
+ }
+
+ /** Sets whether [DataPoint] permissions are `granted` in the given [Intent]. */
+ @JvmStatic
+ public fun putPermissionsGranted(intent: Intent, granted: Boolean) {
+ intent.putExtra(EXTRA_PERMISSIONS_GRANTED, granted)
+ }
+
+ /** Retrieves whether permissions are granted in this [Intent]. */
+ @JvmStatic
+ public fun getPermissionsGranted(intent: Intent): Boolean =
+ intent.getBooleanExtra(EXTRA_PERMISSIONS_GRANTED, true)
+
+ /** Creates a new [DataPoint] of type [DataType.STEPS] with the given `steps`. */
+ @JvmStatic
+ public fun steps(
+ steps: Long,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.STEPS,
+ Value.ofLong(steps),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.STEPS_PER_MINUTE] with the given
+ * `stepsPerMinute`.
+ */
+ @JvmStatic
+ public fun stepsPerMinute(stepsPerMinute: Long, startDurationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(
+ DataType.STEPS_PER_MINUTE,
+ Value.ofLong(stepsPerMinute),
+ startDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.DISTANCE] with the given `meters`. */
+ @JvmStatic
+ public fun distance(
+ meters: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.DISTANCE,
+ Value.ofDouble(meters),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.ELEVATION] with the given `meters`. */
+ @JvmStatic
+ public fun elevation(
+ meters: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.ELEVATION,
+ Value.ofDouble(meters),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.ALTITUDE] with the given `meters`. */
+ @JvmStatic
+ public fun altitude(meters: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.ALTITUDE, Value.ofDouble(meters), durationFromBoot)
+
+ /** Creates a new [DataPoint] of type [DataType.FLOORS] with the given `floors`. */
+ @JvmStatic
+ public fun floors(
+ floors: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.FLOORS,
+ Value.ofDouble(floors),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.TOTAL_CALORIES] with the given `kcalories`. */
+ @JvmStatic
+ public fun calories(
+ kcalories: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.TOTAL_CALORIES,
+ Value.ofDouble(kcalories),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.SWIMMING_STROKES] with the given `kcalories`. */
+ @JvmStatic
+ public fun swimmingStrokes(
+ strokes: Long,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.SWIMMING_STROKES,
+ Value.ofLong(strokes),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.LOCATION] with the given `latitude` and
+ * `longitude`.
+ */
+ @JvmStatic
+ public fun location(
+ latitude: Double,
+ longitude: Double,
+ durationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createSample(
+ DataType.LOCATION,
+ Value.ofDoubleArray(latitude, longitude),
+ durationFromBoot
+ )
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.LOCATION] with the given `latitude`, `longitude`
+ * and `altitude`.
+ */
+ @JvmStatic
+ public fun location(
+ latitude: Double,
+ longitude: Double,
+ altitude: Double,
+ durationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createSample(
+ DataType.LOCATION,
+ Value.ofDoubleArray(latitude, longitude, altitude),
+ durationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.SPEED] with the given `metersPerSecond`. */
+ @JvmStatic
+ public fun speed(metersPerSecond: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.SPEED, Value.ofDouble(metersPerSecond), durationFromBoot)
+
+ /** Creates a new [DataPoint] of type [DataType.PACE] with the given `millisPerKm`. */
+ @JvmStatic
+ public fun pace(millisPerKm: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.PACE, Value.ofDouble(millisPerKm), durationFromBoot)
+
+ /** Creates a new [DataPoint] of type [DataType.HEART_RATE_BPM] with the given `bpm`. */
+ @JvmStatic
+ public fun heartRate(bpm: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.HEART_RATE_BPM, Value.ofDouble(bpm), durationFromBoot)
+
+ /** Creates a new [DataPoint] of type [DataType.SPO2] with the given `percent`. */
+ @JvmStatic
+ public fun spo2(percent: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.SPO2, Value.ofDouble(percent), durationFromBoot)
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.AGGREGATE_DISTANCE] with the given `distance`.
+ */
+ @JvmStatic
+ public fun aggregateDistance(
+ distance: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.AGGREGATE_DISTANCE,
+ Value.ofDouble(distance),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.AGGREGATE_STEP_COUNT] with the given `steps`. */
+ @JvmStatic
+ public fun aggregateSteps(
+ steps: Long,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.AGGREGATE_STEP_COUNT,
+ Value.ofLong(steps),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.AGGREGATE_CALORIES_EXPENDED] with the given
+ * `kcalories`.
+ */
+ @JvmStatic
+ public fun aggregateCalories(
+ kcalories: Double,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.AGGREGATE_CALORIES_EXPENDED,
+ Value.ofDouble(kcalories),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.AGGREGATE_SWIMMING_STROKE_COUNT] with the given
+ * `swimmingStrokes`.
+ */
+ @JvmStatic
+ public fun aggregateSwimmingStrokes(
+ swimmingStrokes: Long,
+ startDurationFromBoot: Duration,
+ endDurationFromBoot: Duration
+ ): DataPoint =
+ DataPoint.createInterval(
+ DataType.AGGREGATE_SWIMMING_STROKE_COUNT,
+ Value.ofLong(swimmingStrokes),
+ startDurationFromBoot,
+ endDurationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.AVERAGE_PACE] with the given `millisPerKm`. */
+ @JvmStatic
+ public fun averagePace(millisPerKm: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(DataType.AVERAGE_PACE, Value.ofDouble(millisPerKm), durationFromBoot)
+
+ /**
+ * Creates a new [DataPoint] of type [DataType.AVERAGE_SPEED] with the given `metersPerSecond`.
+ */
+ @JvmStatic
+ public fun averageSpeed(metersPerSecond: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(
+ DataType.AVERAGE_SPEED,
+ Value.ofDouble(metersPerSecond),
+ durationFromBoot
+ )
+
+ /** Creates a new [DataPoint] of type [DataType.MAX_SPEED] with the given `metersPerSecond`. */
+ @JvmStatic
+ public fun maxSpeed(metersPerSecond: Double, durationFromBoot: Duration): DataPoint =
+ DataPoint.createSample(
+ DataType.MAX_SPEED,
+ Value.ofDouble(metersPerSecond),
+ durationFromBoot
+ )
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
new file mode 100644
index 0000000..d3a0ac6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A data type is a representation of health data managed by Health Services.
+ *
+ * A [DataType] specifies the format of the values inside a [DataPoint]. WHS defines data types for
+ * instantaneous observations [TimeType.SAMPLE](e.g. heart rate) and data types for change between
+ * readings [TimeType.INTERVAL](e.g. distance).
+ *
+ * Note: the data type defines only the representation and format of the data, and not how it's
+ * being collected, the sensor being used, or the parameters of the collection.
+ */
+public data class DataType(
+ /** Returns the name of this [DataType], e.g. `"Steps"`. */
+ val name: String,
+ /** Returns the [TimeType] of this [DataType]. */
+ val timeType: TimeType,
+ /** Returns the expected format for a [Value] of this [DataType]. */
+ val format: Int,
+) : Parcelable {
+
+ /**
+ * Whether the `DataType` corresponds to a measurement spanning an interval, or a sample at a
+ * single point in time.
+ */
+ public enum class TimeType {
+ INTERVAL,
+ SAMPLE
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ with(dest) {
+ writeString(name)
+ writeString(timeType.name)
+ writeInt(format)
+ }
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<DataType> =
+ object : Parcelable.Creator<DataType> {
+ override fun createFromParcel(parcel: Parcel): DataType? {
+ return DataType(
+ parcel.readString() ?: return null,
+ TimeType.valueOf(parcel.readString() ?: return null),
+ parcel.readInt()
+ )
+ }
+
+ override fun newArray(size: Int): Array<DataType?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ /** Current altitude expressed in meters in `double` format. */
+ @JvmField
+ public val ALTITUDE: DataType = DataType("Altitude", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** A distance delta between each reading expressed in meters in `double` format. */
+ @JvmField
+ public val DISTANCE: DataType = DataType("Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * A duration delta during an exercise over which the user was traveling down a decline,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val DECLINE_TIME: DataType =
+ DataType("Decline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * A distance delta traveled over declining ground between each reading expressed in meters
+ * in `double` format.
+ */
+ @JvmField
+ public val DECLINE_DISTANCE: DataType =
+ DataType("Decline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * A duration delta during an exercise over which the user was traveling across flat ground,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val FLAT_TIME: DataType = DataType("Flat Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * A distance delta traveled over flat ground between each reading expressed in meters in
+ * `double` format.
+ */
+ @JvmField
+ public val FLAT_DISTANCE: DataType =
+ DataType("Flat Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * A duration delta during an exercise over which the user was traveling up an incline,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val INCLINE_TIME: DataType =
+ DataType("Incline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * A distance delta traveled over inclining ground between each reading expressed in meters
+ * in `double` format.
+ */
+ @JvmField
+ public val INCLINE_DISTANCE: DataType =
+ DataType("Incline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** An elevation delta between each reading expressed in meters in `double` format. */
+ @JvmField
+ public val ELEVATION: DataType =
+ DataType("Elevation", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** Absolute elevation between each reading expressed in meters in `double` format. */
+ @JvmField
+ public val ABSOLUTE_ELEVATION: DataType =
+ DataType("Absolute Elevation", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** Number of floors climbed between each reading in `double` format */
+ @JvmField
+ public val FLOORS: DataType = DataType("Floors", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** Current heart rate, in beats per minute in `double` format. */
+ @JvmField
+ public val HEART_RATE_BPM: DataType =
+ DataType("HeartRate", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /**
+ * Current latitude, longitude and optionally, altitude in `double[]` format. Latitude at
+ * index [DataPoints.LOCATION_DATA_POINT_LATITUDE_INDEX], longitude at index
+ * [DataPoints.LOCATION_DATA_POINT_LONGITUDE_INDEX] and if available, altitude at index
+ * [DataPoints.LOCATION_DATA_POINT_ALTITUDE_INDEX]
+ */
+ @JvmField
+ public val LOCATION: DataType =
+ DataType("Location", TimeType.SAMPLE, Value.FORMAT_DOUBLE_ARRAY)
+
+ /** Current speed over time. In meters/second in `double` format. */
+ @JvmField
+ public val SPEED: DataType = DataType("Speed", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** Percentage of oxygen in the blood in `double` format. Valid range `0f` - `100f`. */
+ @JvmField public val SPO2: DataType = DataType("SpO2", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** Rate of oxygen consumption in `double` format. Valid range `0f` - `100f`. */
+ @JvmField public val VO2: DataType = DataType("VO2", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /**
+ * Maximum rate of oxygen consumption measured during incremental exercise in `double`
+ * format. Valid range `0f` - `100f`.
+ */
+ @JvmField
+ public val VO2_MAX: DataType = DataType("VO2 Max", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** Delta of steps between each reading in `long` format. */
+ @JvmField
+ public val STEPS: DataType = DataType("Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** Delta of walking steps between each reading in `long` format. */
+ @JvmField
+ public val WALKING_STEPS: DataType =
+ DataType("Walking Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** Delta of running steps between each reading in `long` format. */
+ @JvmField
+ public val RUNNING_STEPS: DataType =
+ DataType("Running Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** Current step rate in steps/minute in `long` format. */
+ @JvmField
+ public val STEPS_PER_MINUTE: DataType =
+ DataType("Step per minute", TimeType.SAMPLE, Value.FORMAT_LONG)
+
+ /** Delta of strokes between each reading of swimming strokes in `long` format. */
+ @JvmField
+ public val SWIMMING_STROKES: DataType =
+ DataType("Swimming Strokes", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * Delta of total calories (including basal rate and activity) between each reading in
+ * `double` format.
+ */
+ @JvmField
+ public val TOTAL_CALORIES: DataType =
+ DataType("Calories", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** Current pace. In millisec/km in `double` format. */
+ @JvmField public val PACE: DataType = DataType("Pace", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The aggregate distance over a period of time expressed in meters in `double` format. */
+ @JvmField
+ public val AGGREGATE_DISTANCE: DataType =
+ DataType("Aggregate Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * The aggregate flat distance over a period of time expressed in meters in `double` format.
+ */
+ @JvmField
+ public val AGGREGATE_FLAT_DISTANCE: DataType =
+ DataType("Aggregate Flat Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * The aggregate incline distance over a period of time expressed in meters in `double`
+ * format.
+ */
+ @JvmField
+ public val AGGREGATE_INCLINE_DISTANCE: DataType =
+ DataType("Aggregate Incline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * The aggregate incline distance over a period of time expressed in meters in `double`
+ * format.
+ */
+ @JvmField
+ public val AGGREGATE_DECLINE_DISTANCE: DataType =
+ DataType("Aggregate Decline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /**
+ * The aggregate duration for an exercise when the user was traveling on flat ground,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val AGGREGATE_FLAT_TIME: DataType =
+ DataType("Aggregate Flat Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * The aggregate duration for an exercise when the user was traveling up an incline,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val AGGREGATE_INCLINE_TIME: DataType =
+ DataType("Aggregate Incline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * The aggregate duration for an exercise when the user was traveling down a decline,
+ * expressed in seconds in `long` format.
+ */
+ @JvmField
+ public val AGGREGATE_DECLINE_TIME: DataType =
+ DataType("Aggregate Decline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /**
+ * The aggregate total calories (including basal rate and activity) expended over a period
+ * of time in `double` format.
+ */
+ @JvmField
+ public val AGGREGATE_CALORIES_EXPENDED: DataType =
+ DataType("Aggregate Calories", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** The aggregate step count over a period of time in `long` format. */
+ @JvmField
+ public val AGGREGATE_STEP_COUNT: DataType =
+ DataType("Aggregate Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** The aggregate walking step count over a period of time in `long` format. */
+ @JvmField
+ public val AGGREGATE_WALKING_STEP_COUNT: DataType =
+ DataType("Aggregate Walking Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** The aggregate running step count over a period of time in `long` format. */
+ @JvmField
+ public val AGGREGATE_RUNNING_STEP_COUNT: DataType =
+ DataType("Aggregate Running Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** The aggregate swimming stroke count over a period of time in `long` format. */
+ @JvmField
+ public val AGGREGATE_SWIMMING_STROKE_COUNT: DataType =
+ DataType("Aggregate Swimming Strokes", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** The aggregate elevation over a period of time in meters in `double` format. */
+ @JvmField
+ public val AGGREGATE_ELEVATION: DataType =
+ DataType("Aggregate Elevation", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** The number of floors climbed over a period of time in `double` format */
+ @JvmField
+ public val AGGREGATE_FLOORS: DataType =
+ DataType("Aggregate Floors", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+ /** The average pace over a period of time in millisec/km in `double` format. */
+ @JvmField
+ public val AVERAGE_PACE: DataType =
+ DataType("Average Pace", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The average speed over a period of time in meters/second in `double` format. */
+ @JvmField
+ public val AVERAGE_SPEED: DataType =
+ DataType("Average Speed", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The average heart rate over a period of time in beats/minute in `double` format. */
+ @JvmField
+ public val AVERAGE_HEART_RATE_BPM: DataType =
+ DataType("Average Heart Rate BPM", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The maximum altitude over a period of time in meters in `double` format. */
+ @JvmField
+ public val MAX_ALTITUDE: DataType =
+ DataType("Max Altitude", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The minimum altitude over a period of time in meters in `double` format. */
+ @JvmField
+ public val MIN_ALTITUDE: DataType =
+ DataType("Min Altitude", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The maximum pace over a period of time in millisec/km in `double` format. */
+ @JvmField
+ public val MAX_PACE: DataType = DataType("Max Pace", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The maximum speed over a period of time in meters/second in `double` format. */
+ @JvmField
+ public val MAX_SPEED: DataType = DataType("Max Speed", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /** The maximum instantaneous heart rate in beats/minute in `double` format. */
+ @JvmField
+ public val MAX_HEART_RATE_BPM: DataType =
+ DataType("Max Heart Rate", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+ /**
+ * The duration during which the user was resting during an Exercise in seconds in `long`
+ * format.
+ */
+ @JvmField
+ public val RESTING_EXERCISE_DURATION: DataType =
+ DataType("Resting Exercise Duration", TimeType.SAMPLE, Value.FORMAT_LONG)
+
+ /** The duration of the time the Exercise was ACTIVE in seconds in `long` format. */
+ @JvmField
+ public val ACTIVE_EXERCISE_DURATION: DataType =
+ DataType("Active Exercise Duration", TimeType.SAMPLE, Value.FORMAT_LONG)
+
+ /** Count of swimming laps ins `long` format. */
+ @JvmField
+ public val SWIMMING_LAP_COUNT: DataType =
+ DataType("Swim Lap Count", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+ /** The current rep count of the exercise in `long` format. */
+ @JvmField
+ public val REP_COUNT: DataType = DataType("Rep Count", TimeType.INTERVAL, Value.FORMAT_LONG)
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeCondition.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeCondition.kt
new file mode 100644
index 0000000..152c9df
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeCondition.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** A condition which is considered met when a data type value passes a defined threshold. */
+public data class DataTypeCondition(
+ val dataType: DataType,
+ val threshold: Value,
+ val comparisonType: ComparisonType,
+) : Parcelable {
+ init {
+ require(dataType.format == threshold.format) {
+ "provided data type must have sample time type."
+ }
+ }
+
+ /** Checks whether or not the condition is satisfied by a given [DataPoint]. */
+ public fun isSatisfied(dataPoint: DataPoint): Boolean {
+ require(dataType == dataPoint.dataType) {
+ "attempted to evaluate data type condition with incorrect data type. Expected " +
+ "${dataType.name} but was ${dataPoint.dataType.name}"
+ }
+ return isThresholdSatisfied(dataPoint.value)
+ }
+
+ /** Checks whether or not the value of the condition is satisfied by a given [Value]. */
+ public fun isThresholdSatisfied(value: Value): Boolean {
+ val comparison = Value.compare(value, threshold)
+ return when (comparisonType) {
+ ComparisonType.LESS_THAN -> comparison < 0
+ ComparisonType.GREATER_THAN -> comparison > 0
+ ComparisonType.LESS_THAN_OR_EQUAL -> comparison <= 0
+ ComparisonType.GREATER_THAN_OR_EQUAL -> comparison >= 0
+ }
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeParcelable(threshold, flags)
+ dest.writeInt(comparisonType.id)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<DataTypeCondition> =
+ object : Parcelable.Creator<DataTypeCondition> {
+ override fun createFromParcel(source: Parcel): DataTypeCondition? {
+ val dataType =
+ source.readParcelable<DataType>(DataType::class.java.classLoader)
+ ?: return null
+ val threshold =
+ source.readParcelable<Value>(Value::class.java.classLoader) ?: return null
+ val comparisonType = ComparisonType.fromId(source.readInt()) ?: return null
+ return DataTypeCondition(dataType, threshold, comparisonType)
+ }
+
+ override fun newArray(size: Int): Array<DataTypeCondition?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypes.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypes.kt
new file mode 100644
index 0000000..b821de8
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypes.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+/** Helper class to facilitate working with [DataTypes] [DataType]. */
+public object DataTypes {
+ private val AGGREGATE_TYPE_TO_RAW_TYPE =
+ DataTypeBiMap(
+ DataType.AGGREGATE_DISTANCE to DataType.DISTANCE,
+ DataType.AGGREGATE_CALORIES_EXPENDED to DataType.TOTAL_CALORIES,
+ DataType.AGGREGATE_STEP_COUNT to DataType.STEPS,
+ DataType.AGGREGATE_ELEVATION to DataType.ELEVATION,
+ DataType.AGGREGATE_FLOORS to DataType.FLOORS,
+ DataType.AGGREGATE_SWIMMING_STROKE_COUNT to DataType.SWIMMING_STROKES
+ )
+
+ private val MAX_TYPE_TO_RAW_TYPE =
+ DataTypeBiMap(
+ DataType.MAX_HEART_RATE_BPM to DataType.HEART_RATE_BPM,
+ DataType.MAX_PACE to DataType.PACE,
+ DataType.MAX_SPEED to DataType.SPEED
+ )
+
+ private val AVERAGE_TYPE_TO_RAW_TYPE =
+ DataTypeBiMap(
+ DataType.AVERAGE_HEART_RATE_BPM to DataType.HEART_RATE_BPM,
+ DataType.AVERAGE_PACE to DataType.PACE,
+ DataType.AVERAGE_SPEED to DataType.SPEED
+ )
+
+ /** Check if a [DataType] represents aggregate value of a collection of non-aggregate data. */
+ @JvmStatic
+ public fun isAggregateDataType(dataType: DataType): Boolean =
+ AGGREGATE_TYPE_TO_RAW_TYPE.map.containsKey(dataType)
+
+ /** Check if a [DataType] represents the maximum value of a collection of non-aggregate data. */
+ @JvmStatic
+ public fun isStatisticalMaxDataType(dataType: DataType): Boolean =
+ MAX_TYPE_TO_RAW_TYPE.map.containsKey(dataType)
+
+ /** Check if a [DataType] represents the average value of a collection of non-aggregate data. */
+ @JvmStatic
+ public fun isStatisticalAverageDataType(dataType: DataType): Boolean =
+ AVERAGE_TYPE_TO_RAW_TYPE.map.containsKey(dataType)
+
+ /**
+ * Check if a [DataType] represents raw data value, i.e., neither aggregate value nor
+ * statistical value.
+ */
+ @JvmStatic
+ public fun isRawType(dataType: DataType): Boolean =
+ !isAggregateDataType(dataType) &&
+ !isStatisticalMaxDataType(dataType) &&
+ !isStatisticalAverageDataType(dataType)
+
+ /** Get the aggregate [DataType] from a raw [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getAggregateTypeFromRawType(rawType: DataType): DataType? =
+ AGGREGATE_TYPE_TO_RAW_TYPE.inverse[rawType]
+
+ /** Get the raw [DataType] from an aggregate [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getRawTypeFromAggregateType(aggregateType: DataType): DataType? =
+ AGGREGATE_TYPE_TO_RAW_TYPE.map[aggregateType]
+
+ /** Get the max [DataType] from a raw [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getMaxTypeFromRawType(rawType: DataType): DataType? =
+ MAX_TYPE_TO_RAW_TYPE.inverse[rawType]
+
+ /** Get the raw [DataType] from a max [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getRawTypeFromMaxType(maxType: DataType): DataType? =
+ MAX_TYPE_TO_RAW_TYPE.map[maxType]
+
+ /** Get the average [DataType] from a raw [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getAverageTypeFromRawType(rawType: DataType): DataType? =
+ AVERAGE_TYPE_TO_RAW_TYPE.inverse[rawType]
+
+ /** Get the raw [DataType] from an average [DataType], or null if it doesn't exist. */
+ @JvmStatic
+ public fun getRawTypeFromAverageType(averageType: DataType): DataType? =
+ AVERAGE_TYPE_TO_RAW_TYPE.map[averageType]
+
+ /** Get the aggregate, average, and max [DataType] from a raw [DataType] if they exist. */
+ @JvmStatic
+ public fun getAggregatedDataTypesFromRawType(rawType: DataType): Set<DataType> {
+ val allDataTypes = HashSet<DataType>()
+
+ getAggregateTypeFromRawType(rawType)?.let { allDataTypes.add(it) }
+ getMaxTypeFromRawType(rawType)?.let { allDataTypes.add(it) }
+ getAverageTypeFromRawType(rawType)?.let { allDataTypes.add(it) }
+
+ return allDataTypes
+ }
+
+ private class DataTypeBiMap(vararg pairs: Pair<DataType, DataType>) {
+ val map = mapOf(*pairs)
+ val inverse = pairs.map { it.second to it.first }.toMap()
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt
new file mode 100644
index 0000000..6564f06
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** Provides exercise specific capabilities data. */
+public data class ExerciseCapabilities(
+ val supportedDataTypes: Set<DataType>,
+ val supportedGoals: Map<DataType, Set<ComparisonType>>,
+ val supportedMilestones: Map<DataType, Set<ComparisonType>>,
+ val supportsAutoPauseAndResume: Boolean,
+ val supportsLaps: Boolean,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(supportedDataTypes.size)
+ dest.writeTypedArray(supportedDataTypes.toTypedArray(), flags)
+
+ writeSupportedDataTypes(supportedGoals, dest, flags)
+ writeSupportedDataTypes(supportedMilestones, dest, flags)
+
+ dest.writeInt(if (supportsAutoPauseAndResume) 1 else 0)
+ dest.writeInt(if (supportsLaps) 1 else 0)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseCapabilities> =
+ object : Parcelable.Creator<ExerciseCapabilities> {
+ override fun createFromParcel(source: Parcel): ExerciseCapabilities? {
+ val supportedDataTypesArray = Array<DataType?>(source.readInt()) { null }
+ source.readTypedArray(supportedDataTypesArray, DataType.CREATOR)
+
+ val supportedGoals = readSupportedDataTypes(source) ?: return null
+ val supportedMilestones = readSupportedDataTypes(source) ?: return null
+ val supportsAutoPauseAndResume = source.readInt() == 1
+ val supportsLaps = source.readInt() == 1
+
+ return ExerciseCapabilities(
+ supportedDataTypesArray.filterNotNull().toSet(),
+ supportedGoals,
+ supportedMilestones,
+ supportsAutoPauseAndResume,
+ supportsLaps
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseCapabilities?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ private fun writeSupportedDataTypes(
+ supportedDataTypes: Map<DataType, Set<ComparisonType>>,
+ dest: Parcel,
+ flags: Int
+ ) {
+ dest.writeInt(supportedDataTypes.size)
+ for ((dataType, comparisonTypeSet) in supportedDataTypes) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeInt(comparisonTypeSet.size)
+ dest.writeIntArray(comparisonTypeSet.map { it.id }.toIntArray())
+ }
+ }
+
+ private fun readSupportedDataTypes(source: Parcel): Map<DataType, Set<ComparisonType>>? {
+ val supportedDataTypes = HashMap<DataType, Set<ComparisonType>>()
+
+ val numSupportedDataTypes = source.readInt()
+ repeat(numSupportedDataTypes) {
+ val dataType: DataType =
+ source.readParcelable(DataType::class.java.classLoader) ?: return null
+
+ val comparisonTypeIntArray = IntArray(source.readInt())
+ source.readIntArray(comparisonTypeIntArray)
+ val comparisonTypeSet =
+ comparisonTypeIntArray.map { ComparisonType.fromId(it) }.filterNotNull().toSet()
+
+ supportedDataTypes[dataType] = comparisonTypeSet
+ }
+
+ return supportedDataTypes
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
new file mode 100644
index 0000000..9531eea
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import java.util.Objects
+
+/** Defines configuration for an exercise tracked using WHS. */
+@Suppress("DataClassPrivateConstructor")
+public data class ExerciseConfig
+protected constructor(
+ /**
+ * [ExerciseType] the user is performing for this exercise.
+ *
+ * This information can be used to tune sensors, e.g. the calories estimate can take the MET
+ * value into account.
+ */
+ val exerciseType: ExerciseType,
+ val dataTypes: Set<DataType>,
+ val autoPauseAndResume: Boolean,
+ val exerciseGoals: List<ExerciseGoal>,
+ val exerciseParams: Bundle,
+) : Parcelable {
+ init {
+ require(dataTypes.isNotEmpty()) { "Must specify the desired data types." }
+ require(exerciseType != ExerciseType.UNKNOWN) { "Must specify a valid exercise type." }
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(exerciseType.id)
+ dest.writeInt(dataTypes.size)
+ dest.writeTypedArray(dataTypes.toTypedArray(), flags)
+ dest.writeInt(if (autoPauseAndResume) 1 else 0)
+ dest.writeInt(exerciseGoals.size)
+ dest.writeTypedArray(exerciseGoals.toTypedArray(), flags)
+ dest.writeBundle(exerciseParams)
+ }
+
+ /** Builder for [ExerciseConfig] instances. */
+ public class Builder {
+ private var exerciseType: ExerciseType? = null
+ private var dataTypes: Set<DataType>? = null
+ private var autoPauseAndResume: Boolean = false
+ private var exerciseGoals: List<ExerciseGoal> = emptyList()
+ private var exerciseParams: Bundle = Bundle.EMPTY
+
+ /**
+ * Sets the active [ExerciseType] the user is performing for this exercise.
+ *
+ * Provide this parameter when tracking a workout to provide more accurate data. This
+ * information can be used to tune sensors, e.g. the calories estimate can take the MET
+ * value into account.
+ */
+ public fun setExerciseType(exerciseType: ExerciseType): Builder {
+ this.exerciseType = exerciseType
+ return this
+ }
+
+ /**
+ * Sets the requested [DataType] s that should be tracked during this exercise. If not
+ * explicitly called, a default set of [DataType] will be chosen based on the [ ].
+ */
+ public fun setDataTypes(dataTypes: Set<DataType>): Builder {
+ this.dataTypes = dataTypes.toSet()
+ return this
+ }
+
+ /**
+ * Sets whether auto pause and auto resume are enabled for this exercise. If not set,
+ * they're disabled by default.
+ */
+ public fun setAutoPauseAndResume(autoPauseAndResume: Boolean): Builder {
+ this.autoPauseAndResume = autoPauseAndResume
+ return this
+ }
+
+ /**
+ * Sets [ExerciseGoal] s specified for this exercise.
+ *
+ * This is useful to have goals specified before the start of an exercise.
+ */
+ public fun setExerciseGoals(exerciseGoals: List<ExerciseGoal>): Builder {
+ this.exerciseGoals = exerciseGoals.toList()
+ return this
+ }
+
+ /**
+ * Sets additional parameters for current exercise. Supported keys can be found in
+ * [ExerciseConfig].
+ */
+ // TODO(b/180612514) expose keys on a per-OEM basis.
+ public fun setExerciseParams(exerciseParams: Bundle): Builder {
+ this.exerciseParams = exerciseParams
+ return this
+ }
+
+ /** Returns the built `ExerciseConfig`. */
+ public fun build(): ExerciseConfig {
+ return ExerciseConfig(
+ checkNotNull(exerciseType) { "No exercise type specified" },
+ checkNotNull(dataTypes) { "No data types specified" },
+ autoPauseAndResume,
+ exerciseGoals,
+ exerciseParams
+ )
+ }
+ }
+
+ // TODO(b/180612514): Bundle doesn't have equals, so we need to override the data class default.
+ override fun equals(other: Any?): Boolean {
+ if (other === this) {
+ return true
+ }
+ if (other is ExerciseConfig) {
+ return exerciseType == other.exerciseType &&
+ dataTypes == other.dataTypes &&
+ autoPauseAndResume == other.autoPauseAndResume &&
+ exerciseGoals == other.exerciseGoals &&
+ BundlesUtil.equals(exerciseParams, other.exerciseParams)
+ }
+ return false
+ }
+
+ // TODO(b/180612514): Bundle doesn't have hashCode, so we need to override the data class
+ // default.
+ override fun hashCode(): Int {
+ return Objects.hash(
+ exerciseType,
+ dataTypes,
+ autoPauseAndResume,
+ exerciseGoals,
+ BundlesUtil.hashCode(exerciseParams)
+ )
+ }
+
+ public companion object {
+ @JvmStatic public fun builder(): Builder = Builder()
+
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseConfig> =
+ object : Parcelable.Creator<ExerciseConfig> {
+ override fun createFromParcel(source: Parcel): ExerciseConfig? {
+ val exerciseType = ExerciseType.fromId(source.readInt())
+
+ val dataTypesArray = Array<DataType?>(source.readInt()) { null }
+ source.readTypedArray(dataTypesArray, DataType.CREATOR)
+
+ val autoPauseAndResume = source.readInt() == 1
+
+ val exerciseGoals = Array<ExerciseGoal?>(source.readInt()) { null }
+ source.readTypedArray(exerciseGoals, ExerciseGoal.CREATOR)
+
+ val exerciseParams =
+ source.readBundle(ExerciseConfig::class.java.classLoader) ?: Bundle()
+
+ return ExerciseConfig(
+ exerciseType,
+ dataTypesArray.filterNotNull().toSet(),
+ autoPauseAndResume,
+ exerciseGoals.filterNotNull().toList(),
+ exerciseParams
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseConfig?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoal.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoal.kt
new file mode 100644
index 0000000..39c2ab1
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoal.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.util.Objects
+
+// TODO(yeabkal): as we support more types of goals, we may want to rename the class.
+/** Defines a goal for an exercise. */
+@Suppress("DataClassPrivateConstructor")
+public data class ExerciseGoal
+protected constructor(
+ val exerciseGoalType: ExerciseGoalType,
+ val dataTypeCondition: DataTypeCondition,
+ // TODO(yeabkal): shall we rename to "getMilestonePeriod"? Currently "getPeriod" is used to be
+ // flexible in case we support other kinds of goals. Recheck when design is fully locked.
+ val period: Value? = null,
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(exerciseGoalType.id)
+ dest.writeParcelable(dataTypeCondition, flags)
+ dest.writeParcelable(period, flags)
+ }
+
+ // TODO(yeabkal): try to unify equality logic across goal types.
+ // TODO(b/186899729): We need a better way to match on achieved goals.
+ override fun equals(other: Any?): Boolean {
+ if (other === this) {
+ return true
+ }
+ if (other !is ExerciseGoal) {
+ return false
+ }
+
+ if (this.exerciseGoalType != other.exerciseGoalType) {
+ return false
+ }
+
+ return when (exerciseGoalType) {
+ ExerciseGoalType.ONE_TIME_GOAL -> dataTypeCondition == other.dataTypeCondition
+ // The threshold of a milestone is not included in the equality calculation to let apps
+ // easily map back an achieved milestone to the milestone they requested for tracking.
+ ExerciseGoalType.MILESTONE ->
+ dataTypeCondition.dataType == other.dataTypeCondition.dataType &&
+ dataTypeCondition.comparisonType == other.dataTypeCondition.comparisonType &&
+ period == other.period
+ }
+ }
+
+ override fun hashCode(): Int {
+ return if (exerciseGoalType == ExerciseGoalType.ONE_TIME_GOAL) {
+ Objects.hash(exerciseGoalType, dataTypeCondition)
+ } else {
+ Objects.hash(
+ exerciseGoalType,
+ dataTypeCondition.dataType,
+ dataTypeCondition.comparisonType,
+ period
+ )
+ }
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseGoal> =
+ object : Parcelable.Creator<ExerciseGoal> {
+ override fun createFromParcel(source: Parcel): ExerciseGoal? {
+ val exerciseGoalType = ExerciseGoalType.fromId(source.readInt()) ?: return null
+ val dataTypeCondition: DataTypeCondition =
+ source.readParcelable(DataTypeCondition::class.java.classLoader)
+ ?: return null
+ val period: Value? = source.readParcelable(Value::class.java.classLoader)
+
+ return ExerciseGoal(exerciseGoalType, dataTypeCondition, period)
+ }
+
+ override fun newArray(size: Int): Array<ExerciseGoal?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ /**
+ * Creates an [ExerciseGoal] that is achieved once when the given [DataTypeCondition] is
+ * satisfied.
+ */
+ @JvmStatic
+ public fun createOneTimeGoal(condition: DataTypeCondition): ExerciseGoal {
+ return ExerciseGoal(ExerciseGoalType.ONE_TIME_GOAL, condition)
+ }
+
+ /**
+ * Creates an [ExerciseGoal] that is achieved multiple times with its threshold being
+ * updated by a `period` value each time it is achieved. For instance, a milestone could be
+ * one for every 2km. This goal will there be triggered at distances = 2km, 4km, 6km, ...
+ */
+ @JvmStatic
+ public fun createMilestone(condition: DataTypeCondition, period: Value): ExerciseGoal {
+ require(period.format == condition.threshold.format) {
+ "The condition's threshold and the period should have the same types of values."
+ }
+ return ExerciseGoal(ExerciseGoalType.MILESTONE, condition, period)
+ }
+
+ /** Creates a new goal that is the same as a given goal but with a new threshold value. */
+ @JvmStatic
+ public fun createMilestoneGoalWithUpdatedThreshold(
+ goal: ExerciseGoal,
+ newThreshold: Value
+ ): ExerciseGoal {
+ require(ExerciseGoalType.MILESTONE == goal.exerciseGoalType) {
+ "The goal to update should be of MILESTONE type."
+ }
+ require(goal.period != null) { "The milestone goal's period should not be null." }
+ val (dataType, oldThreshold, comparisonType) = goal.dataTypeCondition
+ require(oldThreshold.format == newThreshold.format) {
+ "The old and new thresholds should have the same types of values."
+ }
+ return ExerciseGoal(
+ ExerciseGoalType.MILESTONE,
+ DataTypeCondition(dataType, newThreshold, comparisonType),
+ goal.period
+ )
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt
similarity index 65%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt
index 2aa2c9c..0790057 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt
@@ -14,8 +14,15 @@
* limitations under the License.
*/
-/**
- * Insert package level documentation here
- */
-package androidx.health.services.client;
+package androidx.health.services.client.data
+/** Exercise goal types. */
+public enum class ExerciseGoalType(public val id: Int) {
+ ONE_TIME_GOAL(1),
+ MILESTONE(2);
+
+ public companion object {
+ @JvmStatic
+ public fun fromId(id: Int): ExerciseGoalType? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseInfo.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseInfo.kt
new file mode 100644
index 0000000..7c43643
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseInfo.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** High-level info about the exercise. */
+public data class ExerciseInfo(
+ /** Returns the [ExerciseTrackedStatus]. */
+ val exerciseTrackedStatus: ExerciseTrackedStatus,
+
+ /**
+ * Returns the [ExerciseType] of the active exercise, or [ExerciseType.UNKNOWN] if there is no
+ * active exercise.
+ */
+ val exerciseType: ExerciseType,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(exerciseTrackedStatus.id)
+ dest.writeInt(exerciseType.id)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseInfo> =
+ object : Parcelable.Creator<ExerciseInfo> {
+ override fun createFromParcel(source: Parcel): ExerciseInfo? {
+ return ExerciseInfo(
+ ExerciseTrackedStatus.fromId(source.readInt()) ?: return null,
+ ExerciseType.fromId(source.readInt())
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseInfo?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt
new file mode 100644
index 0000000..cd17ab1
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+
+/** Describes a completed exercise lap. */
+public data class ExerciseLapSummary(
+ /** Returns the lap count of this summary. Lap count starts at 1 for the first lap. */
+ val lapCount: Int,
+
+ /** Returns the time at which the lap has started. */
+ val startTime: Instant,
+
+ /** Returns the time at which the lap has ended. */
+ val endTime: Instant,
+
+ /**
+ * Returns the total elapsed time for which the exercise has been active during this lap, i.e.
+ * started but not paused.
+ */
+ val activeDuration: Duration,
+
+ /**
+ * Returns the [DataPoint] s for each metric keyed by [DataType] tracked between [startTime] and
+ * [endTime] i.e. during the duration of this lap. This will only contain aggregated [DataType]
+ * s calculated over the duration of the lap.
+ */
+ val lapMetrics: Map<DataType, DataPoint>,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(lapCount)
+ dest.writeLong(startTime.toEpochMilli())
+ dest.writeLong(endTime.toEpochMilli())
+ dest.writeLong(activeDuration.toMillis())
+
+ dest.writeInt(lapMetrics.size)
+ for ((dataType, dataPoint) in lapMetrics) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeParcelable(dataPoint, flags)
+ }
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseLapSummary> =
+ object : Parcelable.Creator<ExerciseLapSummary> {
+ override fun createFromParcel(source: Parcel): ExerciseLapSummary? {
+ val lapCount = source.readInt()
+ val startTime = Instant.ofEpochMilli(source.readLong())
+ val endTime = Instant.ofEpochMilli(source.readLong())
+ val activeDuration = Duration.ofMillis(source.readLong())
+
+ val lapMetrics = HashMap<DataType, DataPoint>()
+ val numMetrics = source.readInt()
+ repeat(numMetrics) {
+ val dataType: DataType =
+ source.readParcelable(DataType::class.java.classLoader) ?: return null
+ lapMetrics[dataType] =
+ source.readParcelable(DataPoint::class.java.classLoader) ?: return null
+ }
+
+ return ExerciseLapSummary(
+ lapCount,
+ startTime,
+ endTime,
+ activeDuration,
+ lapMetrics
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseLapSummary?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt
new file mode 100644
index 0000000..964e271
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+/** Enumerates the state of an exercise. */
+public enum class ExerciseState(public val id: Int) {
+ /**
+ * The exercise is actively being started, but we don't yet have sensor stability or GPS fix.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_STARTING(1),
+
+ /**
+ * The exercise is actively in-progress.
+ *
+ * Used in both of the manually started exercise and the automatic exercise detection. It's also
+ * the state when the automatic exercise detection has detected an exercise and the exercise is
+ * actively in-progress.
+ */
+ ACTIVE(2),
+
+ /**
+ * The session is being paused by the user. Sensors are actively being flushed.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_PAUSING(3),
+
+ /**
+ * The session has been paused by the user. Sensors have completed flushing.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_PAUSED(4),
+
+ /**
+ * The session is being paused by auto-pause. Sensors are actively being flushed.
+ *
+ * Used only in the manually started exercise.
+ */
+ AUTO_PAUSING(5),
+
+ /**
+ * The session has been automatically paused. Sensors have completed flushing.
+ *
+ * Used only in the manually started exercise.
+ */
+ AUTO_PAUSED(6),
+
+ /**
+ * The session is being resumed by the user.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_RESUMING(7),
+
+ /**
+ * The session is being automatically resumed.
+ *
+ * Used only in the manually started exercise.
+ */
+ AUTO_RESUMING(8),
+
+ /**
+ * The exercise is being ended by the user. Sensors are actively being flushed.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_ENDING(9),
+
+ /**
+ * The exercise has been ended by the user. No new metrics will be exported and a final summary
+ * should be provided to the client.
+ *
+ * Used only in the manually started exercise.
+ */
+ USER_ENDED(10),
+
+ /**
+ * The exercise is being automatically ended due to a lack of exercise updates being received by
+ * the user. Sensors are actively being flushed.
+ *
+ * Used only in the manually started exercise.
+ */
+ AUTO_ENDING(11),
+
+ /**
+ * The exercise has been automatically ended due to a lack of exercise updates being received by
+ * the user. No new metrics will be exported and a final summary should be provided to the
+ * client.
+ *
+ * Used only in the manually started exercise.
+ */
+ AUTO_ENDED(12),
+
+ /**
+ * The exercise is being ended because it has been superseded by a new exercise being started by
+ * another client. Sensors are actively being flushed.
+ *
+ * Used in both of the manually started exercise and the automatic exercise detection.
+ */
+ TERMINATING(13),
+
+ /**
+ * The exercise has been ended because it was superseded by a new exercise being started by
+ * another client. No new metrics will be exported and a final summary should be provided to the
+ * client.
+ *
+ * Used in both of the manually started exercise and the automatic exercise detection.
+ */
+ TERMINATED(14);
+
+ /**
+ * Returns true if this [ExerciseState] corresponds to one of the paused states and false
+ * otherwise. This method returns false if the exercise has ended, to check whether it has ended
+ * call [isEnded].
+ */
+ public val isPaused: Boolean
+ get() = PAUSED_STATES.contains(this)
+
+ /**
+ * Returns true if this [ExerciseState] corresponds to one of the resuming states and false
+ * otherwise. This method returns false if the exercise has ended, to check whether it has ended
+ * call [isEnded].
+ */
+ public val isResuming: Boolean
+ get() = RESUMING_STATES.contains(this)
+
+ /**
+ * Returns true if this [ExerciseState] corresponds to one of the ended states and false
+ * otherwise. This method returns false if the exercise has been paused, to check whether it is
+ * currently paused call [isPaused].
+ */
+ public val isEnded: Boolean
+ get() = ENDED_STATES.contains(this)
+
+ public companion object {
+ private val RESUMING_STATES = setOf(USER_RESUMING, AUTO_RESUMING)
+ private val PAUSED_STATES = setOf(USER_PAUSED, AUTO_PAUSED)
+ private val ENDED_STATES = setOf(USER_ENDED, AUTO_ENDED, TERMINATED)
+
+ @JvmStatic public fun fromId(id: Int): ExerciseState? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt
new file mode 100644
index 0000000..2a1279b
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+/** Status representing if an exercise is being tracked and which app owns the exercise. */
+public enum class ExerciseTrackedStatus(public val id: Int) {
+ OTHER_APP_IN_PROGRESS(1),
+ OWNED_EXERCISE_IN_PROGRESS(2),
+ NO_EXERCISE_IN_PROGRESS(3);
+
+ public companion object {
+ @JvmStatic
+ public fun fromId(id: Int): ExerciseTrackedStatus? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt
new file mode 100644
index 0000000..d385c67
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+// TODO(b/185276729): Keep track of values separately to maintain alphabetical order
+// once values are locked in
+/** Exercise type used to configure sensors and algorithms. */
+public enum class ExerciseType(
+ /** Returns a unique identifier of for the [ExerciseType], as an `int`. */
+ public val id: Int
+) {
+ /** The current exercise type of the user is unknown or not set. */
+ UNKNOWN(0),
+ BACK_EXTENSION(1),
+ BADMINTON(2),
+ BARBELL_SHOULDER_PRESS(3),
+ BASEBALL(4),
+ BASKETBALL(5),
+ BENCH_PRESS(6),
+ BENCH_SIT_UP(7),
+ BIKING(8),
+ BIKING_STATIONARY(9),
+ BOOT_CAMP(10),
+ BOXING(11),
+ BURPEE(12),
+
+ /** (E.g., push ups, sit ups, pull-ups, jumping jacks). */
+ CALISTHENICS(13),
+ CRICKET(14),
+ CRUNCH(15),
+ DANCING(16),
+ DEADLIFT(17),
+ DUMBBELL_CURL_RIGHT_ARM(18),
+ DUMBBELL_CURL_LEFT_ARM(19),
+ DUMBBELL_FRONT_RAISE(20),
+ DUMBBELL_LATERAL_RAISE(21),
+ DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM(22),
+ DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM(23),
+ DUMBBELL_TRICEPS_EXTENSION_TWO_ARM(24),
+ ELLIPTICAL(25),
+ EXERCISE_CLASS(26),
+ FENCING(27),
+ FRISBEE_DISC(28),
+ FOOTBALL_AMERICAN(29),
+ FOOTBALL_AUSTRALIAN(30),
+ GOLF(31),
+ GUIDED_BREATHING(32),
+ GYNMASTICS(33),
+ HANDBALL(34),
+ HIGH_INTENSITY_INTERVAL_TRAINING(35),
+ HIKING(36),
+ ICE_HOCKEY(37),
+ ICE_SKATING(38),
+ JUMP_ROPE(39),
+ JUMPING_JACK(40),
+ LAT_PULL_DOWN(41),
+ LUNGE(42),
+ MARTIAL_ARTS(43),
+ MEDITATION(44),
+ PADDLING(45),
+ PARA_GLIDING(46),
+ PILATES(47),
+ PLANK(48),
+ RACQUETBALL(49),
+ ROCK_CLIMBING(50),
+ ROLLER_HOCKEY(51),
+ ROWING(52),
+ ROWING_MACHINE(53),
+ RUNNING(54),
+ RUNNING_TREADMILL(55),
+ RUGBY(56),
+ SAILING(57),
+ SCUBA_DIVING(58),
+ SKATING(59),
+ SKIING(60),
+ SNOWBOARDING(61),
+ SNOWSHOEING(62),
+ SOCCER(63),
+ SOFTBALL(64),
+ SQUASH(65),
+ SQUAT(66),
+ STAIR_CLIMBING(67),
+ STAIR_CLIMBING_MACHINE(68),
+ STRENGTH_TRAINING(69),
+ STRETCHING(70),
+ SURFING(71),
+ SWIMMING_OPEN_WATER(72),
+ SWIMMING_POOL(73),
+ TABLE_TENNIS(74),
+ TENNIS(75),
+ VOLLEYBALL(76),
+ WALKING(77),
+ WATER_POLO(78),
+ WEIGHTLIFTING(79),
+ WORKOUT_INDOOR(80),
+ WORKOUT_OUTDOOR(81),
+ YOGA(82);
+
+ public companion object {
+ private val IDS = initialize()
+ private fun initialize(): Map<Int, ExerciseType> {
+ val map = mutableMapOf<Int, ExerciseType>()
+ for (exerciseType in values()) {
+ map.put(exerciseType.id, exerciseType)
+ }
+ return map
+ }
+
+ /**
+ * Returns the [ExerciseType] based on its unique `id`.
+ *
+ * If the `id` doesn't map to an particular [ExerciseType], then [ExerciseType.UNKNOWN] is
+ * returned by default.
+ */
+ @JvmStatic
+ public fun fromId(id: Int): ExerciseType {
+ val exerciseType = IDS[id]
+ return exerciseType ?: UNKNOWN
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt
new file mode 100644
index 0000000..915d490a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+
+// TODO(b/179756269): add aggregated metrics.
+/** Contains the latest updated state and metrics for the current exercise. */
+public data class ExerciseUpdate(
+ /** Returns the current status of the exercise. */
+ val state: ExerciseState,
+
+ /** Returns the time at which the exercise was started. */
+ val startTime: Instant,
+
+ /**
+ * Returns the total elapsed time for which the exercise has been active, i.e. started but not
+ * paused.
+ */
+ val activeDuration: Duration,
+
+ /**
+ * Returns the list of latest [DataPoint] for each metric keyed by data type name. This allows a
+ * client to easily query for the "current" values of each metric since last call. There will
+ * only be one value for an Aggregated DataType.
+ */
+ val latestMetrics: Map<DataType, List<DataPoint>>,
+
+ /**
+ * Returns the latest `#ONE_TIME_GOAL` [ExerciseGoal] s that have been achieved. `#MILESTONE`
+ * [ExerciseGoal] s will be returned via `#getLatestMilestoneMarkerSummaries` below.
+ */
+ val latestAchievedGoals: Set<AchievedExerciseGoal>,
+
+ /** Returns the latest [MilestoneMarkerSummary] s. */
+ val latestMilestoneMarkerSummaries: Set<MilestoneMarkerSummary>,
+
+ /**
+ * Returns the [ExerciseConfig] used by the exercise when the [ExerciseUpdate] was dispatched,
+ * or `null` if there isn't any manually started exercise.
+ */
+ val exerciseConfig: ExerciseConfig?,
+
+ /**
+ * Returns the [AutoExerciseConfig] associated for the automatic exercise detection or `null` if
+ * the automatic exercise detection is stopped.
+ */
+ val autoExerciseConfig: AutoExerciseConfig?,
+
+ /**
+ * Returns the [ExerciseType] instance detected by the automatic exercise detection, otherwise
+ * [ExerciseType.UNKNOWN].
+ *
+ * It's only relevant when the automatic exercise detection is on.
+ */
+ val autoExerciseDetected: ExerciseType?,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(state.id)
+ dest.writeLong(startTime.toEpochMilli())
+ dest.writeLong(activeDuration.toMillis())
+
+ dest.writeInt(latestMetrics.size)
+ for ((dataType, dataPoints) in latestMetrics) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeInt(dataPoints.size)
+ dest.writeTypedArray(dataPoints.toTypedArray(), flags)
+ }
+
+ dest.writeInt(latestAchievedGoals.size)
+ dest.writeTypedArray(latestAchievedGoals.toTypedArray(), flags)
+
+ dest.writeInt(latestMilestoneMarkerSummaries.size)
+ dest.writeTypedArray(latestMilestoneMarkerSummaries.toTypedArray(), flags)
+
+ dest.writeParcelable(exerciseConfig, flags)
+ dest.writeParcelable(autoExerciseConfig, flags)
+ dest.writeInt(autoExerciseDetected?.id ?: -1)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseUpdate> =
+ object : Parcelable.Creator<ExerciseUpdate> {
+ override fun createFromParcel(source: Parcel): ExerciseUpdate? {
+ val exerciseState = ExerciseState.fromId(source.readInt()) ?: return null
+ val startTime = Instant.ofEpochMilli(source.readLong())
+ val activeDuration = Duration.ofMillis(source.readLong())
+
+ val numMetrics = source.readInt()
+ val latestMetrics = HashMap<DataType, List<DataPoint>>()
+ repeat(numMetrics) {
+ val dataType: DataType =
+ source.readParcelable(DataType::class.java.classLoader) ?: return null
+ val dataPointsArray = Array<DataPoint?>(source.readInt()) { null }
+ source.readTypedArray(dataPointsArray, DataPoint.CREATOR)
+ latestMetrics[dataType] = dataPointsArray.filterNotNull().toList()
+ }
+
+ val latestAchievedGoalsArray =
+ Array<AchievedExerciseGoal?>(source.readInt()) { null }
+ source.readTypedArray(latestAchievedGoalsArray, AchievedExerciseGoal.CREATOR)
+
+ val latestMilestoneMarkerSummariesArray =
+ Array<MilestoneMarkerSummary?>(source.readInt()) { null }
+ source.readTypedArray(
+ latestMilestoneMarkerSummariesArray,
+ MilestoneMarkerSummary.CREATOR
+ )
+
+ val exerciseConfig: ExerciseConfig? =
+ source.readParcelable(ExerciseConfig::class.java.classLoader)
+ val autoExerciseConfig: AutoExerciseConfig? =
+ source.readParcelable(AutoExerciseConfig::class.java.classLoader)
+ val autoExerciseDetectedId = source.readInt()
+ val autoExerciseDetected =
+ if (autoExerciseDetectedId == -1) null
+ else ExerciseType.fromId(autoExerciseDetectedId)
+
+ return ExerciseUpdate(
+ exerciseState,
+ startTime,
+ activeDuration,
+ latestMetrics,
+ latestAchievedGoalsArray.filterNotNull().toSet(),
+ latestMilestoneMarkerSummariesArray.filterNotNull().toSet(),
+ exerciseConfig,
+ autoExerciseConfig,
+ autoExerciseDetected
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseUpdate?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+
+ /** Supported Exercise session types. */
+ public enum class ExerciseSessionType {
+ /**
+ * The exercise is a manually started exercise session.
+ *
+ * [ExerciseUpdate.getExerciseConfig] returns non-null config with this type.
+ */
+ MANUALLY_STARTED_EXERCISE,
+
+ /**
+ * The exercise is an automatic exercise detection session.
+ *
+ * [ExerciseUpdate.getAutoExerciseConfig] returns non-null config with this type.
+ */
+ AUTO_EXERCISE_DETECTION,
+ }
+
+ /** Returns the current [ExerciseSessionType] WHS is carrying out. */
+ public fun getExerciseSessionType(): ExerciseSessionType {
+ return if (autoExerciseConfig != null) {
+ ExerciseSessionType.AUTO_EXERCISE_DETECTION
+ } else {
+ ExerciseSessionType.MANUALLY_STARTED_EXERCISE
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt
new file mode 100644
index 0000000..bffc1ec
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A place holder class that represents the capabilities of the WHS measuring client on the device.
+ */
+public data class MeasureCapabilities(
+ /**
+ * Set of supported [DataType] s for measure capture on this device.
+ *
+ * Some data types are not available for measurement; this is typically used to measure health
+ * data (e.g. HR).
+ */
+ val supportedDataTypesMeasure: Set<DataType>,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeTypedList(supportedDataTypesMeasure.toList())
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<MeasureCapabilities> =
+ object : Parcelable.Creator<MeasureCapabilities> {
+ override fun createFromParcel(source: Parcel): MeasureCapabilities? {
+ val measureDataTypes = ArrayList<DataType>()
+ source.readTypedList(measureDataTypes, DataType.CREATOR)
+ return MeasureCapabilities(
+ measureDataTypes.toSet(),
+ )
+ }
+
+ override fun newArray(size: Int): Array<MeasureCapabilities?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt
new file mode 100644
index 0000000..98a340c
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+
+/**
+ * The summary of metrics and state from the previously achieved milestone marker [ExerciseGoal].
+ */
+public data class MilestoneMarkerSummary(
+ /** Returns the time at which this milestone marker started being tracked. */
+ val startTime: Instant,
+
+ /** Returns the time at which this milestone marker was reached. */
+ val endTime: Instant,
+
+ /**
+ * Returns the total elapsed time for which the exercise was active during this milestone, i.e.
+ * started but not paused.
+ */
+ val activeDuration: Duration,
+
+ /** The [AchievedExerciseGoal] that triggered this milestone summary. */
+ val achievedGoal: AchievedExerciseGoal,
+
+ /**
+ * Returns the [DataPoint] for each aggregated metric keyed by [DataType] tracked between
+ * [startTime] and [endTime] i.e. during the duration of this milestone.
+ */
+ val summaryMetrics: Map<DataType, DataPoint>,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeLong(startTime.toEpochMilli())
+ dest.writeLong(endTime.toEpochMilli())
+ dest.writeLong(activeDuration.toMillis())
+ dest.writeParcelable(achievedGoal, flags)
+
+ dest.writeInt(summaryMetrics.size)
+ for ((dataType, dataPoint) in summaryMetrics) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeParcelable(dataPoint, flags)
+ }
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<MilestoneMarkerSummary> =
+ object : Parcelable.Creator<MilestoneMarkerSummary> {
+ override fun createFromParcel(source: Parcel): MilestoneMarkerSummary? {
+ val startTime = Instant.ofEpochMilli(source.readLong())
+ val endTime = Instant.ofEpochMilli(source.readLong())
+ val activeDuration = Duration.ofMillis(source.readLong())
+ val achievedGoal: AchievedExerciseGoal =
+ source.readParcelable(AchievedExerciseGoal::class.java.classLoader)
+ ?: return null
+
+ val summaryMetrics = HashMap<DataType, DataPoint>()
+ repeat(source.readInt()) {
+ val dataType: DataType =
+ source.readParcelable(DataType::class.java.classLoader) ?: return null
+ val dataPoint: DataPoint =
+ source.readParcelable(DataPoint::class.java.classLoader) ?: return null
+ summaryMetrics[dataType] = dataPoint
+ }
+
+ return MilestoneMarkerSummary(
+ startTime = startTime,
+ endTime = endTime,
+ activeDuration = activeDuration,
+ achievedGoal = achievedGoal,
+ summaryMetrics = summaryMetrics
+ )
+ }
+
+ override fun newArray(size: Int): Array<MilestoneMarkerSummary?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveActivityState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveActivityState.kt
new file mode 100644
index 0000000..0013973
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveActivityState.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.content.Intent
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_EXERCISE
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_INACTIVE
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_PASSIVE
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_UNKNOWN
+import java.time.Instant
+
+/**
+ * Represents state from Passive tracking.
+ *
+ * Provides [DataPoint] s associated with the Passive tracking, in addition to data related to the
+ * user's [UserActivityState].
+ */
+public data class PassiveActivityState(
+ /** List of [DataPoint] s from Passive tracking. */
+ val dataPoints: List<DataPoint>,
+
+ /** The [UserActivityState] of the user from Passive tracking. */
+ val userActivityState: UserActivityState,
+
+ /**
+ * The [ExerciseType] of the user for a [UserActivityState.USER_ACTIVITY_EXERCISE] state, and
+ * `null` for other [UserActivityState] s.
+ */
+ val exerciseType: ExerciseType?,
+
+ /** The time at which the current state took effect. */
+ val stateChangeTime: Instant,
+) : Parcelable {
+
+ /**
+ * Puts the state as an extra into a given [Intent]. The state can then be obtained from the
+ * intent via [PassiveActivityState.fromIntent].
+ */
+ public fun putToIntent(intent: Intent) {
+ intent.putExtra(EXTRA_KEY, this)
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeInt(dataPoints.size)
+ dest.writeTypedArray(dataPoints.toTypedArray(), flags)
+
+ dest.writeInt(userActivityState.id)
+ dest.writeInt(exerciseType?.id ?: -1)
+ dest.writeLong(stateChangeTime.toEpochMilli())
+ }
+
+ public companion object {
+ private const val EXTRA_KEY = "whs.passive_activity_state"
+
+ @JvmField
+ public val CREATOR: Parcelable.Creator<PassiveActivityState> =
+ object : Parcelable.Creator<PassiveActivityState> {
+ override fun createFromParcel(source: Parcel): PassiveActivityState? {
+ val dataPointsArray: Array<DataPoint?> = arrayOfNulls(source.readInt())
+ source.readTypedArray(dataPointsArray, DataPoint.CREATOR)
+
+ val activityState = UserActivityState.fromId(source.readInt()) ?: return null
+ val exerciseTypeId = source.readInt()
+ val exerciseType =
+ if (exerciseTypeId == -1) null else ExerciseType.fromId(exerciseTypeId)
+ val time = Instant.ofEpochMilli(source.readLong())
+
+ return PassiveActivityState(
+ dataPointsArray.filterNotNull().toList(),
+ activityState,
+ exerciseType,
+ time
+ )
+ }
+
+ override fun newArray(size: Int): Array<PassiveActivityState?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ /** Creates a [PassiveActivityState] for [USER_ACTIVITY_UNKNOWN]. */
+ @JvmStatic
+ public fun createUnknownTypeState(
+ dataPoints: List<DataPoint>,
+ stateChangeTime: Instant
+ ): PassiveActivityState =
+ PassiveActivityState(
+ dataPoints,
+ USER_ACTIVITY_UNKNOWN,
+ exerciseType = null,
+ stateChangeTime
+ )
+
+ /** Creates a [PassiveActivityState] for [USER_ACTIVITY_EXERCISE]. */
+ @JvmStatic
+ public fun createActiveExerciseState(
+ dataPoints: List<DataPoint>,
+ exerciseType: ExerciseType,
+ stateChangeTime: Instant
+ ): PassiveActivityState =
+ PassiveActivityState(dataPoints, USER_ACTIVITY_EXERCISE, exerciseType, stateChangeTime)
+
+ /** Creates a [PassiveActivityState] for [USER_ACTIVITY_PASSIVE]. */
+ @JvmStatic
+ public fun createPassiveActivityState(
+ dataPoints: List<DataPoint>,
+ stateChangeTime: Instant
+ ): PassiveActivityState =
+ PassiveActivityState(
+ dataPoints,
+ USER_ACTIVITY_PASSIVE,
+ exerciseType = null,
+ stateChangeTime
+ )
+
+ /**
+ * Creates a [PassiveActivityState] from an [Intent]. Returns null if no
+ * [PassiveActivityState] is stored in the given intent.
+ */
+ @JvmStatic
+ public fun fromIntent(intent: Intent): PassiveActivityState? =
+ intent.getParcelableExtra(EXTRA_KEY)
+
+ /** Creates a [PassiveActivityState] for [USER_ACTIVITY_INACTIVE]. */
+ @JvmStatic
+ public fun createInactiveState(
+ dataPoints: List<DataPoint>,
+ stateChangeTime: Instant
+ ): PassiveActivityState =
+ PassiveActivityState(
+ dataPoints,
+ USER_ACTIVITY_INACTIVE,
+ exerciseType = null,
+ stateChangeTime
+ )
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt
new file mode 100644
index 0000000..b715fda
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A place holder class that represents the capabilities of the WHS passive monitoring client on the
+ * device.
+ */
+public data class PassiveMonitoringCapabilities(
+
+ /**
+ * Set of supported [DataType] s for background capture on this device.
+ *
+ * Some data types are only available during exercise (e.g. location) or for measurements.
+ */
+ val supportedDataTypesPassiveMonitoring: Set<DataType>,
+
+ /** Set of supported [DataType] s for event callbacks on this device. */
+ val supportedDataTypesEvents: Set<DataType>,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeTypedList(supportedDataTypesPassiveMonitoring.toList())
+ dest.writeTypedList(supportedDataTypesEvents.toList())
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<PassiveMonitoringCapabilities> =
+ object : Parcelable.Creator<PassiveMonitoringCapabilities> {
+ override fun createFromParcel(source: Parcel): PassiveMonitoringCapabilities? {
+ val passiveMonitoringDataTypes = ArrayList<DataType>()
+ source.readTypedList(passiveMonitoringDataTypes, DataType.CREATOR)
+ val eventDataTypes = ArrayList<DataType>()
+ source.readTypedList(eventDataTypes, DataType.CREATOR)
+ return PassiveMonitoringCapabilities(
+ passiveMonitoringDataTypes.toSet(),
+ eventDataTypes.toSet()
+ )
+ }
+
+ override fun newArray(size: Int): Array<PassiveMonitoringCapabilities?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt
new file mode 100644
index 0000000..5ba779e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+/** Types of user activity states. */
+public enum class UserActivityState(public val id: Int) {
+ USER_ACTIVITY_UNKNOWN(0),
+ USER_ACTIVITY_EXERCISE(1),
+ USER_ACTIVITY_PASSIVE(2),
+ USER_ACTIVITY_INACTIVE(3);
+
+ public companion object {
+ @JvmStatic
+ public fun fromId(id: Int): UserActivityState? = values().firstOrNull { it.id == id }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/Value.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/Value.kt
new file mode 100644
index 0000000..5ae780d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/Value.kt
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** A [Parcelable] wrapper that can hold a value of a specified type. */
+@Suppress("DataClassPrivateConstructor")
+public data class Value
+private constructor(
+ // TODO(b/175054913): Investigate using a AutoOneOf instead.
+ val format: Int,
+ val doubleList: List<Double>,
+ val longValue: Long,
+) : Parcelable {
+
+ /**
+ * Returns this [Value] represented as an `long`.
+ *
+ * @throws IllegalStateException if [isLong] is `false`
+ */
+ public fun asLong(): Long {
+ check(isLong) { "Attempted to read value as long, but value is not of type long" }
+ return longValue
+ }
+
+ /**
+ * Returns this [Value] represented as an `boolean`.
+ *
+ * @throws IllegalStateException if [isBoolean] is `false`
+ */
+ public fun asBoolean(): Boolean {
+ check(isBoolean) { "Attempted to read value as boolean, but value is not of type boolean" }
+ return longValue != 0L
+ }
+
+ /**
+ * Returns this [Value] represented as a `double`.
+ *
+ * @throws IllegalStateException if [isDouble] is `false`
+ */
+ public fun asDouble(): Double {
+ check(isDouble) { "Attempted to read value as double, but value is not of type double" }
+ return doubleList[0]
+ }
+
+ /**
+ * Returns this [Value] represented as a `double[]`.
+ *
+ * @throws IllegalStateException if [isDoubleArray] is `false`
+ */
+ public fun asDoubleArray(): DoubleArray {
+ check(isDoubleArray) {
+ "Attempted to read value as double array, but value is not correct type"
+ }
+ return doubleList.toDoubleArray()
+ }
+
+ /** Whether or not this [Value] can be represented as an `long`. */
+ public val isLong: Boolean
+ get() = format == FORMAT_LONG
+
+ /** Whether or not this [Value] can be represented as an `boolean`. */
+ public val isBoolean: Boolean
+ get() = format == FORMAT_BOOLEAN
+
+ /** Whether or not this [Value] can be represented as a `double`. */
+ public val isDouble: Boolean
+ get() = format == FORMAT_DOUBLE
+
+ /** Whether or not this [Value] can be represented as a `double[]`. */
+ public val isDoubleArray: Boolean
+ get() = format == FORMAT_DOUBLE_ARRAY
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ /**
+ * Writes the value of this object to [dest].
+ *
+ * @throws IllegalStateException if [format] is invalid
+ */
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ val format = format
+ dest.writeInt(format)
+ when (format) {
+ FORMAT_BOOLEAN, FORMAT_LONG -> {
+ dest.writeLong(longValue)
+ return
+ }
+ FORMAT_DOUBLE -> {
+ dest.writeDouble(asDouble())
+ return
+ }
+ FORMAT_DOUBLE_ARRAY -> {
+ val doubleArray = asDoubleArray()
+ dest.writeInt(doubleArray.size)
+ dest.writeDoubleArray(doubleArray)
+ return
+ }
+ else -> {}
+ }
+ throw IllegalStateException(String.format("Unexpected format: %s", format))
+ }
+
+ public companion object {
+ /** The format used for a [Value] represented as a `double`. */
+ public const val FORMAT_DOUBLE: Int = 1
+
+ /** The format used for a [Value] represented as an `long`. */
+ public const val FORMAT_LONG: Int = 2
+
+ /** The format used for a [Value] represented as an `boolean`. */
+ public const val FORMAT_BOOLEAN: Int = 4
+
+ /** The format used for a [Value] represented as a `double[]`. */
+ public const val FORMAT_DOUBLE_ARRAY: Int = 3
+
+ @JvmField
+ public val CREATOR: Parcelable.Creator<Value> =
+ object : Parcelable.Creator<Value> {
+ override fun createFromParcel(parcel: Parcel): Value {
+ val format = parcel.readInt()
+ when (format) {
+ FORMAT_BOOLEAN, FORMAT_LONG ->
+ return Value(format, listOf(), parcel.readLong())
+ FORMAT_DOUBLE ->
+ return Value(format, listOf(parcel.readDouble()), /* longValue= */ 0)
+ FORMAT_DOUBLE_ARRAY -> {
+ val doubleArray = DoubleArray(parcel.readInt())
+ parcel.readDoubleArray(doubleArray)
+ return Value(format, doubleArray.toList(), /* longValue= */ 0)
+ }
+ else -> {}
+ }
+ throw IllegalStateException(String.format("Unexpected format: %s", format))
+ }
+
+ override fun newArray(size: Int): Array<Value?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ /** Creates a [Value] that represents a `long`. */
+ @JvmStatic
+ public fun ofLong(value: Long): Value {
+ return Value(FORMAT_LONG, listOf(), value)
+ }
+
+ /** Creates a [Value] that represents an `boolean`. */
+ @JvmStatic
+ public fun ofBoolean(value: Boolean): Value {
+ return Value(FORMAT_BOOLEAN, listOf(), if (value) 1 else 0)
+ }
+
+ /** Creates a [Value] that represents a `double`. */
+ @JvmStatic
+ public fun ofDouble(value: Double): Value {
+ return Value(FORMAT_DOUBLE, listOf(value), longValue = 0)
+ }
+
+ /** Creates a [Value] that represents a `double[]`. */
+ @JvmStatic
+ public fun ofDoubleArray(vararg doubleArray: Double): Value {
+ return Value(FORMAT_DOUBLE_ARRAY, doubleArray.toList(), longValue = 0)
+ }
+
+ /**
+ * Compares two [Value] s based on their representation.
+ *
+ * @throws IllegalStateException if `first` and `second` do not share the same format or are
+ * represented as a `double[]`
+ */
+ internal fun compare(first: Value, second: Value): Int {
+ check(first.format == second.format) {
+ "Attempted to compare Values with different formats"
+ }
+ when (first.format) {
+ FORMAT_LONG -> return first.longValue.compareTo(second.longValue)
+ FORMAT_BOOLEAN -> return first.asBoolean().compareTo(second.asBoolean())
+ FORMAT_DOUBLE -> return first.doubleList[0].compareTo(second.doubleList[0])
+ FORMAT_DOUBLE_ARRAY ->
+ throw IllegalStateException(
+ "Attempted to compare Values with invalid format (double array)"
+ )
+ else -> {}
+ }
+ throw IllegalStateException(String.format("Unexpected format: %s", first.format))
+ }
+
+ /**
+ * Adds two [Value] s based on their representation.
+ *
+ * @throws IllegalStateException if `first` and `second` do not share the same format or are
+ * represented as a `double[]` or `boolean`
+ */
+ @JvmStatic
+ public fun sum(first: Value, second: Value): Value {
+ require(first.format == second.format) {
+ "Attempted to add Values with different formats"
+ }
+ when (first.format) {
+ FORMAT_LONG -> return ofLong(first.asLong() + second.asLong())
+ FORMAT_DOUBLE -> return ofDouble(first.asDouble() + second.asDouble())
+ FORMAT_BOOLEAN ->
+ throw IllegalStateException(
+ "Attempted to add Values with invalid format (boolean)"
+ )
+ FORMAT_DOUBLE_ARRAY ->
+ throw IllegalStateException(
+ "Attempted to add Values with invalid format (double array)"
+ )
+ else -> {}
+ }
+ throw IllegalStateException(String.format("Unexpected format: %s", first.format))
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/event/Event.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/event/Event.kt
new file mode 100644
index 0000000..5e28beb
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/event/Event.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.data.event
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataPoint
+import androidx.health.services.client.data.DataTypeCondition
+
+/** Defines an event that will be triggered when the specified condition is met. */
+public data class Event(
+ /** [DataTypeCondition] which must be met for the event to be triggered. */
+ val dataTypeCondition: DataTypeCondition,
+ val triggerType: TriggerType,
+) : Parcelable {
+
+ /** Whether or not repeated events should be triggered. */
+ public enum class TriggerType(public val id: Int) {
+ /** The event will trigger the first time the specified conditions are met. */
+ ONCE(1),
+
+ /** The event will trigger each time the specified conditions *become* met. */
+ REPEATED(2);
+
+ public companion object {
+ @JvmStatic
+ public fun fromId(id: Int): TriggerType? = values().firstOrNull { it.id == id }
+ }
+ }
+
+ /**
+ * Does the provided [DataPoint] satisfy the event condition.
+ *
+ * @throws IllegalArgumentException if the provided data point is not of the same data type as
+ * the condition itself.
+ */
+ public fun isTriggered(dataPoint: DataPoint): Boolean {
+ return dataTypeCondition.isSatisfied(dataPoint)
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(dataTypeCondition, flags)
+ dest.writeInt(triggerType.id)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<Event> =
+ object : Parcelable.Creator<Event> {
+ override fun createFromParcel(source: Parcel): Event? {
+ return Event(
+ source.readParcelable(DataTypeCondition::class.java.classLoader)
+ ?: return null,
+ TriggerType.fromId(source.readInt()) ?: return null
+ )
+ }
+
+ override fun newArray(size: Int): Array<Event?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseIpcClient.kt
new file mode 100644
index 0000000..7ab0e8a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseIpcClient.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service to make Exercise API calls.
+ *
+ * @hide
+ */
+public class ExerciseIpcClient internal constructor(connectionManager: ConnectionManager) :
+ Client(
+ CLIENT_CONFIGURATION,
+ connectionManager,
+ VersionGetter { binder: IBinder -> getServiceInterface(binder).apiVersion }
+ ) {
+
+ public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+ return super.execute(operation)
+ }
+
+ public override fun <T> registerListener(
+ listenerKey: ListenerKey,
+ registerListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.registerListener(listenerKey, registerListenerOperation)
+ }
+
+ public override fun <T> unregisterListener(
+ listenerKey: ListenerKey,
+ unregisterListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.unregisterListener(listenerKey, unregisterListenerOperation)
+ }
+
+ public companion object {
+ public const val SERVICE_BIND_ACTION: String =
+ "com.google.android.wearable.healthservices.ExerciseClient"
+ private const val CLIENT = "HealthServicesExerciseClient"
+ private const val SERVICE_PACKAGE_NAME = "com.google.android.wearable.healthservices"
+ private val CLIENT_CONFIGURATION =
+ ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+ @JvmStatic
+ internal fun getServiceInterface(binder: IBinder): IExerciseApiService {
+ return IExerciseApiService.Stub.asInterface(binder)
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
new file mode 100644
index 0000000..d48bee2
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import androidx.annotation.GuardedBy
+import androidx.health.services.client.ExerciseUpdateListener
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import androidx.health.services.client.impl.response.ExerciseLapSummaryResponse
+import androidx.health.services.client.impl.response.ExerciseUpdateResponse
+import java.util.HashMap
+import java.util.concurrent.Executor
+
+/**
+ * A stub implementation for IExerciseUpdateListener.
+ *
+ * @hide
+ */
+public class ExerciseUpdateListenerStub
+private constructor(private val listener: ExerciseUpdateListener, private val executor: Executor) :
+ IExerciseUpdateListener.Stub() {
+
+ public val listenerKey: ListenerKey = ListenerKey(listener)
+
+ override fun onExerciseUpdate(response: ExerciseUpdateResponse) {
+ executor.execute { listener.onExerciseUpdate(response.exerciseUpdate) }
+ }
+
+ override fun onLapSummary(response: ExerciseLapSummaryResponse) {
+ executor.execute { listener.onLapSummary(response.exerciseLapSummary) }
+ }
+
+ /**
+ * A class that stores unique active instances of [ExerciseUpdateListener] to ensure same binder
+ * object is passed by framework to service side of the IPC.
+ */
+ public class ExerciseUpdateListenerCache private constructor() {
+ @GuardedBy("this")
+ private val listeners: MutableMap<ExerciseUpdateListener, ExerciseUpdateListenerStub> =
+ HashMap()
+
+ @Synchronized
+ public fun getOrCreate(
+ listener: ExerciseUpdateListener,
+ executor: Executor
+ ): ExerciseUpdateListenerStub {
+ return listeners.getOrPut(listener) { ExerciseUpdateListenerStub(listener, executor) }
+ }
+
+ @Synchronized
+ public fun remove(
+ exerciseUpdateListener: ExerciseUpdateListener
+ ): ExerciseUpdateListenerStub? {
+ return listeners.remove(exerciseUpdateListener)
+ }
+
+ public companion object {
+ @JvmField
+ public val INSTANCE: ExerciseUpdateListenerCache = ExerciseUpdateListenerCache()
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/HealthServicesIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/HealthServicesIpcClient.kt
new file mode 100644
index 0000000..f46fda7
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/HealthServicesIpcClient.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service.
+ *
+ * @hide
+ */
+public class HealthServicesIpcClient internal constructor(connectionManager: ConnectionManager) :
+ Client(
+ CLIENT_CONFIGURATION,
+ connectionManager,
+ VersionGetter { binder -> getServiceInterface(binder).apiVersion }
+ ) {
+
+ public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+ return super.execute(operation)
+ }
+
+ public override fun <T> registerListener(
+ listenerKey: ListenerKey,
+ registerListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.registerListener(listenerKey, registerListenerOperation)
+ }
+
+ public override fun <T> unregisterListener(
+ listenerKey: ListenerKey,
+ unregisterListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.unregisterListener(listenerKey, unregisterListenerOperation)
+ }
+
+ public companion object {
+ private const val CLIENT = "HealthServicesClient"
+ private const val SERVICE_PACKAGE_NAME = "com.google.android.wearable.healthservices"
+ public const val SERVICE_BIND_ACTION: String =
+ "com.google.android.wearable.healthservices.HealthServicesClient"
+ private val CLIENT_CONFIGURATION =
+ ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+ @JvmStatic
+ internal fun getServiceInterface(binder: IBinder): IHealthServicesApiService {
+ return IHealthServicesApiService.Stub.asInterface(binder)
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt
new file mode 100644
index 0000000..476c6f6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
+import androidx.health.services.client.MeasureCallback
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import androidx.health.services.client.impl.response.AvailabilityResponse
+import androidx.health.services.client.impl.response.DataPointsResponse
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.HashMap
+import java.util.concurrent.Executor
+
+/**
+ * A stub implementation for IMeasureCallback.
+ *
+ * @hide
+ */
+public class MeasureCallbackStub
+private constructor(callbackKey: MeasureCallbackKey, private val callback: MeasureCallback) :
+ IMeasureCallback.Stub() {
+
+ public val listenerKey: ListenerKey = ListenerKey(callbackKey)
+
+ @get:VisibleForTesting
+ public var executor: Executor = MoreExecutors.directExecutor()
+ private set
+
+ override fun onAvailabilityChanged(response: AvailabilityResponse) {
+ executor.execute {
+ callback.onAvailabilityChanged(response.dataType, response.availability)
+ }
+ }
+
+ override fun onData(response: DataPointsResponse) {
+ executor.execute { callback.onData(response.dataPoints) }
+ }
+
+ /**
+ * Its important to use the same stub for registration and un-registration, to ensure same
+ * binder object is passed by framework to service side of the IPC.
+ */
+ public class MeasureCallbackCache private constructor() {
+ @GuardedBy("this")
+ private val listeners: MutableMap<MeasureCallbackKey, MeasureCallbackStub> = HashMap()
+
+ @Synchronized
+ public fun getOrCreate(
+ dataType: DataType,
+ measureCallback: MeasureCallback,
+ executor: Executor
+ ): MeasureCallbackStub {
+ val callbackKey = MeasureCallbackKey(dataType, measureCallback)
+
+ // If a measure callback happens for the same datatype with same callback, pass the same
+ // stub instance, but update the executor, as executor might have changed. Its ok to
+ // register
+ // the callback once again, as on our service implementation re-registering for same
+ // datatype
+ // with same callback is a no-op.
+ var measureCallbackStub = listeners[callbackKey]
+ if (measureCallbackStub == null) {
+ measureCallbackStub = MeasureCallbackStub(callbackKey, measureCallback)
+ listeners[callbackKey] = measureCallbackStub
+ }
+ measureCallbackStub.executor = executor
+ return measureCallbackStub
+ }
+
+ @Synchronized
+ public fun remove(
+ dataType: DataType,
+ measureCallback: MeasureCallback
+ ): MeasureCallbackStub? {
+ val callbackKey = MeasureCallbackKey(dataType, measureCallback)
+ return listeners.remove(callbackKey)
+ }
+
+ public companion object {
+ @JvmField public val INSTANCE: MeasureCallbackCache = MeasureCallbackCache()
+ }
+ }
+
+ private data class MeasureCallbackKey(
+ private val dataType: DataType,
+ private val measureCallback: MeasureCallback
+ )
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureIpcClient.kt
new file mode 100644
index 0000000..e816a0a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureIpcClient.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service to make Measure API calls.
+ *
+ * @hide
+ */
+public class MeasureIpcClient internal constructor(connectionManager: ConnectionManager) :
+ Client(
+ CLIENT_CONFIGURATION,
+ connectionManager,
+ VersionGetter { binder -> getServiceInterface(binder).apiVersion }
+ ) {
+
+ public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+ return super.execute(operation)
+ }
+
+ public override fun <T> registerListener(
+ listenerKey: ListenerKey,
+ registerListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.registerListener(listenerKey, registerListenerOperation)
+ }
+
+ public override fun <T> unregisterListener(
+ listenerKey: ListenerKey,
+ unregisterListenerOperation: ServiceOperation<T>
+ ): ListenableFuture<T> {
+ return super.unregisterListener(listenerKey, unregisterListenerOperation)
+ }
+
+ public companion object {
+ public const val SERVICE_BIND_ACTION: String =
+ "com.google.android.wearable.healthservices.MeasureClient"
+ public const val CLIENT: String = "HealthServicesMeasureClient"
+ public const val SERVICE_PACKAGE_NAME: String = "com.google.android.wearable.healthservices"
+ private val CLIENT_CONFIGURATION =
+ ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+ @JvmStatic
+ internal fun getServiceInterface(binder: IBinder): IMeasureApiService {
+ return IMeasureApiService.Stub.asInterface(binder)
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringCallbackStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringCallbackStub.kt
new file mode 100644
index 0000000..700f1af
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringCallbackStub.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import androidx.health.services.client.PassiveMonitoringCallback
+import androidx.health.services.client.impl.response.PassiveActivityStateResponse
+
+/**
+ * A stub implementation for IPassiveMonitoringCallback.
+ *
+ * @hide
+ */
+internal class PassiveMonitoringCallbackStub
+internal constructor(private val callback: PassiveMonitoringCallback) :
+ IPassiveMonitoringCallback.Stub() {
+
+ override fun onPassiveActivityState(response: PassiveActivityStateResponse) {
+ callback.onPassiveActivityState(response.passiveActivityState)
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringIpcClient.kt
new file mode 100644
index 0000000..acbe468
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringIpcClient.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service to make passive monitoring
+ * API calls.
+ *
+ * @hide
+ */
+public class PassiveMonitoringIpcClient(connectionManager: ConnectionManager) :
+ Client(
+ CLIENT_CONFIGURATION,
+ connectionManager,
+ VersionGetter { binder -> getServiceInterface(binder).apiVersion }
+ ) {
+
+ public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+ return super.execute(operation)
+ }
+
+ public companion object {
+ public const val SERVICE_BIND_ACTION: String =
+ "com.google.android.wearable.healthservices.PassiveMonitoringClient"
+ private const val CLIENT = "HealthServicesPassiveMonitoringClient"
+ private const val SERVICE_PACKAGE_NAME = "com.google.android.wearable.healthservices"
+ private val CLIENT_CONFIGURATION =
+ ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+ @JvmStatic
+ internal fun getServiceInterface(binder: IBinder): IPassiveMonitoringApiService {
+ return IPassiveMonitoringApiService.Stub.asInterface(binder)
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
new file mode 100644
index 0000000..adff9c2
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import android.content.Context
+import androidx.core.content.ContextCompat
+import androidx.health.services.client.ExerciseClient
+import androidx.health.services.client.ExerciseUpdateListener
+import androidx.health.services.client.data.Capabilities
+import androidx.health.services.client.data.ExerciseConfig
+import androidx.health.services.client.data.ExerciseGoal
+import androidx.health.services.client.data.ExerciseInfo
+import androidx.health.services.client.impl.ExerciseIpcClient.Companion.getServiceInterface
+import androidx.health.services.client.impl.internal.ExerciseInfoCallback
+import androidx.health.services.client.impl.internal.StatusCallback
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.request.AutoPauseAndResumeConfigRequest
+import androidx.health.services.client.impl.request.CapabilitiesRequest
+import androidx.health.services.client.impl.request.ExerciseGoalRequest
+import androidx.health.services.client.impl.request.StartExerciseRequest
+import androidx.health.services.client.impl.response.CapabilitiesResponse
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/**
+ * [ExerciseClient] API implementation that receives data from the WHS Service.
+ *
+ * @hide
+ */
+internal class ServiceBackedExerciseClient
+private constructor(private val context: Context, connectionManager: ConnectionManager) :
+ ExerciseClient {
+
+ private val ipcClient: ExerciseIpcClient = ExerciseIpcClient(connectionManager)
+
+ override fun startExercise(configuration: ExerciseConfig): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .startExercise(
+ StartExerciseRequest(context.packageName, configuration),
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun pauseExercise(): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .pauseExercise(context.packageName, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun resumeExercise(): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .resumeExercise(context.packageName, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun endExercise(): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .endExercise(context.packageName, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun markLap(): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .markLap(context.packageName, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override val currentExerciseInfo: ListenableFuture<ExerciseInfo>
+ get() {
+ val serviceOperation =
+ ServiceOperation<ExerciseInfo> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .getCurrentExerciseInfo(
+ context.packageName,
+ ExerciseInfoCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun setUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void> {
+ return setUpdateListener(listener, ContextCompat.getMainExecutor(context))
+ }
+
+ override fun setUpdateListener(
+ listener: ExerciseUpdateListener,
+ executor: Executor
+ ): ListenableFuture<Void> {
+ val listenerStub =
+ ExerciseUpdateListenerStub.ExerciseUpdateListenerCache.INSTANCE.getOrCreate(
+ listener,
+ executor
+ )
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .setUpdateListener(
+ context.packageName,
+ listenerStub,
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.registerListener(listenerStub.listenerKey, serviceOperation)
+ }
+
+ override fun clearUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void> {
+ val listenerStub =
+ ExerciseUpdateListenerStub.ExerciseUpdateListenerCache.INSTANCE.remove(listener)
+ ?: return Futures.immediateFailedFuture(
+ IllegalArgumentException("Given listener was not added.")
+ )
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .clearUpdateListener(
+ context.packageName,
+ listenerStub,
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.unregisterListener(listenerStub.listenerKey, serviceOperation)
+ }
+
+ override fun addGoalToActiveExercise(exerciseGoal: ExerciseGoal): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .addGoalToActiveExercise(
+ ExerciseGoalRequest(context.packageName, exerciseGoal),
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun overrideAutoPauseAndResumeForActiveExercise(
+ enabled: Boolean
+ ): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .overrideAutoPauseAndResumeForActiveExercise(
+ AutoPauseAndResumeConfigRequest(context.packageName, enabled),
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override val capabilities: ListenableFuture<Capabilities>
+ get() {
+ val request = CapabilitiesRequest(context.packageName)
+ val serviceOperation =
+ ServiceOperation<CapabilitiesResponse> { binder, resultFuture ->
+ resultFuture.set(getServiceInterface(binder).getCapabilities(request))
+ }
+ return Futures.transform(
+ ipcClient.execute(serviceOperation),
+ { response -> response?.capabilities },
+ ContextCompat.getMainExecutor(context)
+ )
+ }
+
+ internal companion object {
+ @JvmStatic
+ fun getClient(context: Context): ServiceBackedExerciseClient {
+ return ServiceBackedExerciseClient(context, WhsConnectionManager.getInstance(context))
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedHealthServicesClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedHealthServicesClient.kt
new file mode 100644
index 0000000..ef052fb
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedHealthServicesClient.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import android.content.Context
+import androidx.health.services.client.ExerciseClient
+import androidx.health.services.client.HealthServicesClient
+import androidx.health.services.client.MeasureClient
+import androidx.health.services.client.PassiveMonitoringClient
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+
+/**
+ * A [HealthServicesClient] implementation.
+ *
+ * @hide
+ */
+public class ServiceBackedHealthServicesClient
+internal constructor(context: Context, connectionManager: ConnectionManager) :
+ HealthServicesClient {
+
+ private val applicationContext: Context = context.applicationContext
+ private val ipcClient: HealthServicesIpcClient = HealthServicesIpcClient(connectionManager)
+
+ public constructor(context: Context) : this(context, WhsConnectionManager.getInstance(context))
+
+ override val exerciseClient: ExerciseClient
+ get() = ServiceBackedExerciseClient.getClient(applicationContext)
+ override val passiveMonitoringClient: PassiveMonitoringClient
+ get() = ServiceBackedPassiveMonitoringClient(applicationContext)
+ override val measureClient: MeasureClient
+ get() = ServiceBackedMeasureClient.getClient(applicationContext)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt
new file mode 100644
index 0000000..51e21ed
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.ContextCompat
+import androidx.health.services.client.MeasureCallback
+import androidx.health.services.client.MeasureClient
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.MeasureCapabilities
+import androidx.health.services.client.impl.MeasureCallbackStub.MeasureCallbackCache
+import androidx.health.services.client.impl.MeasureIpcClient.Companion.getServiceInterface
+import androidx.health.services.client.impl.internal.StatusCallback
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.request.CapabilitiesRequest
+import androidx.health.services.client.impl.request.MeasureRegistrationRequest
+import androidx.health.services.client.impl.request.MeasureUnregistrationRequest
+import androidx.health.services.client.impl.response.MeasureCapabilitiesResponse
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/**
+ * [MeasureClient] that interacts with WHS via IPC.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class ServiceBackedMeasureClient(
+ private val context: Context,
+ connectionManager: ConnectionManager
+) : MeasureClient {
+
+ private val ipcClient: MeasureIpcClient = MeasureIpcClient(connectionManager)
+
+ override fun registerCallback(
+ dataType: DataType,
+ callback: MeasureCallback
+ ): ListenableFuture<Void> {
+ return registerCallback(dataType, callback, ContextCompat.getMainExecutor(context))
+ }
+
+ override fun registerCallback(
+ dataType: DataType,
+ callback: MeasureCallback,
+ executor: Executor
+ ): ListenableFuture<Void> {
+ val request = MeasureRegistrationRequest(context.packageName, dataType)
+ val callbackStub = MeasureCallbackCache.INSTANCE.getOrCreate(dataType, callback, executor)
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .registerCallback(request, callbackStub, StatusCallback(resultFuture))
+ }
+ return ipcClient.registerListener(callbackStub.listenerKey, serviceOperation)
+ }
+
+ override fun unregisterCallback(
+ dataType: DataType,
+ callback: MeasureCallback
+ ): ListenableFuture<Void> {
+ val callbackStub =
+ MeasureCallbackCache.INSTANCE.remove(dataType, callback)
+ ?: return Futures.immediateFailedFuture(
+ IllegalArgumentException("Given callback was not registered.")
+ )
+ val request = MeasureUnregistrationRequest(context.packageName, dataType)
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .unregisterCallback(request, callbackStub, StatusCallback(resultFuture))
+ }
+ return ipcClient.unregisterListener(callbackStub.listenerKey, serviceOperation)
+ }
+
+ override val capabilities: ListenableFuture<MeasureCapabilities>
+ get() {
+ val request = CapabilitiesRequest(context.packageName)
+ val serviceOperation =
+ ServiceOperation<MeasureCapabilitiesResponse> { binder, resultFuture ->
+ resultFuture.set(getServiceInterface(binder).getCapabilities(request))
+ }
+ return Futures.transform(
+ ipcClient.execute(serviceOperation),
+ { response -> response?.measureCapabilities },
+ ContextCompat.getMainExecutor(context)
+ )
+ }
+
+ internal companion object {
+ @JvmStatic
+ fun getClient(context: Context): ServiceBackedMeasureClient {
+ return ServiceBackedMeasureClient(context, WhsConnectionManager.getInstance(context))
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
new file mode 100644
index 0000000..f156c38
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl
+
+import android.app.PendingIntent
+import android.content.Context
+import androidx.core.content.ContextCompat
+import androidx.health.services.client.PassiveMonitoringCallback
+import androidx.health.services.client.PassiveMonitoringClient
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.PassiveMonitoringCapabilities
+import androidx.health.services.client.data.event.Event
+import androidx.health.services.client.impl.PassiveMonitoringIpcClient.Companion.getServiceInterface
+import androidx.health.services.client.impl.internal.StatusCallback
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.request.BackgroundRegistrationRequest
+import androidx.health.services.client.impl.request.CapabilitiesRequest
+import androidx.health.services.client.impl.request.EventRequest
+import androidx.health.services.client.impl.response.PassiveMonitoringCapabilitiesResponse
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Passive monitoring client that interacts with WHS via IPC.
+ *
+ * @hide
+ */
+internal class ServiceBackedPassiveMonitoringClient(private val applicationContext: Context) :
+ PassiveMonitoringClient {
+
+ private val ipcClient: PassiveMonitoringIpcClient =
+ PassiveMonitoringIpcClient(WhsConnectionManager.getInstance(applicationContext))
+
+ override fun registerDataCallback(
+ dataTypes: Set<DataType>,
+ callbackIntent: PendingIntent
+ ): ListenableFuture<Void> {
+ return registerDataCallbackInternal(dataTypes, callbackIntent, callback = null)
+ }
+
+ override fun registerDataCallback(
+ dataTypes: Set<DataType>,
+ callbackIntent: PendingIntent,
+ callback: PassiveMonitoringCallback
+ ): ListenableFuture<Void> {
+ return registerDataCallbackInternal(dataTypes, callbackIntent, callback)
+ }
+
+ override fun unregisterDataCallback(): ListenableFuture<Void> {
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .unregisterDataCallback(
+ applicationContext.packageName,
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun registerEventCallback(
+ event: Event,
+ callbackIntent: PendingIntent
+ ): ListenableFuture<Void> {
+ val request = EventRequest(applicationContext.packageName, event)
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .registerEventCallback(request, callbackIntent, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override fun unregisterEventCallback(event: Event): ListenableFuture<Void> {
+ val request = EventRequest(applicationContext.packageName, event)
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .unregisterEventCallback(request, StatusCallback(resultFuture))
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+
+ override val capabilities: ListenableFuture<PassiveMonitoringCapabilities>
+ get() {
+ val request = CapabilitiesRequest(applicationContext.packageName)
+ val serviceOperation =
+ ServiceOperation<PassiveMonitoringCapabilitiesResponse> { binder, resultFuture ->
+ resultFuture.set(getServiceInterface(binder).getCapabilities(request))
+ }
+ return Futures.transform(
+ ipcClient.execute(serviceOperation),
+ { response -> response?.passiveMonitoringCapabilities },
+ ContextCompat.getMainExecutor(applicationContext)
+ )
+ }
+
+ private fun registerDataCallbackInternal(
+ dataTypes: Set<DataType>,
+ callbackIntent: PendingIntent,
+ callback: PassiveMonitoringCallback?
+ ): ListenableFuture<Void> {
+ val request = BackgroundRegistrationRequest(applicationContext.packageName, dataTypes)
+ val serviceOperation =
+ ServiceOperation<Void> { binder, resultFuture ->
+ getServiceInterface(binder)
+ .registerDataCallback(
+ request,
+ callbackIntent,
+ callback?.let { PassiveMonitoringCallbackStub(it) },
+ StatusCallback(resultFuture)
+ )
+ }
+ return ipcClient.execute(serviceOperation)
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt
new file mode 100644
index 0000000..078cc154
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.internal
+
+import android.os.RemoteException
+import androidx.health.services.client.data.ExerciseInfo
+import androidx.health.services.client.impl.response.ExerciseInfoResponse
+import com.google.common.util.concurrent.SettableFuture
+
+/**
+ * A callback for ipc invocations dealing with [ExerciseInfo].
+ *
+ * @hide
+ */
+public class ExerciseInfoCallback(private val resultFuture: SettableFuture<ExerciseInfo>) :
+ IExerciseInfoCallback.Stub() {
+
+ @Throws(RemoteException::class)
+ override fun onExerciseInfo(response: ExerciseInfoResponse) {
+ resultFuture.set(response.exerciseInfo)
+ }
+
+ @Throws(RemoteException::class)
+ override fun onFailure(message: String) {
+ resultFuture.setException(Exception(message))
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt
new file mode 100644
index 0000000..89789e6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.internal
+
+import android.os.RemoteException
+import com.google.common.util.concurrent.SettableFuture
+
+/**
+ * A generic callback for ipc invocations.
+ *
+ * @hide
+ */
+public class StatusCallback(private val resultFuture: SettableFuture<Void>) :
+ IStatusCallback.Stub() {
+
+ @Throws(RemoteException::class)
+ override fun onSuccess() {
+ resultFuture.set(null)
+ }
+
+ @Throws(RemoteException::class)
+ override fun onFailure(msg: String) {
+ resultFuture.setException(Exception(msg))
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/WhsConnectionManager.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/WhsConnectionManager.kt
new file mode 100644
index 0000000..eb701b6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/WhsConnectionManager.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.internal
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
+import androidx.annotation.GuardedBy
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+
+/**
+ * Utility to return an instance of connection manager.
+ *
+ * @hide
+ */
+public object WhsConnectionManager {
+
+ private val lock = Any()
+
+ // Suppress StaticFieldLeak; we're only storing application Context.
+ @SuppressLint("StaticFieldLeak")
+ @GuardedBy("lock")
+ private lateinit var instance: ConnectionManager
+
+ @JvmStatic
+ public fun getInstance(context: Context): ConnectionManager {
+ synchronized(lock) {
+ if (!::instance.isInitialized) {
+ val looper = startHandlerThread()
+ instance = ConnectionManager(context.applicationContext, looper)
+ }
+
+ return instance
+ }
+ }
+
+ private fun startHandlerThread(): Looper {
+ val handlerThread =
+ HandlerThread(
+ "WhsConnectionManager",
+ Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE
+ )
+ handlerThread.start()
+ return handlerThread.looper
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ApiVersionException.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ApiVersionException.java
new file mode 100644
index 0000000..134ffa3
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ApiVersionException.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Exception that is thrown when API version requirements are not met.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class ApiVersionException extends ExecutionException {
+
+ private final int mRemoteVersion;
+ private final int mMinVersion;
+
+ public ApiVersionException(int remoteVersion, int minVersion) {
+ super(
+ "Version requirements for calling the method was not met, remoteVersion: "
+ + remoteVersion
+ + ", minVersion: "
+ + minVersion);
+ this.mRemoteVersion = remoteVersion;
+ this.mMinVersion = minVersion;
+ }
+
+ public int getRemoteVersion() {
+ return mRemoteVersion;
+ }
+
+ public int getMinVersion() {
+ return mMinVersion;
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/Client.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/Client.java
new file mode 100644
index 0000000..b417620
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/Client.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.VisibleForTesting;
+import androidx.health.services.client.impl.ipc.internal.BaseQueueOperation;
+import androidx.health.services.client.impl.ipc.internal.ConnectionConfiguration;
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager;
+import androidx.health.services.client.impl.ipc.internal.ExecutionTracker;
+import androidx.health.services.client.impl.ipc.internal.ListenerKey;
+import androidx.health.services.client.impl.ipc.internal.QueueOperation;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * SDK client for establishing connection to a cross process service.
+ *
+ * <p>Extend this class to create a new client. Each client should represent one connection to AIDL
+ * interface. For user instruction see: go/wear-dd-wcs-sdk
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public abstract class Client {
+
+ /** Interface abstracting extraction of the API version from the binder. */
+ public interface VersionGetter {
+ /** Returns the API version. */
+ Integer readVersion(IBinder binder) throws RemoteException;
+ }
+
+ private static final int UNKNOWN_VERSION = -1;
+
+ private final ConnectionConfiguration mConnectionConfiguration;
+ private final ConnectionManager mConnectionManager;
+ private final ServiceOperation<Integer> mApiVersionOperation;
+
+ @VisibleForTesting volatile int mCurrentVersion = UNKNOWN_VERSION;
+
+ public Client(
+ ClientConfiguration clientConfiguration,
+ ConnectionManager connectionManager,
+ VersionGetter versionGetter) {
+ QueueOperation versionOperation =
+ new QueueOperation() {
+ @Override
+ public void execute(IBinder binder) throws RemoteException {
+ mCurrentVersion = versionGetter.readVersion(binder);
+ }
+
+ @Override
+ public void setException(Throwable exception) {}
+
+ @Override
+ public QueueOperation trackExecution(ExecutionTracker tracker) {
+ return this;
+ }
+
+ @Override
+ public ConnectionConfiguration getConnectionConfiguration() {
+ return Client.this.getConnectionConfiguration();
+ }
+ };
+ this.mConnectionConfiguration =
+ new ConnectionConfiguration(
+ clientConfiguration.getServicePackageName(),
+ clientConfiguration.getApiClientName(),
+ clientConfiguration.getBindAction(),
+ versionOperation);
+ this.mConnectionManager = connectionManager;
+ this.mApiVersionOperation =
+ (binder, resultFuture) -> resultFuture.set(versionGetter.readVersion(binder));
+ }
+
+ /**
+ * Executes given operation against a IPC service defined by {@code clientConfiguration}.
+ *
+ * @param operation Operation that will be executed against the service
+ * @param <R> Type of returned variable
+ * @return {@link ListenableFuture<R>} with the result of the operation or an exception if the
+ * execution fails.
+ */
+ protected <R> ListenableFuture<R> execute(ServiceOperation<R> operation) {
+ SettableFuture<R> settableFuture = SettableFuture.create();
+ mConnectionManager.scheduleForExecution(
+ createQueueOperation(operation, mConnectionConfiguration, settableFuture));
+ return settableFuture;
+ }
+
+ protected <R> ListenableFuture<R> executeWithVersionCheck(
+ ServiceOperation<R> operation, int minApiVersion) {
+ if (mCurrentVersion == UNKNOWN_VERSION) {
+ SettableFuture<R> settableFuture = SettableFuture.create();
+ ListenableFuture<Integer> versionFuture = execute(mApiVersionOperation);
+ Futures.addCallback(
+ versionFuture,
+ new FutureCallback<Integer>() {
+ @Override
+ public void onSuccess(@Nullable Integer remoteVersion) {
+ mCurrentVersion =
+ remoteVersion == null ? UNKNOWN_VERSION : remoteVersion;
+ if (mCurrentVersion < minApiVersion) {
+ settableFuture.setException(
+ getApiVersionCheckFailureException(
+ mCurrentVersion, minApiVersion));
+ } else {
+ getConnectionManager()
+ .scheduleForExecution(
+ createQueueOperation(
+ operation,
+ getConnectionConfiguration(),
+ settableFuture));
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable throwable) {
+ settableFuture.setException(throwable);
+ }
+ },
+ MoreExecutors.directExecutor());
+ return settableFuture;
+ } else if (mCurrentVersion >= minApiVersion) {
+ return execute(operation);
+ } else {
+ // This empty operation is executed just to connect to the service. If we didn't connect
+ // it
+ // could happen that we won't detect change in the API version.
+ mConnectionManager.scheduleForExecution(
+ new BaseQueueOperation(mConnectionConfiguration));
+ return Futures.immediateFailedFuture(
+ getApiVersionCheckFailureException(mCurrentVersion, minApiVersion));
+ }
+ }
+
+ /**
+ * Registers a listener by executing the provided {@link ServiceOperation}.
+ *
+ * <p>The provided {@code registerListenerOperation} will be stored for every unique {@code
+ * listenerKey} and re-executed when connection is lost.
+ *
+ * @param listenerKey Key based on which listeners will be distinguished.
+ * @param registerListenerOperation Method that registers the listener, can by any {@link
+ * ServiceOperation}.
+ * @param <R> Type of return value returned in the future.
+ * @return {@link ListenableFuture<R>} with the result of the operation or an exception if the
+ * execution fails.
+ */
+ protected <R> ListenableFuture<R> registerListener(
+ ListenerKey listenerKey, ServiceOperation<R> registerListenerOperation) {
+ SettableFuture<R> settableFuture = SettableFuture.create();
+ mConnectionManager.registerListener(
+ listenerKey,
+ createQueueOperation(
+ registerListenerOperation, mConnectionConfiguration, settableFuture));
+ return settableFuture;
+ }
+
+ /**
+ * Unregisters a listener by executing the provided {@link ServiceOperation}.
+ *
+ * @param listenerKey Key based on which listeners will be distinguished.
+ * @param unregisterListenerOperation Method that unregisters the listener, can by any {@link
+ * ServiceOperation}.
+ * @param <R> Type of return value returned in the future.
+ * @return {@link ListenableFuture<R>} with the result of the operation or an exception if the
+ * execution fails.
+ */
+ protected <R> ListenableFuture<R> unregisterListener(
+ ListenerKey listenerKey, ServiceOperation<R> unregisterListenerOperation) {
+ SettableFuture<R> settableFuture = SettableFuture.create();
+ mConnectionManager.unregisterListener(
+ listenerKey,
+ createQueueOperation(
+ unregisterListenerOperation, getConnectionConfiguration(), settableFuture));
+ return settableFuture;
+ }
+
+ protected Exception getApiVersionCheckFailureException(int currentVersion, int minApiVersion) {
+ return new ApiVersionException(currentVersion, minApiVersion);
+ }
+
+ ConnectionConfiguration getConnectionConfiguration() {
+ return mConnectionConfiguration;
+ }
+
+ ConnectionManager getConnectionManager() {
+ return mConnectionManager;
+ }
+
+ private static <R> QueueOperation createQueueOperation(
+ ServiceOperation<R> operation,
+ ConnectionConfiguration connectionConfiguration,
+ SettableFuture<R> settableFuture) {
+ return new BaseQueueOperation(connectionConfiguration) {
+ @Override
+ public void execute(IBinder binder) throws RemoteException {
+ operation.execute(binder, settableFuture);
+ }
+
+ @Override
+ public void setException(Throwable exception) {
+ settableFuture.setException(exception);
+ }
+
+ @Override
+ public QueueOperation trackExecution(ExecutionTracker tracker) {
+ tracker.track(settableFuture);
+ return this;
+ }
+ };
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java
new file mode 100644
index 0000000..cf824d1
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * An interface that provides basic information about the IPC service. This is required for building
+ * the service in {@link Client}.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class ClientConfiguration {
+ private final String mServicePackageName;
+ private final String mBindAction;
+ private final String mApiClientName;
+
+ public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {
+ this.mServicePackageName = servicePackageName;
+ this.mBindAction = bindAction;
+ this.mApiClientName = apiClientName;
+ }
+
+ /** Returns the application package of the remote service. */
+ public String getServicePackageName() {
+ return mServicePackageName;
+ }
+
+ /** Returns the action used to bind to the remote service. */
+ public String getBindAction() {
+ return mBindAction;
+ }
+
+ /** Returns name of the service, use for logging and debugging only. */
+ public String getApiClientName() {
+ return mApiClientName;
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java
new file mode 100644
index 0000000..c15503e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * General operation that will be executed against given service. A new operation can be created by
+ * implementing this interface. User is then responsible for setting the result Future with the
+ * result value.
+ *
+ * @param <R> Type of the returned value.
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public interface ServiceOperation<R> {
+
+ /**
+ * Method executed against the service.
+ *
+ * @param binder Already connected binder to the target service.
+ * @param resultFuture A {@link SettableFuture} that should be set with the result.
+ */
+ void execute(IBinder binder, SettableFuture<R> resultFuture) throws RemoteException;
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java
new file mode 100644
index 0000000..079d509
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * Abstract implementation of QueueOperation that accepts {@link ConnectionConfiguration} describing
+ * the service where it will be executed.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class BaseQueueOperation implements QueueOperation {
+ private final ConnectionConfiguration mConnectionConfiguration;
+
+ public BaseQueueOperation(ConnectionConfiguration connectionConfiguration) {
+ this.mConnectionConfiguration = checkNotNull(connectionConfiguration);
+ }
+
+ @Override
+ public void execute(IBinder binder) throws RemoteException {}
+
+ @Override
+ public void setException(Throwable exception) {}
+
+ @Override
+ public QueueOperation trackExecution(ExecutionTracker tracker) {
+ return this;
+ }
+
+ /** Configuration of the service connection on which the operation will be executed. */
+ @Override
+ public ConnectionConfiguration getConnectionConfiguration() {
+ return mConnectionConfiguration;
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java
new file mode 100644
index 0000000..9b6c42b
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.health.services.client.impl.ipc.ClientConfiguration;
+
+/**
+ * Internal representation of configuration of IPC service connection.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public final class ConnectionConfiguration {
+ private final String mPackageName;
+ private final String mClientName;
+ private final String mBindAction;
+ private final QueueOperation mRefreshVersionOperation;
+
+ public ConnectionConfiguration(
+ String packageName,
+ String clientName,
+ String bindAction,
+ QueueOperation refreshVersionOperation) {
+ this.mPackageName = checkNotNull(packageName);
+ this.mClientName = checkNotNull(clientName);
+ this.mBindAction = checkNotNull(bindAction);
+ this.mRefreshVersionOperation = checkNotNull(refreshVersionOperation);
+ }
+
+ /** A key that defines the connection among other IPC connections. It should be unique. */
+ String getKey() {
+ return String.format("%s#%s#%s", mClientName, mPackageName, mBindAction);
+ }
+
+ /** API Client name defined in the {@link ClientConfiguration#getApiClientName()}. */
+ String getClientName() {
+ return mClientName;
+ }
+
+ /**
+ * An action used to bind to the remote service. Taken from {@link
+ * ClientConfiguration#getBindAction()}.
+ */
+ String getBindAction() {
+ return mBindAction;
+ }
+
+ /**
+ * Package name of remote service. Taken from {@link
+ * ClientConfiguration#getServicePackageName()}.
+ */
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ QueueOperation getRefreshVersionOperation() {
+ return mRefreshVersionOperation;
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java
new file mode 100644
index 0000000..739a531
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc.internal;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages connections to a service in a different process.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public final class ConnectionManager implements Handler.Callback, ServiceConnection.Callback {
+ private static final String TAG = "ConnectionManager";
+
+ private static final int MSG_CONNECTED = 1;
+ private static final int MSG_DISCONNECTED = 2;
+ private static final int MSG_EXECUTE = 3;
+ private static final int MSG_REGISTER_LISTENER = 4;
+ private static final int MSG_UNREGISTER_LISTENER = 5;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Map<String, ServiceConnection> mServiceConnectionMap = new HashMap<>();
+
+ private boolean mBindToSelfEnabled;
+
+ public ConnectionManager(Context context, Looper looper) {
+ this.mContext = context;
+ this.mHandler = new Handler(looper, this);
+ }
+
+ /**
+ * Schedules operation for execution
+ *
+ * @param operation Operation prepared for scheduling on the connection queue.
+ */
+ public void scheduleForExecution(QueueOperation operation) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_EXECUTE, operation));
+ }
+
+ /**
+ * Registers a listener by executing an operation represented by the {@link QueueOperation}.
+ *
+ * @param listenerKey Key based on which listeners will be distinguished.
+ * @param registerOperation Queue operation executed against the corresponding connection to
+ * register the listener. Will be used to re-register when connection is lost.
+ */
+ public void registerListener(ListenerKey listenerKey, QueueOperation registerOperation) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(
+ MSG_REGISTER_LISTENER, new ListenerHolder(listenerKey, registerOperation)));
+ }
+
+ /**
+ * Unregisters a listener by executing an operation represented by the {@link QueueOperation}.
+ *
+ * @param listenerKey Key based on which listeners will be distinguished.
+ * @param unregisterOperation Queue operation executed against the corresponding connection to
+ * unregister the listener.
+ */
+ public void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterOperation) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(
+ MSG_UNREGISTER_LISTENER,
+ new ListenerHolder(listenerKey, unregisterOperation)));
+ }
+
+ @Override
+ public void onConnected(ServiceConnection connection) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTED, connection));
+ }
+
+ @Override
+ public void onDisconnected(ServiceConnection connection, long reconnectDelayMs) {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_DISCONNECTED, connection), reconnectDelayMs);
+ }
+
+ @Override
+ public boolean isBindToSelfEnabled() {
+ return mBindToSelfEnabled;
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CONNECTED:
+ ServiceConnection serviceConnection = ((ServiceConnection) msg.obj);
+ serviceConnection.reRegisterAllListeners();
+ serviceConnection.refreshServiceVersion();
+ serviceConnection.flushQueue();
+ return true;
+ case MSG_DISCONNECTED:
+ ((ServiceConnection) msg.obj).maybeReconnect();
+ return true;
+ case MSG_EXECUTE:
+ QueueOperation queueOperation = (QueueOperation) msg.obj;
+ getConnection(queueOperation.getConnectionConfiguration()).enqueue(queueOperation);
+ return true;
+ case MSG_REGISTER_LISTENER:
+ ListenerHolder registerListenerHolder = (ListenerHolder) msg.obj;
+ getConnection(
+ registerListenerHolder
+ .getListenerOperation()
+ .getConnectionConfiguration())
+ .registerListener(
+ registerListenerHolder.getListenerKey(),
+ registerListenerHolder.getListenerOperation());
+ return true;
+ case MSG_UNREGISTER_LISTENER:
+ ListenerHolder unregisterListenerHolder = (ListenerHolder) msg.obj;
+ getConnection(
+ unregisterListenerHolder
+ .getListenerOperation()
+ .getConnectionConfiguration())
+ .unregisterListener(
+ unregisterListenerHolder.getListenerKey(),
+ unregisterListenerHolder.getListenerOperation());
+ return true;
+ default:
+ Log.e(TAG, "Received unknown message: " + msg.what);
+ return false;
+ }
+ }
+
+ public void setBindToSelf(boolean bindToSelfEnabled) {
+ this.mBindToSelfEnabled = bindToSelfEnabled;
+ }
+
+ private ServiceConnection getConnection(ConnectionConfiguration connectionConfiguration) {
+ String connectionKey = connectionConfiguration.getKey();
+ ServiceConnection serviceConnection = mServiceConnectionMap.get(connectionKey);
+ if (serviceConnection == null) {
+ serviceConnection =
+ new ServiceConnection(
+ mContext, connectionConfiguration, new DefaultExecutionTracker(), this);
+ mServiceConnectionMap.put(connectionKey, serviceConnection);
+ }
+ return serviceConnection;
+ }
+
+ private static class ListenerHolder {
+ private final ListenerKey mListenerKey;
+ private final QueueOperation mListenerOperation;
+
+ ListenerHolder(ListenerKey listenerKey, QueueOperation listenerOperation) {
+ this.mListenerKey = listenerKey;
+ this.mListenerOperation = listenerOperation;
+ }
+
+ ListenerKey getListenerKey() {
+ return mListenerKey;
+ }
+
+ QueueOperation getListenerOperation() {
+ return mListenerOperation;
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java
new file mode 100644
index 0000000..c8b527e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc.internal;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Default implementation of {@link ExecutionTracker}.
+ *
+ * @hide
+ */
+@SuppressWarnings("ExecutorTaskName")
+@RestrictTo(Scope.LIBRARY)
+public class DefaultExecutionTracker implements ExecutionTracker {
+ private final Set<SettableFuture<?>> mFuturesInProgress = new HashSet<>();
+
+ @Override
+ public void track(SettableFuture<?> future) {
+ mFuturesInProgress.add(future);
+ future.addListener(() -> mFuturesInProgress.remove(future), MoreExecutors.directExecutor());
+ }
+
+ @Override
+ public void cancelPendingFutures(Throwable throwable) {
+ for (SettableFuture<?> future : mFuturesInProgress) {
+ future.setException(throwable);
+ }
+ mFuturesInProgress.clear();
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java
new file mode 100644
index 0000000..eb9e81d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc.internal;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * Tracker for tracking operations that are currently in progress.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public interface ExecutionTracker {
+
+ /** Track given future as in progress. */
+ void track(SettableFuture<?> future);
+
+ /** Cancel all tracked futures with given exception. */
+ void cancelPendingFutures(Throwable throwable);
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java
new file mode 100644
index 0000000..f2960e3
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc.internal;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * Unique key to hold listener reference.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public final class ListenerKey {
+ private final Object mListenerKey;
+
+ public ListenerKey(Object listenerKey) {
+ this.mListenerKey = listenerKey;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ListenerKey)) {
+ return false;
+ }
+
+ ListenerKey that = (ListenerKey) o;
+ return mListenerKey.equals(that);
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(mListenerKey);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(mListenerKey);
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java
new file mode 100644
index 0000000..6021bdf
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc.internal;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * A wrapper for SDK operation that will be executed on a connected binder. It is intended for
+ * scheduling in execution queue.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public interface QueueOperation {
+ /**
+ * Method executed against the service.
+ *
+ * @param binder already connected to the target service.
+ */
+ void execute(IBinder binder) throws RemoteException;
+
+ /** Sets exception as the result of the operation. */
+ void setException(Throwable exception);
+
+ /**
+ * Tracks the operation execution with an {@link ExecutionTracker}.
+ *
+ * @param tracker To track the execution as in progress.
+ */
+ QueueOperation trackExecution(ExecutionTracker tracker);
+
+ /** Returns configuration of the service connection on which the operation will be executed. */
+ ConnectionConfiguration getConnectionConfiguration();
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java
new file mode 100644
index 0000000..fa432bc
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.ipc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * A class that maintains a connection to IPC backend service. If connection is not available it
+ * uses a queue to store service requests until connection is renewed. One {@link ServiceConnection}
+ * is associated with one AIDL file .
+ *
+ * <p>Note: this class is not thread safe and should be called always from the same thread.
+ *
+ * @hide
+ */
+@NotThreadSafe
+@RestrictTo(Scope.LIBRARY)
+public class ServiceConnection implements android.content.ServiceConnection {
+ private static final String TAG = "ServiceConnection";
+
+ /** Callback for reporting back to the manager. */
+ public interface Callback {
+
+ /** Called when the connection to the server was successfully established. */
+ void onConnected(ServiceConnection connection);
+
+ /**
+ * Called when the connection to the server was lost.
+ *
+ * @param connection Represents this connection to a service.
+ * @param reconnectDelayMs Delay before the caller should try to reconnect this connection.
+ */
+ void onDisconnected(ServiceConnection connection, long reconnectDelayMs);
+
+ /**
+ * Return true if the {@link ServiceConnection} should bind to the service in the same
+ * application for testing reason.
+ */
+ boolean isBindToSelfEnabled();
+ }
+
+ private static final int MAX_RETRIES = 10;
+
+ private final Context mContext;
+ private final Queue<QueueOperation> mOperationQueue = new ConcurrentLinkedQueue<>();
+ private final ConnectionConfiguration mConnectionConfiguration;
+ private final ExecutionTracker mExecutionTracker;
+ private final Map<ListenerKey, QueueOperation> mRegisteredListeners = new HashMap<>();
+ private final Callback mCallback;
+
+ @VisibleForTesting @Nullable IBinder mBinder;
+ private volatile boolean mIsServiceBound;
+ /** Denotes how many times connection to the service failed and we retried. */
+ private int mServiceConnectionRetry;
+
+ ServiceConnection(
+ Context context,
+ ConnectionConfiguration connectionConfiguration,
+ ExecutionTracker executionTracker,
+ Callback callback) {
+ this.mContext = checkNotNull(context);
+ this.mConnectionConfiguration = checkNotNull(connectionConfiguration);
+ this.mExecutionTracker = checkNotNull(executionTracker);
+ this.mCallback = checkNotNull(callback);
+ }
+
+ private String getBindPackageName() {
+ if (mCallback.isBindToSelfEnabled()) {
+ return mContext.getPackageName();
+ } else {
+ return mConnectionConfiguration.getPackageName();
+ }
+ }
+
+ /** Connects to the service. */
+ public void connect() {
+ if (mIsServiceBound) {
+ return;
+ }
+ try {
+ mIsServiceBound =
+ mContext.bindService(
+ new Intent()
+ .setPackage(getBindPackageName())
+ .setAction(mConnectionConfiguration.getBindAction()),
+ this,
+ Context.BIND_AUTO_CREATE | Context.BIND_ADJUST_WITH_ACTIVITY);
+ } catch (SecurityException exception) {
+ Log.w(
+ TAG,
+ "Failed to bind connection '"
+ + mConnectionConfiguration.getKey()
+ + "', no permission or service not found.",
+ exception);
+ mIsServiceBound = false;
+ mBinder = null;
+ throw exception;
+ }
+
+ if (!mIsServiceBound) {
+ // Service not found or we don't have permission to call it.
+ Log.e(
+ TAG,
+ "Connection to service is not available for package '"
+ + mConnectionConfiguration.getPackageName()
+ + "' and action '"
+ + mConnectionConfiguration.getBindAction()
+ + "'.");
+ handleNonRetriableDisconnection(new CancellationException("Service not available"));
+ }
+ }
+
+ private void handleNonRetriableDisconnection(Throwable throwable) {
+ // Set retry count to maximum to prevent retries
+ mServiceConnectionRetry = MAX_RETRIES;
+ handleRetriableDisconnection(throwable);
+ }
+
+ private synchronized void handleRetriableDisconnection(Throwable throwable) {
+ if (isConnected()) {
+ // Connection is already re-established. So just return.
+ Log.w(TAG, "Connection is already re-established. No need to reconnect again");
+ return;
+ }
+
+ clearConnection(throwable);
+
+ if (mServiceConnectionRetry < MAX_RETRIES) {
+ Log.w(
+ TAG,
+ "WCS SDK Client '"
+ + mConnectionConfiguration.getClientName()
+ + "' disconnected, retrying connection. Retry attempt: "
+ + mServiceConnectionRetry,
+ throwable);
+ mCallback.onDisconnected(this, getRetryDelayMs(mServiceConnectionRetry));
+ } else {
+ Log.e(TAG, "Connection disconnected and maximum number of retries reached.", throwable);
+ }
+ }
+
+ private static int getRetryDelayMs(int retryNumber) {
+ // Exponential retry delay starting on 200ms.
+ return (200 << retryNumber);
+ }
+
+ private void clearConnection(Throwable throwable) {
+ if (mIsServiceBound) {
+ try {
+ mContext.unbindService(this);
+ mIsServiceBound = false;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to unbind the service. Ignoring and continuing", e);
+ }
+ }
+
+ mBinder = null;
+ mExecutionTracker.cancelPendingFutures(throwable);
+ cancelAllOperationsInQueue(throwable);
+ }
+
+ void enqueue(QueueOperation operation) {
+ if (isConnected()) {
+ execute(operation);
+ } else {
+ mOperationQueue.add(operation);
+ connect();
+ }
+ }
+
+ void registerListener(ListenerKey listenerKey, QueueOperation registerListenerOperation) {
+ mRegisteredListeners.put(listenerKey, registerListenerOperation);
+ if (isConnected()) {
+ enqueue(registerListenerOperation);
+ } else {
+ connect();
+ }
+ }
+
+ void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterListenerOperation) {
+ mRegisteredListeners.remove(listenerKey);
+ enqueue(unregisterListenerOperation);
+ }
+
+ void maybeReconnect() {
+ if (mRegisteredListeners.isEmpty()) {
+ Log.d(
+ TAG,
+ "No listeners registered, service "
+ + mConnectionConfiguration.getClientName()
+ + " is not automatically reconnected.");
+ } else {
+ mServiceConnectionRetry++;
+ Log.d(
+ TAG,
+ "Listeners for service "
+ + mConnectionConfiguration.getClientName()
+ + " are registered, reconnecting.");
+ connect();
+ }
+ }
+
+ @VisibleForTesting
+ void execute(QueueOperation operation) {
+ try {
+ operation.trackExecution(mExecutionTracker);
+ operation.execute(checkNotNull(mBinder));
+ } catch (DeadObjectException exception) {
+ handleRetriableDisconnection(exception);
+ // TODO(b/152024821): Consider possible TransactionTooLargeException failure.
+ } catch (RemoteException | RuntimeException exception) {
+ operation.setException(exception);
+ }
+ }
+
+ void reRegisterAllListeners() {
+ for (Map.Entry<ListenerKey, QueueOperation> entry : mRegisteredListeners.entrySet()) {
+ Log.d(TAG, "Re-registering listener: " + entry.getKey());
+ execute(entry.getValue());
+ }
+ }
+
+ void refreshServiceVersion() {
+ mOperationQueue.add(mConnectionConfiguration.getRefreshVersionOperation());
+ }
+
+ void flushQueue() {
+ for (QueueOperation operation : new ArrayList<>(mOperationQueue)) {
+ boolean removed = mOperationQueue.remove(operation);
+ if (removed) {
+ execute(operation);
+ }
+ }
+ }
+
+ private void cancelAllOperationsInQueue(Throwable throwable) {
+ for (QueueOperation operation : new ArrayList<>(mOperationQueue)) {
+ boolean removed = mOperationQueue.remove(operation);
+ if (removed) {
+ operation.setException(throwable);
+ execute(operation);
+ }
+ }
+ }
+
+ private boolean isConnected() {
+ return mBinder != null && mBinder.isBinderAlive();
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder binder) {
+ Log.d(TAG, "onServiceConnected(), componentName = " + componentName);
+ if (binder == null) {
+ Log.e(TAG, "Service connected but binder is null.");
+ return;
+ }
+ mServiceConnectionRetry = 0;
+ cleanOnDeath(binder);
+ this.mBinder = binder;
+ mCallback.onConnected(this);
+ }
+
+ private void cleanOnDeath(IBinder binder) {
+ try {
+ binder.linkToDeath(
+ () -> {
+ Log.w(
+ TAG,
+ "Binder died for client:"
+ + mConnectionConfiguration.getClientName());
+ handleRetriableDisconnection(new CancellationException());
+ },
+ /* flags= */ 0);
+ } catch (RemoteException exception) {
+ Log.w(
+ TAG,
+ "Cannot link to death, binder already died. Cleaning operations.",
+ exception);
+ handleRetriableDisconnection(exception);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ Log.d(TAG, "onServiceDisconnected(), componentName = " + componentName);
+ // Service disconnected but binding still exists so it should reconnect automatically.
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ Log.e(TAG, "Binding died for client '" + mConnectionConfiguration.getClientName() + "'.");
+ handleRetriableDisconnection(new CancellationException());
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ Log.e(
+ TAG,
+ "Cannot bind client '"
+ + mConnectionConfiguration.getClientName()
+ + "', binder is null");
+ // This connection will never be usable, don't bother with retries.
+ handleNonRetriableDisconnection(new CancellationException("Null binding"));
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
new file mode 100644
index 0000000..6fc9161
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Request for enabling/disabling auto pause/resume.
+ *
+ * @hide
+ */
+public data class AutoPauseAndResumeConfigRequest(
+ val packageName: String,
+ val shouldEnable: Boolean,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeInt(if (shouldEnable) 1 else 0)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<AutoPauseAndResumeConfigRequest> =
+ object : Parcelable.Creator<AutoPauseAndResumeConfigRequest> {
+ override fun createFromParcel(source: Parcel): AutoPauseAndResumeConfigRequest? {
+ return AutoPauseAndResumeConfigRequest(
+ source.readString() ?: return null,
+ source.readInt() == 1,
+ )
+ }
+
+ override fun newArray(size: Int): Array<AutoPauseAndResumeConfigRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt
new file mode 100644
index 0000000..2c0296a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataType
+
+/**
+ * Request for background registration.
+ *
+ * @hide
+ */
+public data class BackgroundRegistrationRequest(
+ val packageName: String,
+ val dataTypes: Set<DataType>,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeTypedList(dataTypes.toList())
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<BackgroundRegistrationRequest> =
+ object : Parcelable.Creator<BackgroundRegistrationRequest> {
+ override fun createFromParcel(source: Parcel): BackgroundRegistrationRequest? {
+ val packageName = source.readString() ?: return null
+ val list = ArrayList<DataType>()
+ source.readTypedList(list, DataType.CREATOR)
+ return BackgroundRegistrationRequest(packageName, list.toSet())
+ }
+
+ override fun newArray(size: Int): Array<BackgroundRegistrationRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt
new file mode 100644
index 0000000..67fcaa8
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Request for background unregistration.
+ *
+ * @hide
+ */
+public data class BackgroundUnregistrationRequest(val packageName: String) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<BackgroundUnregistrationRequest> =
+ object : Parcelable.Creator<BackgroundUnregistrationRequest> {
+ override fun createFromParcel(source: Parcel): BackgroundUnregistrationRequest? {
+ val packageName = source.readString() ?: return null
+ return BackgroundUnregistrationRequest(packageName)
+ }
+
+ override fun newArray(size: Int): Array<BackgroundUnregistrationRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
new file mode 100644
index 0000000..ba837e4
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Request for capabilities.
+ *
+ * @hide
+ */
+public data class CapabilitiesRequest(val packageName: String) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<CapabilitiesRequest> =
+ object : Parcelable.Creator<CapabilitiesRequest> {
+ override fun createFromParcel(source: Parcel): CapabilitiesRequest? {
+ val packageName = source.readString() ?: return null
+ return CapabilitiesRequest(packageName)
+ }
+
+ override fun newArray(size: Int): Array<CapabilitiesRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/EventRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/EventRequest.kt
new file mode 100644
index 0000000..ddfce39
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/EventRequest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.event.Event
+
+/**
+ * Request for event registration.
+ *
+ * @hide
+ */
+public data class EventRequest(val packageName: String, val event: Event) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeParcelable(event, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<EventRequest> =
+ object : Parcelable.Creator<EventRequest> {
+ override fun createFromParcel(source: Parcel): EventRequest? {
+ return EventRequest(
+ source.readString() ?: return null,
+ source.readParcelable(Event::class.java.classLoader) ?: return null,
+ )
+ }
+
+ override fun newArray(size: Int): Array<EventRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt
new file mode 100644
index 0000000..a427c67
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseGoal
+
+/**
+ * Request for adding a [ExerciseGoal] to an exercise.
+ *
+ * @hide
+ */
+public data class ExerciseGoalRequest(val packageName: String, val exerciseGoal: ExerciseGoal) :
+ Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeParcelable(exerciseGoal, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseGoalRequest> =
+ object : Parcelable.Creator<ExerciseGoalRequest> {
+ override fun createFromParcel(source: Parcel): ExerciseGoalRequest? {
+ return ExerciseGoalRequest(
+ source.readString() ?: return null,
+ source.readParcelable(ExerciseGoal::class.java.classLoader) ?: return null,
+ )
+ }
+
+ override fun newArray(size: Int): Array<ExerciseGoalRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
new file mode 100644
index 0000000..2d88026
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataType
+
+/**
+ * Request for measure registration.
+ *
+ * @hide
+ */
+public data class MeasureRegistrationRequest(
+ val packageName: String,
+ val dataType: DataType,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeParcelable(dataType, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<MeasureRegistrationRequest> =
+ object : Parcelable.Creator<MeasureRegistrationRequest> {
+ override fun createFromParcel(source: Parcel): MeasureRegistrationRequest? {
+ return MeasureRegistrationRequest(
+ source.readString() ?: return null,
+ source.readParcelable(DataType::class.java.classLoader) ?: return null
+ )
+ }
+
+ override fun newArray(size: Int): Array<MeasureRegistrationRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
new file mode 100644
index 0000000..55e8054
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataType
+
+/**
+ * Request for measure unregistration.
+ *
+ * @hide
+ */
+public data class MeasureUnregistrationRequest(
+ val packageName: String,
+ val dataType: DataType,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeParcelable(dataType, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<MeasureUnregistrationRequest> =
+ object : Parcelable.Creator<MeasureUnregistrationRequest> {
+ override fun createFromParcel(source: Parcel): MeasureUnregistrationRequest? {
+ val packageName = source.readString() ?: return null
+ val dataType =
+ source.readParcelable<DataType>(DataType::class.java.classLoader)
+ ?: return null
+ return MeasureUnregistrationRequest(packageName, dataType)
+ }
+
+ override fun newArray(size: Int): Array<MeasureUnregistrationRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
new file mode 100644
index 0000000..d67b359
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseConfig
+
+/**
+ * Request for starting an exercise.
+ *
+ * @hide
+ */
+public data class StartExerciseRequest(
+ val packageName: String,
+ val exerciseConfig: ExerciseConfig,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(packageName)
+ dest.writeParcelable(exerciseConfig, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<StartExerciseRequest> =
+ object : Parcelable.Creator<StartExerciseRequest> {
+ override fun createFromParcel(source: Parcel): StartExerciseRequest? {
+ val packageName = source.readString() ?: return null
+ val parcelable =
+ source.readParcelable<ExerciseConfig>(
+ ExerciseConfig::class.java.classLoader
+ )
+ ?: return null
+ return StartExerciseRequest(packageName, parcelable)
+ }
+
+ override fun newArray(size: Int): Array<StartExerciseRequest?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt
new file mode 100644
index 0000000..3a3ccf6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.Availability
+import androidx.health.services.client.data.DataType
+
+/**
+ * Response sent on MeasureCallback with a [DataType] and its associated [Availability] status.
+ *
+ * @hide
+ */
+public data class AvailabilityResponse(
+ /** [DataType] of the [AvailabilityResponse]. */
+ val dataType: DataType,
+ /** [Availability] of the [AvailabilityResponse]. */
+ val availability: Availability,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(dataType, flags)
+ dest.writeInt(availability.id)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<AvailabilityResponse> =
+ object : Parcelable.Creator<AvailabilityResponse> {
+ override fun createFromParcel(source: Parcel): AvailabilityResponse? {
+ val parcelable =
+ source.readParcelable<DataType>(DataType::class.java.classLoader)
+ ?: return null
+ val availability = Availability.fromId(source.readInt()) ?: return null
+ return AvailabilityResponse(parcelable, availability)
+ }
+
+ override fun newArray(size: Int): Array<AvailabilityResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt
new file mode 100644
index 0000000..213a060
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.Capabilities
+
+/**
+ * Response containing the capabilities of WHS client on the device.
+ *
+ * @hide
+ */
+public data class CapabilitiesResponse(
+ /** [Capabilities] supported by this device. */
+ val capabilities: Capabilities,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(capabilities, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<CapabilitiesResponse> =
+ object : Parcelable.Creator<CapabilitiesResponse> {
+ override fun createFromParcel(source: Parcel): CapabilitiesResponse? {
+ val parcelable =
+ source.readParcelable<Capabilities>(Capabilities::class.java.classLoader)
+ ?: return null
+ return CapabilitiesResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<CapabilitiesResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt
new file mode 100644
index 0000000..0b30f6d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataPoint
+
+/**
+ * Response sent on MeasureCallback when new [DataPoints] [DataPoint] are available.
+ *
+ * @hide
+ */
+public data class DataPointsResponse(val dataPoints: List<DataPoint>) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeTypedList(dataPoints)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<DataPointsResponse> =
+ object : Parcelable.Creator<DataPointsResponse> {
+ override fun createFromParcel(source: Parcel): DataPointsResponse? {
+ val list = ArrayList<DataPoint>()
+ source.readTypedList(list, DataPoint.CREATOR)
+ return DataPointsResponse(list)
+ }
+
+ override fun newArray(size: Int): Array<DataPointsResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
new file mode 100644
index 0000000..7a44767
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseInfo
+
+/**
+ * Response containing [ExerciseInfo] when changed.
+ *
+ * @hide
+ */
+public data class ExerciseInfoResponse(val exerciseInfo: ExerciseInfo) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(exerciseInfo, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseInfoResponse> =
+ object : Parcelable.Creator<ExerciseInfoResponse> {
+ override fun createFromParcel(source: Parcel): ExerciseInfoResponse? {
+ val parcelable: ExerciseInfo =
+ source.readParcelable(ExerciseInfo::class.java.classLoader) ?: return null
+ return ExerciseInfoResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<ExerciseInfoResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
new file mode 100644
index 0000000..769346d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseLapSummary
+
+/**
+ * Response containing [ExerciseLapSummary] when it's updated.
+ *
+ * @hide
+ */
+public data class ExerciseLapSummaryResponse(val exerciseLapSummary: ExerciseLapSummary) :
+ Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(exerciseLapSummary, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseLapSummaryResponse> =
+ object : Parcelable.Creator<ExerciseLapSummaryResponse> {
+ override fun createFromParcel(source: Parcel): ExerciseLapSummaryResponse? {
+ val parcelable =
+ source.readParcelable<ExerciseLapSummary>(
+ ExerciseLapSummary::class.java.classLoader
+ )
+ ?: return null
+ return ExerciseLapSummaryResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<ExerciseLapSummaryResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
new file mode 100644
index 0000000..6c800d4
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseUpdate
+
+/**
+ * Response containing [ExerciseUpdate] when it's updated.
+ *
+ * @hide
+ */
+public data class ExerciseUpdateResponse(val exerciseUpdate: ExerciseUpdate) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(exerciseUpdate, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<ExerciseUpdateResponse> =
+ object : Parcelable.Creator<ExerciseUpdateResponse> {
+ override fun createFromParcel(source: Parcel): ExerciseUpdateResponse? {
+ val parcelable =
+ source.readParcelable<ExerciseUpdate>(
+ ExerciseUpdate::class.java.classLoader
+ )
+ ?: return null
+ return ExerciseUpdateResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<ExerciseUpdateResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
new file mode 100644
index 0000000..12f80e3
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.MeasureCapabilities
+
+/**
+ * Response containing the capabilities of WHS client on the device.
+ *
+ * @hide
+ */
+public data class MeasureCapabilitiesResponse(
+ /** [MeasureCapabilities] supported by this device. */
+ val measureCapabilities: MeasureCapabilities,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(measureCapabilities, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<MeasureCapabilitiesResponse> =
+ object : Parcelable.Creator<MeasureCapabilitiesResponse> {
+ override fun createFromParcel(source: Parcel): MeasureCapabilitiesResponse? {
+ val parcelable =
+ source.readParcelable<MeasureCapabilities>(
+ MeasureCapabilities::class.java.classLoader
+ )
+ ?: return null
+ return MeasureCapabilitiesResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<MeasureCapabilitiesResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt
new file mode 100644
index 0000000..77e98b5
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.PassiveActivityState
+
+/**
+ * Response containing [PassiveActivityState].
+ *
+ * @hide
+ */
+public data class PassiveActivityStateResponse(val passiveActivityState: PassiveActivityState) :
+ Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(passiveActivityState, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<PassiveActivityStateResponse> =
+ object : Parcelable.Creator<PassiveActivityStateResponse> {
+ override fun createFromParcel(source: Parcel): PassiveActivityStateResponse? {
+ val parcelable =
+ source.readParcelable<PassiveActivityState>(
+ PassiveActivityState::class.java.classLoader
+ )
+ ?: return null
+ return PassiveActivityStateResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<PassiveActivityStateResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
new file mode 100644
index 0000000..753735e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.PassiveMonitoringCapabilities
+
+/**
+ * Response containing the capabilities of WHS client on the device.
+ *
+ * @hide
+ */
+public data class PassiveMonitoringCapabilitiesResponse(
+ /** [PassiveMonitoringCapabilities] supported by this device. */
+ val passiveMonitoringCapabilities: PassiveMonitoringCapabilities,
+) : Parcelable {
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(passiveMonitoringCapabilities, flags)
+ }
+
+ public companion object {
+ @JvmField
+ public val CREATOR: Parcelable.Creator<PassiveMonitoringCapabilitiesResponse> =
+ object : Parcelable.Creator<PassiveMonitoringCapabilitiesResponse> {
+ override fun createFromParcel(
+ source: Parcel
+ ): PassiveMonitoringCapabilitiesResponse? {
+ val parcelable =
+ source.readParcelable<PassiveMonitoringCapabilities>(
+ PassiveMonitoringCapabilities::class.java.classLoader
+ )
+ ?: return null
+ return PassiveMonitoringCapabilitiesResponse(parcelable)
+ }
+
+ override fun newArray(size: Int): Array<PassiveMonitoringCapabilitiesResponse?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/health/health-services-client/src/main/androidx/health/package-info.java b/health/health-services-client/src/main/java/androidx/health/services/client/package-info.java
similarity index 82%
copy from health/health-services-client/src/main/androidx/health/package-info.java
copy to health/health-services-client/src/main/java/androidx/health/services/client/package-info.java
index 2aa2c9c..09f9b13 100644
--- a/health/health-services-client/src/main/androidx/health/package-info.java
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2021 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.
@@ -15,7 +15,7 @@
*/
/**
- * Insert package level documentation here
+ * This package contains top level interfaces for the Health Services client.
*/
package androidx.health.services.client;
diff --git a/media/media/api/current.txt b/media/media/api/current.txt
index ab43c37..e82dbe2 100644
--- a/media/media/api/current.txt
+++ b/media/media/api/current.txt
@@ -729,6 +729,7 @@
field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
field public static final String METADATA_KEY_NEXT_EPISODE_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
+ field public static final String METADATA_KEY_SERIES_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
field public static final long METADATA_VALUE_ATTRIBUTE_PRESENT = 1L; // 0x1L
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT = "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL = "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
diff --git a/media/media/api/public_plus_experimental_current.txt b/media/media/api/public_plus_experimental_current.txt
index cf5874c..d9037ca 100644
--- a/media/media/api/public_plus_experimental_current.txt
+++ b/media/media/api/public_plus_experimental_current.txt
@@ -729,6 +729,7 @@
field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
field public static final String METADATA_KEY_NEXT_EPISODE_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
+ field public static final String METADATA_KEY_SERIES_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
field public static final long METADATA_VALUE_ATTRIBUTE_PRESENT = 1L; // 0x1L
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT = "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL = "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
diff --git a/media/media/api/restricted_current.txt b/media/media/api/restricted_current.txt
index 3f5c9d9..bd39a46 100644
--- a/media/media/api/restricted_current.txt
+++ b/media/media/api/restricted_current.txt
@@ -761,6 +761,7 @@
field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
field public static final String METADATA_KEY_NEXT_EPISODE_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
+ field public static final String METADATA_KEY_SERIES_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
field public static final long METADATA_VALUE_ATTRIBUTE_PRESENT = 1L; // 0x1L
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT = "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL = "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
diff --git a/media/media/src/main/java/androidx/media/utils/MediaConstants.java b/media/media/src/main/java/androidx/media/utils/MediaConstants.java
index 8d81004..bd04425 100644
--- a/media/media/src/main/java/androidx/media/utils/MediaConstants.java
+++ b/media/media/src/main/java/androidx/media/utils/MediaConstants.java
@@ -113,7 +113,7 @@
/**
* Bundle key used for media content id in {@link MediaMetadataCompat metadata}, should contain
* the same ID provided to Media Actions Catalog in reference to this title (e.g., episode,
- * movie). Google uses this information to allow users to resume watching this title on your app
+ * movie). This information can be used to allow users to resume watching this title on your app
* across the supported surfaces (e.g., Android TV's Play Next row)
*
* <p>TYPE: String
@@ -126,12 +126,10 @@
/**
* Bundle key used for next episode's media content ID in {@link MediaMetadataCompat metadata},
- * following the same ID and format provided to
- * <a href="https://developers.google.com/actions/media">Media Actions Catalog</a> in reference
- * to the next episode of the current title episode. Google uses this information to allow users
- * to resume watching the next episode of this title on your app once the current episode ends
- * across the supported surfaces (e.g., Android TV's Play Next row). This can be left blank for
- * movies.
+ * following the same ID and format provided to Media Actions Catalog in reference to the next
+ * episode of the current title episode. This information can be used to allow users to resume
+ * watching the next episode of this title on your app once the current episode ends across the
+ * supported surfaces (e.g., Android TV's Play Next row). This can be left blank for movies.
*
* <p>TYPE: String
*
@@ -142,6 +140,22 @@
"androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
/**
+ * Bundle key used for the TV series's media content ID in {@link MediaMetadataCompat metadata},
+ * following the same ID and format provided to Media Actions Catalog</a> in reference to the
+ * TV series of the title episode. This information can be used to allow users to resume
+ * watching the current episode or next episode of this title on your app across the
+ * supported surfaces (e.g., Android TV's Play Next row). This value is only valid for TV
+ * Episode content type.
+ *
+ * <p>TYPE: String
+ *
+ * @see MediaMetadataCompat
+ */
+ @SuppressLint("IntentName")
+ public static final String METADATA_KEY_SERIES_CONTENT_ID =
+ "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
+
+ /**
* Key sent through a key-value mapping in {@link MediaMetadataCompat#getLong(String)} or in the
* {@link MediaDescriptionCompat#getExtras()} bundle to the hosting {@link MediaBrowserCompat}
* to indicate that the corresponding {@link MediaMetadataCompat} or {@link
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
index 6bf9217..6f7d3de 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
@@ -58,6 +58,7 @@
selected = entryRoute == route,
onClick = {
navController.navigate(route) {
+ launchSingleTop = true
restoreState = true
popUpTo(navController.graph.startDestinationId) {
saveState = true
diff --git a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt
index 3f63801..e385658 100644
--- a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt
+++ b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt
@@ -62,7 +62,7 @@
*/
@JvmStatic
public fun onNavDestinationSelected(item: MenuItem, navController: NavController): Boolean {
- val builder = NavOptions.Builder().setLaunchSingleTop(true)
+ val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true)
if (
navController.currentDestination!!.parent!!.findNode(item.itemId)
is ActivityNavigator.Destination
@@ -78,7 +78,11 @@
.setPopExitAnim(R.animator.nav_default_pop_exit_anim)
}
if (item.order and Menu.CATEGORY_SECONDARY == 0) {
- builder.setPopUpTo(findStartDestination(navController.graph).id, false)
+ builder.setPopUpTo(
+ findStartDestination(navController.graph).id,
+ inclusive = false,
+ saveState = true
+ )
}
val options = builder.build()
return try {
diff --git a/settings.gradle b/settings.gradle
index 183c95f..48fb65e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -384,6 +384,10 @@
includeProject(":emoji2:emoji2-views", "emoji2/emoji2-views", [BuildType.MAIN])
includeProject(":emoji2:emoji2-views-helper", "emoji2/emoji2-views-helper", [BuildType.MAIN])
includeProject(":emoji2:emoji2-benchmark", "emoji2/emoji2-benchmark", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-disabled-macrobenchmark", "emoji2/integration-tests/init-disabled-macrobenchmark", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-disabled-macrobenchmark-target", "emoji2/integration-tests/init-disabled-macrobenchmark-target", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-enabled-macrobenchmark", "emoji2/integration-tests/init-enabled-macrobenchmark", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-enabled-macrobenchmark-target", "emoji2/integration-tests/init-enabled-macrobenchmark-target", [BuildType.MAIN])
includeProject(":enterprise-feedback", "enterprise/feedback", [BuildType.MAIN])
includeProject(":enterprise-feedback-testing", "enterprise/feedback/testing", [BuildType.MAIN])
includeProject(":exifinterface:exifinterface", "exifinterface/exifinterface", [BuildType.MAIN])
diff --git a/slices/core/src/main/res/values-af/strings.xml b/slices/core/src/main/res/values-af/strings.xml
index 1619490..c6a4763 100644
--- a/slices/core/src/main/res/values-af/strings.xml
+++ b/slices/core/src/main/res/values-af/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Laat <xliff:g id="APP_0">%1$s</xliff:g> toe om <xliff:g id="APP_2">%2$s</xliff:g>-skyfies te wys?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Dit kan inligting in <xliff:g id="APP">%1$s</xliff:g> lees"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Dit kan handelinge binne <xliff:g id="APP">%1$s</xliff:g> uitvoer"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Laat <xliff:g id="APP">%1$s</xliff:g> toe om skyfies uit enige program te wys"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Laat toe"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Weier"</string>
</resources>
diff --git a/slices/core/src/main/res/values-am/strings.xml b/slices/core/src/main/res/values-am/strings.xml
index c187e0f..3fc492a 100644
--- a/slices/core/src/main/res/values-am/strings.xml
+++ b/slices/core/src/main/res/values-am/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን እንዲያሳይ ይፈቀድለት?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ከ<xliff:g id="APP">%1$s</xliff:g> የመጣ መረጃን ማንበብ ይችላል"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- በ<xliff:g id="APP">%1$s</xliff:g> ውስጥ እርምጃዎችን መውሰድ ይችላል"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ከማንኛውም መተግበሪያ የመጡ ቁራጮችን እንዲያሳይ ፍቀድለት"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ፍቀድ"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ከልክል"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ar/strings.xml b/slices/core/src/main/res/values-ar/strings.xml
index b5de01c..2d76a8d 100644
--- a/slices/core/src/main/res/values-ar/strings.xml
+++ b/slices/core/src/main/res/values-ar/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"هل تريد السماح لتطبيق <xliff:g id="APP_0">%1$s</xliff:g> بعرض شرائح <xliff:g id="APP_2">%2$s</xliff:g>؟"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- إمكانية قراءة المعلومات من <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- إمكانية اتخاذ إجراءات داخل <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"السماح لتطبيق <xliff:g id="APP">%1$s</xliff:g> بعرض شرائح من أي تطبيق"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"سماح"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"رفض"</string>
</resources>
diff --git a/slices/core/src/main/res/values-as/strings.xml b/slices/core/src/main/res/values-as/strings.xml
index 95722b9..ea2a877 100644
--- a/slices/core/src/main/res/values-as/strings.xml
+++ b/slices/core/src/main/res/values-as/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>ক <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাবলৈ অনুমতি দিবনে?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ই <xliff:g id="APP">%1$s</xliff:g>ৰ তথ্য পঢ়িব পাৰে"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ই <xliff:g id="APP">%1$s</xliff:g>ৰ ভিতৰত কাৰ্য কৰিব পাৰে"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>ক যিকোনো এপৰ অংশ দেখুওৱাবলৈ অনুমতি দিয়ক"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"অনুমতি দিয়ক"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"অস্বীকাৰ কৰক"</string>
</resources>
diff --git a/slices/core/src/main/res/values-az/strings.xml b/slices/core/src/main/res/values-az/strings.xml
index ff7be30..2f1bce4 100644
--- a/slices/core/src/main/res/values-az/strings.xml
+++ b/slices/core/src/main/res/values-az/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> tətbiqinə <xliff:g id="APP_2">%2$s</xliff:g> hissələrini göstərmək üçün icazə verilsin?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> tətbiqindən məlumat oxuya bilər"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> daxilində əməliyyatlar edə bilər"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> tətbiqinə istənilən tətbiqdən hissə göstərmək icazəsi verin"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"İcazə verin"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rədd edin"</string>
</resources>
diff --git a/slices/core/src/main/res/values-b+sr+Latn/strings.xml b/slices/core/src/main/res/values-b+sr+Latn/strings.xml
index 95c1692c..3e20b9e 100644
--- a/slices/core/src/main/res/values-b+sr+Latn/strings.xml
+++ b/slices/core/src/main/res/values-b+sr+Latn/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Želite li da dozvolite aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Može da čita podatke iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Može da obavlja radnje u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dozvoli aplikaciji <xliff:g id="APP">%1$s</xliff:g> da prikazuje isečke iz bilo koje aplikacije"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dozvoli"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odbij"</string>
</resources>
diff --git a/slices/core/src/main/res/values-be/strings.xml b/slices/core/src/main/res/values-be/strings.xml
index 5fadc3f..84cd4a22 100644
--- a/slices/core/src/main/res/values-be/strings.xml
+++ b/slices/core/src/main/res/values-be/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Дазволіць праграме <xliff:g id="APP_0">%1$s</xliff:g> паказваць фрагменты праграмы <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Можа счытваць інфармацыю з праграмы <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Можа выконваць дзеянні ў праграме <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дазволіць праграме <xliff:g id="APP">%1$s</xliff:g> паказваць фрагменты іншых праграм"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дазволіць"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Адмовіць"</string>
</resources>
diff --git a/slices/core/src/main/res/values-bg/strings.xml b/slices/core/src/main/res/values-bg/strings.xml
index cb6f067..dcd2606 100644
--- a/slices/core/src/main/res/values-bg/strings.xml
+++ b/slices/core/src/main/res/values-bg/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Ще разрешите ли на <xliff:g id="APP_0">%1$s</xliff:g> да показва части от <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Може да чете информация от <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Може да предприема действия в/ъв <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Разрешаване на <xliff:g id="APP">%1$s</xliff:g> да показва части от което и да е приложение"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Разрешаване"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Отказ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-bn/strings.xml b/slices/core/src/main/res/values-bn/strings.xml
index cfa569d..4b46f9b 100644
--- a/slices/core/src/main/res/values-bn/strings.xml
+++ b/slices/core/src/main/res/values-bn/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> অ্যাপটিকে <xliff:g id="APP_2">%2$s</xliff:g> এর অংশ দেখানোর অনুমতি দেবেন?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- এটি <xliff:g id="APP">%1$s</xliff:g> এর তথ্য অ্যাক্সেস করতে পারবে"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- এটি <xliff:g id="APP">%1$s</xliff:g> এর মধ্যে কাজ করতে পারবে"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> অ্যাপটিকে যেকোনও অ্যাপের অংশ দেখাতে দিন"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"অনুমতি দিন"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"খারিজ করুন"</string>
</resources>
diff --git a/slices/core/src/main/res/values-bs/strings.xml b/slices/core/src/main/res/values-bs/strings.xml
index 651e4d6..b203a79 100644
--- a/slices/core/src/main/res/values-bs/strings.xml
+++ b/slices/core/src/main/res/values-bs/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Dozvoliti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> prikazivanje isječaka aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Može čitati informacije iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Može poduzeti radnje u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dozvoli aplikaciji <xliff:g id="APP">%1$s</xliff:g> prikazivanje isječaka iz svake aplikacije"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dozvoli"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odbij"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ca/strings.xml b/slices/core/src/main/res/values-ca/strings.xml
index 0c953cf..ac93488 100644
--- a/slices/core/src/main/res/values-ca/strings.xml
+++ b/slices/core/src/main/res/values-ca/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vols permetre que <xliff:g id="APP_0">%1$s</xliff:g> mostri porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pot llegir informació de l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pot dur a terme accions dins de l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permet que <xliff:g id="APP">%1$s</xliff:g> mostri porcions de qualsevol aplicació"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permet"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Denega"</string>
</resources>
diff --git a/slices/core/src/main/res/values-cs/strings.xml b/slices/core/src/main/res/values-cs/strings.xml
index 6db0934..da1053b 100644
--- a/slices/core/src/main/res/values-cs/strings.xml
+++ b/slices/core/src/main/res/values-cs/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Povolit aplikaci <xliff:g id="APP_0">%1$s</xliff:g> zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Může číst informace z aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Může provádět akce v aplikaci <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Povolit aplikaci <xliff:g id="APP">%1$s</xliff:g> zobrazovat ukázky z libovolné aplikace"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Povolit"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Zamítnout"</string>
</resources>
diff --git a/slices/core/src/main/res/values-da/strings.xml b/slices/core/src/main/res/values-da/strings.xml
index 70f1a22..67a7ed08 100644
--- a/slices/core/src/main/res/values-da/strings.xml
+++ b/slices/core/src/main/res/values-da/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vil du give <xliff:g id="APP_0">%1$s</xliff:g> tilladelse til at vise eksempler fra <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Den kan læse oplysninger fra <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Den kan foretage handlinger i <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Tillad, at <xliff:g id="APP">%1$s</xliff:g> viser eksempler fra enhver app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Tillad"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Afvis"</string>
</resources>
diff --git a/slices/core/src/main/res/values-de/strings.xml b/slices/core/src/main/res/values-de/strings.xml
index 89ce98e..419fa20 100644
--- a/slices/core/src/main/res/values-de/strings.xml
+++ b/slices/core/src/main/res/values-de/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> erlauben, Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzuzeigen?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Darf Informationen aus <xliff:g id="APP">%1$s</xliff:g> lesen"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Darf Aktionen in <xliff:g id="APP">%1$s</xliff:g> ausführen"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> darf Teile aus jeder beliebigen App anzeigen"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Zulassen"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Ablehnen"</string>
</resources>
diff --git a/slices/core/src/main/res/values-el/strings.xml b/slices/core/src/main/res/values-el/strings.xml
index ed0b624..898d2ea 100644
--- a/slices/core/src/main/res/values-el/strings.xml
+++ b/slices/core/src/main/res/values-el/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Να επιτρέπεται στην εφαρμογή <xliff:g id="APP_0">%1$s</xliff:g> να εμφανίζει τμήματα της εφαρμογής <xliff:g id="APP_2">%2$s</xliff:g>;"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Μπορεί να διαβάζει πληροφορίες από την εφαρμογή <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Μπορεί να εκτελεί ενέργειες εντός της εφαρμογής <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Να επιτρέπεται στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> να εμφανίζει τμήματα από οποιαδήποτε εφαρμογή"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Να επιτρέπεται"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Να μην επιτρέπεται"</string>
</resources>
diff --git a/slices/core/src/main/res/values-en-rAU/strings.xml b/slices/core/src/main/res/values-en-rAU/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rAU/strings.xml
+++ b/slices/core/src/main/res/values-en-rAU/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
</resources>
diff --git a/slices/core/src/main/res/values-en-rCA/strings.xml b/slices/core/src/main/res/values-en-rCA/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rCA/strings.xml
+++ b/slices/core/src/main/res/values-en-rCA/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
</resources>
diff --git a/slices/core/src/main/res/values-en-rGB/strings.xml b/slices/core/src/main/res/values-en-rGB/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rGB/strings.xml
+++ b/slices/core/src/main/res/values-en-rGB/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
</resources>
diff --git a/slices/core/src/main/res/values-en-rIN/strings.xml b/slices/core/src/main/res/values-en-rIN/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rIN/strings.xml
+++ b/slices/core/src/main/res/values-en-rIN/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
</resources>
diff --git a/slices/core/src/main/res/values-en-rXC/strings.xml b/slices/core/src/main/res/values-en-rXC/strings.xml
index 5f82056..efb1d5f 100644
--- a/slices/core/src/main/res/values-en-rXC/strings.xml
+++ b/slices/core/src/main/res/values-en-rXC/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
</resources>
diff --git a/slices/core/src/main/res/values-es-rUS/strings.xml b/slices/core/src/main/res/values-es-rUS/strings.xml
index 9db16c5..31746f0 100644
--- a/slices/core/src/main/res/values-es-rUS/strings.xml
+++ b/slices/core/src/main/res/values-es-rUS/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Puede leer información de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Puede realizar acciones en <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> muestre fragmentos de cualquier app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rechazar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-es/strings.xml b/slices/core/src/main/res/values-es/strings.xml
index 44283aa..3a65289 100644
--- a/slices/core/src/main/res/values-es/strings.xml
+++ b/slices/core/src/main/res/values-es/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Puede leer información de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Puede realizar acciones en <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> muestre fragmentos de cualquier aplicación"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Denegar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-et/strings.xml b/slices/core/src/main/res/values-et/strings.xml
index 6c45d68..5462dd7 100644
--- a/slices/core/src/main/res/values-et/strings.xml
+++ b/slices/core/src/main/res/values-et/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Kas lubada rakendusel <xliff:g id="APP_0">%1$s</xliff:g> näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- See saab lugeda teavet rakendusest <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- See saab rakenduses <xliff:g id="APP">%1$s</xliff:g> toiminguid teha"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Luba rakendus <xliff:g id="APP">%1$s</xliff:g>, et kuvada lõike mis tahes rakendusest"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Lubamine"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Keelamine"</string>
</resources>
diff --git a/slices/core/src/main/res/values-eu/strings.xml b/slices/core/src/main/res/values-eu/strings.xml
index e608918..8776fec 100644
--- a/slices/core/src/main/res/values-eu/strings.xml
+++ b/slices/core/src/main/res/values-eu/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakusteko baimena eman nahi diozu <xliff:g id="APP_0">%1$s</xliff:g> aplikazioari?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> aplikazioaren informazioa irakur dezake."</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> aplikazioan ekintzak gauza ditzake."</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Eman aplikazio guztien zatiak erakusteko baimena <xliff:g id="APP">%1$s</xliff:g> aplikazioari"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Baimendu"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Ukatu"</string>
</resources>
diff --git a/slices/core/src/main/res/values-fa/strings.xml b/slices/core/src/main/res/values-fa/strings.xml
index 38a286d..d5e0416 100644
--- a/slices/core/src/main/res/values-fa/strings.xml
+++ b/slices/core/src/main/res/values-fa/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"به <xliff:g id="APP_0">%1$s</xliff:g> اجازه داده شود تکههای <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد؟"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- میتواند اطلاعات <xliff:g id="APP">%1$s</xliff:g> را بخواند"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- میتواند در <xliff:g id="APP">%1$s</xliff:g> اقدام انجام دهد"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"به <xliff:g id="APP">%1$s</xliff:g> اجازه داده شود تکههایی از برنامهها نشان دهد"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"مجاز بودن"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"مجاز نبودن"</string>
</resources>
diff --git a/slices/core/src/main/res/values-fi/strings.xml b/slices/core/src/main/res/values-fi/strings.xml
index 1cc8122..d682f1a 100644
--- a/slices/core/src/main/res/values-fi/strings.xml
+++ b/slices/core/src/main/res/values-fi/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Saako <xliff:g id="APP_0">%1$s</xliff:g> näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Se voi lukea tietoja sovelluksesta <xliff:g id="APP">%1$s</xliff:g>."</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Se voi suorittaa toimintoja sovelluksessa <xliff:g id="APP">%1$s</xliff:g>."</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Salli sovelluksen <xliff:g id="APP">%1$s</xliff:g> näyttää osia mistä tahansa sovelluksesta"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Salli"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Estä"</string>
</resources>
diff --git a/slices/core/src/main/res/values-fr-rCA/strings.xml b/slices/core/src/main/res/values-fr-rCA/strings.xml
index d92e649..a0e55d3 100644
--- a/slices/core/src/main/res/values-fr-rCA/strings.xml
+++ b/slices/core/src/main/res/values-fr-rCA/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher <xliff:g id="APP_2">%2$s</xliff:g> tranches?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Il peut lire de l\'information de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Il peut effectuer des actions dans <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à afficher des tranches de n\'importe quelle application"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Autoriser"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuser"</string>
</resources>
diff --git a/slices/core/src/main/res/values-fr/strings.xml b/slices/core/src/main/res/values-fr/strings.xml
index 9edcb27..65f06bc 100644
--- a/slices/core/src/main/res/values-fr/strings.xml
+++ b/slices/core/src/main/res/values-fr/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g> ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Accès aux informations de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Capacité d\'action dans <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à afficher des éléments de n\'importe quelle application"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Autoriser"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuser"</string>
</resources>
diff --git a/slices/core/src/main/res/values-gl/strings.xml b/slices/core/src/main/res/values-gl/strings.xml
index 617e5fc..50befe5 100644
--- a/slices/core/src/main/res/values-gl/strings.xml
+++ b/slices/core/src/main/res/values-gl/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Queres permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre fragmentos de aplicación de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler información da aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode levar a cabo accións dentro da aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> mostre fragmentos de calquera aplicación"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Denegar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-gu/strings.xml b/slices/core/src/main/res/values-gu/strings.xml
index 4e8fcb8..804b774 100644
--- a/slices/core/src/main/res/values-gu/strings.xml
+++ b/slices/core/src/main/res/values-gu/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>ને <xliff:g id="APP_2">%2$s</xliff:g> સ્લાઇસ બતાવવાની મંજૂરી આપીએ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- તે <xliff:g id="APP">%1$s</xliff:g>માંથી માહિતી વાંચી શકે છે"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- તે <xliff:g id="APP">%1$s</xliff:g>ની અંદર ક્રિયાઓ કરી શકે છે"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>ને કોઈપણ ઍપના સ્લાઇસ બતાવવાની મંજૂરી આપો"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"મંજૂરી આપો"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"નકારો"</string>
</resources>
diff --git a/slices/core/src/main/res/values-hi/strings.xml b/slices/core/src/main/res/values-hi/strings.xml
index 23e4d3f..9dfe2b0 100644
--- a/slices/core/src/main/res/values-hi/strings.xml
+++ b/slices/core/src/main/res/values-hi/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> को <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाने की मंज़ूरी दें?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- यह <xliff:g id="APP">%1$s</xliff:g> से जानकारी पा सकता है"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- यह <xliff:g id="APP">%1$s</xliff:g> में कार्रवाई कर सकता है"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> काे किसी भी ऐप्लिकेशन के हिस्से (स्लाइस) दिखाने की मंज़ूरी दें"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"अनुमति दें"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"अनुमति न दें"</string>
</resources>
diff --git a/slices/core/src/main/res/values-hr/strings.xml b/slices/core/src/main/res/values-hr/strings.xml
index e0bffaa..9e614b5 100644
--- a/slices/core/src/main/res/values-hr/strings.xml
+++ b/slices/core/src/main/res/values-hr/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Želite li dopustiti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– može čitati informacije aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– može vršiti radnje u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dopusti aplikaciji <xliff:g id="APP">%1$s</xliff:g> da prikazuje isječke iz bilo koje aplikacije"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dopusti"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odbij"</string>
</resources>
diff --git a/slices/core/src/main/res/values-hu/strings.xml b/slices/core/src/main/res/values-hu/strings.xml
index 4601a88..004829f 100644
--- a/slices/core/src/main/res/values-hu/strings.xml
+++ b/slices/core/src/main/res/values-hu/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Engedélyezi a(z) <xliff:g id="APP_0">%1$s</xliff:g> alkalmazásnak, hogy részleteket mutasson a(z) <xliff:g id="APP_2">%2$s</xliff:g> alkalmazásból?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Információkat olvashat a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásból"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Műveleteket végezhet a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazáson belül"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Engedélyezi a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásnak, hogy bármely alkalmazásból részletet jelenítsen meg"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Engedélyezés"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Elutasítás"</string>
</resources>
diff --git a/slices/core/src/main/res/values-hy/strings.xml b/slices/core/src/main/res/values-hy/strings.xml
index c8850ff..1d716d7 100644
--- a/slices/core/src/main/res/values-hy/strings.xml
+++ b/slices/core/src/main/res/values-hy/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Թույլատրե՞լ <xliff:g id="APP_0">%1$s</xliff:g> հավելվածին ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Կարող է կարդալ տեղեկություններ <xliff:g id="APP">%1$s</xliff:g> հավելվածից"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Կարող է կատարել գործողություններ <xliff:g id="APP">%1$s</xliff:g> հավելվածում"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Թույլատրել <xliff:g id="APP">%1$s</xliff:g> հավելվածին ցուցադրել հատվածներ ցանկացած հավելվածից"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Թույլատրել"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Մերժել"</string>
</resources>
diff --git a/slices/core/src/main/res/values-in/strings.xml b/slices/core/src/main/res/values-in/strings.xml
index a68a7c7..860ed1c 100644
--- a/slices/core/src/main/res/values-in/strings.xml
+++ b/slices/core/src/main/res/values-in/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Izinkan <xliff:g id="APP_0">%1$s</xliff:g> menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Dapat membaca informasi dari <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Dapat mengambil tindakan di dalam <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Izinkan <xliff:g id="APP">%1$s</xliff:g> menampilkan potongan dari aplikasi apa pun"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Izinkan"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Tolak"</string>
</resources>
diff --git a/slices/core/src/main/res/values-is/strings.xml b/slices/core/src/main/res/values-is/strings.xml
index 24f3ace..da79a76 100644
--- a/slices/core/src/main/res/values-is/strings.xml
+++ b/slices/core/src/main/res/values-is/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Viltu leyfa <xliff:g id="APP_0">%1$s</xliff:g> að sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Það getur lesið upplýsingar úr <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Það getur gripið til aðgerða í <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Leyfa <xliff:g id="APP">%1$s</xliff:g> að sýna sneiðar úr hvaða forriti sem er"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Leyfa"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Hafna"</string>
</resources>
diff --git a/slices/core/src/main/res/values-it/strings.xml b/slices/core/src/main/res/values-it/strings.xml
index 333547d..5518d66 100644
--- a/slices/core/src/main/res/values-it/strings.xml
+++ b/slices/core/src/main/res/values-it/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vuoi consentire all\'app <xliff:g id="APP_0">%1$s</xliff:g> di mostrare porzioni dell\'app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Può leggere informazioni dell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Può compiere azioni nell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Consenti all\'app <xliff:g id="APP">%1$s</xliff:g> di mostrare porzioni di qualsiasi app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Consenti"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rifiuta"</string>
</resources>
diff --git a/slices/core/src/main/res/values-iw/strings.xml b/slices/core/src/main/res/values-iw/strings.xml
index 7534117..2151548 100644
--- a/slices/core/src/main/res/values-iw/strings.xml
+++ b/slices/core/src/main/res/values-iw/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"האם לאפשר ל-<xliff:g id="APP_0">%1$s</xliff:g> להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- תהיה לה אפשרות לקרוא מידע מהאפליקציה <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- תהיה לה יכולת לנקוט פעולה בתוך <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"יש לאשר לאפליקציית <xliff:g id="APP">%1$s</xliff:g> להציג חלקים מכל אפליקציה שהיא"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"אישור"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"אני לא מרשה"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ja/strings.xml b/slices/core/src/main/res/values-ja/strings.xml
index 315326f..1c28f4c 100644
--- a/slices/core/src/main/res/values-ja/strings.xml
+++ b/slices/core/src/main/res/values-ja/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> のスライスの表示を <xliff:g id="APP_0">%1$s</xliff:g> に許可しますか?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> からの情報を読み取ることができます"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> 内部で操作することがあります"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"すべてのアプリのスライスを表示することを <xliff:g id="APP">%1$s</xliff:g> に許可する"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"許可"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒否"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ka/strings.xml b/slices/core/src/main/res/values-ka/strings.xml
index f21d1be..b78206c 100644
--- a/slices/core/src/main/res/values-ka/strings.xml
+++ b/slices/core/src/main/res/values-ka/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"ანიჭებთ ნებართვას <xliff:g id="APP_0">%1$s</xliff:g>-ს, აჩვენოს <xliff:g id="APP_2">%2$s</xliff:g>-ის ფრაგმენტები?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- მას შეუძლია ინფორმაციის <xliff:g id="APP">%1$s</xliff:g>-დან წაკითხვა"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- მას შეუძლია ქმედებების <xliff:g id="APP">%1$s</xliff:g>-ში განხორციელება"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>-ისთვის ფრაგმენტების ნებისმიერი აპიდან ჩვენების ნების დართვა"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"დაშვება"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"უარყოფა"</string>
</resources>
diff --git a/slices/core/src/main/res/values-kk/strings.xml b/slices/core/src/main/res/values-kk/strings.xml
index aa9409f..fcb0509 100644
--- a/slices/core/src/main/res/values-kk/strings.xml
+++ b/slices/core/src/main/res/values-kk/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасына <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсетуге рұқсат берілсін бе?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> қолданбасындағы ақпаратты оқи алады"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> қолданбасында әрекет ете алады"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> қолданбасына кез келген қолданбаның үзіндісін көрсетуге рұқсат беру"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Рұқсат беру"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Тыйым салу"</string>
</resources>
diff --git a/slices/core/src/main/res/values-km/strings.xml b/slices/core/src/main/res/values-km/strings.xml
index a98f409..5d0a988 100644
--- a/slices/core/src/main/res/values-km/strings.xml
+++ b/slices/core/src/main/res/values-km/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"អនុញ្ញាតឱ្យ <xliff:g id="APP_0">%1$s</xliff:g> បង្ហាញស្ថិតិប្រើប្រាស់របស់ <xliff:g id="APP_2">%2$s</xliff:g> ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- វាអាចអានព័ត៌មានពី <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- វាអាចធ្វើសកម្មភាពនៅក្នុង <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"អនុញ្ញាតឱ្យ <xliff:g id="APP">%1$s</xliff:g> បង្ហាញស្ថិតិប្រើប្រាស់ពីកម្មវិធីនានា"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"អនុញ្ញាត"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"បដិសេធ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-kn/strings.xml b/slices/core/src/main/res/values-kn/strings.xml
index e8a0559..7fe32e0 100644
--- a/slices/core/src/main/res/values-kn/strings.xml
+++ b/slices/core/src/main/res/values-kn/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP_0">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ನಿಂದ ಮಾಹಿತಿಯನ್ನು ಓದಬಹುದು"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ಒಳಗಡೆ ಕ್ರಿಯೆಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಬಹುದು"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್ನಿಂದ ಸ್ಲೈಸ್ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸಿ"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ಅನುಮತಿಸಿ"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ನಿರಾಕರಿಸಿ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ko/strings.xml b/slices/core/src/main/res/values-ko/strings.xml
index cc88f25..93c62f0 100644
--- a/slices/core/src/main/res/values-ko/strings.xml
+++ b/slices/core/src/main/res/values-ko/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하도록 허용하시겠습니까?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g>의 정보를 읽을 수 있음"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g>에서 작업할 수 있음"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>에서 모든 앱의 슬라이스를 표시하도록 허용"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"허용"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"거부"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ky/strings.xml b/slices/core/src/main/res/values-ky/strings.xml
index 99ce121..9615740 100644
--- a/slices/core/src/main/res/values-ky/strings.xml
+++ b/slices/core/src/main/res/values-ky/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосуна <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөтүүгө уруксат берилсинби?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунун маалыматын окуйт"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунда аракеттерди аткарат"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> бардык колдонмолордун үлгүлөрүн көрсөтүүгө уруксат берүү"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Уруксат берүү"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Тыюу салынат"</string>
</resources>
diff --git a/slices/core/src/main/res/values-lo/strings.xml b/slices/core/src/main/res/values-lo/strings.xml
index 74bb119..9b18f5b 100644
--- a/slices/core/src/main/res/values-lo/strings.xml
+++ b/slices/core/src/main/res/values-lo/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"ອະນຸຍາດ <xliff:g id="APP_0">%1$s</xliff:g> ໃຫ້ສະແດງ <xliff:g id="APP_2">%2$s</xliff:g> ສະໄລ້ບໍ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ມັນສາມາດອ່ານຂໍ້ມູນຈາກ <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ມັນສາມາດໃຊ້ຄຳສັ່ງພາຍໃນ <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ອະນຸຍາດ <xliff:g id="APP">%1$s</xliff:g> ເພື່ອສະແດງສະໄລ້ຈາກແອັບ"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ອະນຸຍາດ"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ປະຕິເສດ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-lt/strings.xml b/slices/core/src/main/res/values-lt/strings.xml
index 50eb4a6..1e88cd6 100644
--- a/slices/core/src/main/res/values-lt/strings.xml
+++ b/slices/core/src/main/res/values-lt/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Leisti „<xliff:g id="APP_0">%1$s</xliff:g>“ rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Gali nuskaityti informaciją iš „<xliff:g id="APP">%1$s</xliff:g>“"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Gali imtis veiksmų programoje „<xliff:g id="APP">%1$s</xliff:g>“"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Leisti „<xliff:g id="APP">%1$s</xliff:g>“ rodyti bet kurios programos fragmentus"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Leisti"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Atmesti"</string>
</resources>
diff --git a/slices/core/src/main/res/values-lv/strings.xml b/slices/core/src/main/res/values-lv/strings.xml
index 4bfb688..1f3ccde 100644
--- a/slices/core/src/main/res/values-lv/strings.xml
+++ b/slices/core/src/main/res/values-lv/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vai atļaut lietotnei <xliff:g id="APP_0">%1$s</xliff:g> rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Var lasīt informāciju no lietotnes <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Var veikt darbības lietotnē <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Atļaut lietotnei <xliff:g id="APP">%1$s</xliff:g> rādīt sadaļas no jebkuras lietotnes"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Atļaut"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Neatļaut"</string>
</resources>
diff --git a/slices/core/src/main/res/values-mk/strings.xml b/slices/core/src/main/res/values-mk/strings.xml
index 2525781..a5ccc53 100644
--- a/slices/core/src/main/res/values-mk/strings.xml
+++ b/slices/core/src/main/res/values-mk/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Да се дозволи <xliff:g id="APP_0">%1$s</xliff:g> да прикажува делови од <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Може да чита информации од <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Може да презема дејства во <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дозволете <xliff:g id="APP">%1$s</xliff:g> да прикажува делови од која било апликација"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дозволете"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Одбијте"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ml/strings.xml b/slices/core/src/main/res/values-ml/strings.xml
index ce177d2..21802b7 100644
--- a/slices/core/src/main/res/values-ml/strings.xml
+++ b/slices/core/src/main/res/values-ml/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP_0">%1$s</xliff:g>-നെ അനുവദിക്കണോ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ഇതിന് <xliff:g id="APP">%1$s</xliff:g>-ൽ നിന്ന് വിവരങ്ങൾ വായിക്കാനാകും"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ഇതിന് <xliff:g id="APP">%1$s</xliff:g>-നുള്ളിൽ പ്രവർത്തനങ്ങൾ ചെയ്യാനാകും"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ഏത് ആപ്പിൽ നിന്നും സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP">%1$s</xliff:g>-നെ അനുവദിക്കുക"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"അനുവദിക്കുക"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"നിരസിക്കുക"</string>
</resources>
diff --git a/slices/core/src/main/res/values-mn/strings.xml b/slices/core/src/main/res/values-mn/strings.xml
index 50764d9..8e0661a 100644
--- a/slices/core/src/main/res/values-mn/strings.xml
+++ b/slices/core/src/main/res/values-mn/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>-д <xliff:g id="APP_2">%2$s</xliff:g>-н хэсгүүдийг харуулахыг зөвшөөрөх үү?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Энэ <xliff:g id="APP">%1$s</xliff:g>-с мэдээлэл унших боломжтой"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Энэ <xliff:g id="APP">%1$s</xliff:g> дотор үйлдэл хийх боломжтой"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>-д дурын аппаас хэсэг харуулахыг зөвшөөрөх"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Зөвшөөрөх"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Татгалзах"</string>
</resources>
diff --git a/slices/core/src/main/res/values-mr/strings.xml b/slices/core/src/main/res/values-mr/strings.xml
index f938652..ab48521 100644
--- a/slices/core/src/main/res/values-mr/strings.xml
+++ b/slices/core/src/main/res/values-mr/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवण्याची अनुमती द्यायची का?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ते <xliff:g id="APP">%1$s</xliff:g> ची माहिती वाचू शकते"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ते <xliff:g id="APP">%1$s</xliff:g> मध्ये कृती करू शकते"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ला कुठल्याही अॅपमधील तुकडे दाखवण्याची अनुमती द्या"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"अनुमती द्या"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"नकार द्या"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ms/strings.xml b/slices/core/src/main/res/values-ms/strings.xml
index af2a67e..4b20a63 100644
--- a/slices/core/src/main/res/values-ms/strings.xml
+++ b/slices/core/src/main/res/values-ms/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Benarkan <xliff:g id="APP_0">%1$s</xliff:g> menunjukkan hirisan <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Hos hirisan boleh membaca maklumat daripada <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Hos hirisan boleh mengambil tindakan dalam <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Benarkan <xliff:g id="APP">%1$s</xliff:g> menunjukkan hirisan daripada mana-mana apl"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Benarkan"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Tolak"</string>
</resources>
diff --git a/slices/core/src/main/res/values-my/strings.xml b/slices/core/src/main/res/values-my/strings.xml
index 0cf5d3e..666640ef 100644
--- a/slices/core/src/main/res/values-my/strings.xml
+++ b/slices/core/src/main/res/values-my/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> အား <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များ ပြသခွင့်ပြုပါသလား။"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ၎င်းသည် <xliff:g id="APP">%1$s</xliff:g> မှ အချက်အလက်ကို ဖတ်နိုင်သည်"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ၎င်းသည် <xliff:g id="APP">%1$s</xliff:g> အတွင်း လုပ်ဆောင်ချက်များ ပြုလုပ်နိုင်သည်"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"မည်သည့်အက်ပ်မဆိုမှ အချပ်များ ပြသရန်အတွက် <xliff:g id="APP">%1$s</xliff:g> ကို ခွင့်ပြုရန်"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ခွင့်ပြုရန်"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ငြင်းပယ်ရန်"</string>
</resources>
diff --git a/slices/core/src/main/res/values-nb/strings.xml b/slices/core/src/main/res/values-nb/strings.xml
index 634eac3..9448d03 100644
--- a/slices/core/src/main/res/values-nb/strings.xml
+++ b/slices/core/src/main/res/values-nb/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vil du tillate at <xliff:g id="APP_0">%1$s</xliff:g> viser <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Den kan lese informasjon fra <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Den kan utføre handlinger i <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Tillat at <xliff:g id="APP">%1$s</xliff:g> viser utsnitt fra alle apper"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Tillat"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Ikke tillat"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ne/strings.xml b/slices/core/src/main/res/values-ne/strings.xml
index adf3a1b..bfa42445 100644
--- a/slices/core/src/main/res/values-ne/strings.xml
+++ b/slices/core/src/main/res/values-ne/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> लाई <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन अनुमति दिने हो?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- यसले <xliff:g id="APP">%1$s</xliff:g> को जानकारी पढ्न सक्छ"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- यसले <xliff:g id="APP">%1$s</xliff:g> भित्र कारबाही गर्न सक्छ"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> लाई सबै एपका स्लाइसहरू देखाउन अनुमति दिनुहोस्"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"अनुमति दिनुहोस्"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"नदिने"</string>
</resources>
diff --git a/slices/core/src/main/res/values-nl/strings.xml b/slices/core/src/main/res/values-nl/strings.xml
index 46b2c9f..4d8006e 100644
--- a/slices/core/src/main/res/values-nl/strings.xml
+++ b/slices/core/src/main/res/values-nl/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> toestaan om segmenten van <xliff:g id="APP_2">%2$s</xliff:g> te tonen?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Deze kan informatie lezen van <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Deze kan acties uitvoeren in <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> toestaan om segmenten van apps te tonen"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Toestaan"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Weigeren"</string>
</resources>
diff --git a/slices/core/src/main/res/values-or/strings.xml b/slices/core/src/main/res/values-or/strings.xml
index a87a506..a757613 100644
--- a/slices/core/src/main/res/values-or/strings.xml
+++ b/slices/core/src/main/res/values-or/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> ସ୍ଲାଇସ୍କୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_0">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ଏହା <xliff:g id="APP">%1$s</xliff:g>ରୁ ସୂଚନାକୁ ପଢ଼ିପାରିବ"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ଏହା <xliff:g id="APP">%1$s</xliff:g> ଭିତରେ କାମ କରିପାରିବ"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ଯେକୌଣସି ଆପ୍ରେ ସ୍ଲାଇସ୍କୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP">%1$s</xliff:g>କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ଅଗ୍ରାହ୍ୟ କରନ୍ତୁ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-pa/strings.xml b/slices/core/src/main/res/values-pa/strings.xml
index e1ef272..d68cc39 100644
--- a/slices/core/src/main/res/values-pa/strings.xml
+++ b/slices/core/src/main/res/values-pa/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"ਕੀ <xliff:g id="APP_0">%1$s</xliff:g> ਨੂੰ <xliff:g id="APP_2">%2$s</xliff:g> ਦੇ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦੇਣੇ ਹਨ?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ਇਹ <xliff:g id="APP">%1$s</xliff:g> ਵਿੱਚੋਂ ਜਾਣਕਾਰੀ ਪੜ੍ਹ ਸਕਦਾ ਹੈ"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ਇਸ <xliff:g id="APP">%1$s</xliff:g> ਦੇ ਅੰਦਰ ਕਾਰਵਾਈਆਂ ਕਰ ਸਕਦਾ ਹੈ"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ਨੂੰ ਕਿਸੇ ਵੀ ਐਪ ਵਿੱਚੋਂ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦਿਓ"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ਕਰਨ ਦਿਓ"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ਅਸਵੀਕਾਰ ਕਰੋ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-pl/strings.xml b/slices/core/src/main/res/values-pl/strings.xml
index f66d32c..ea32902 100644
--- a/slices/core/src/main/res/values-pl/strings.xml
+++ b/slices/core/src/main/res/values-pl/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Zezwolić aplikacji <xliff:g id="APP_0">%1$s</xliff:g> na pokazywanie wycinków z aplikacji <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Może odczytywać informacje z aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Może wykonywać działania w aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Zezwalaj aplikacji <xliff:g id="APP">%1$s</xliff:g> na pokazywanie wycinków z dowolnych aplikacji"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Zezwól"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odmów"</string>
</resources>
diff --git a/slices/core/src/main/res/values-pt-rBR/strings.xml b/slices/core/src/main/res/values-pt-rBR/strings.xml
index 246fc77..b58e786 100644
--- a/slices/core/src/main/res/values-pt-rBR/strings.xml
+++ b/slices/core/src/main/res/values-pt-rBR/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode realizar ações no app <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> mostre partes de qualquer app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Negar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-pt-rPT/strings.xml b/slices/core/src/main/res/values-pt-rPT/strings.xml
index 472dce8..832b5e5 100644
--- a/slices/core/src/main/res/values-pt-rPT/strings.xml
+++ b/slices/core/src/main/res/values-pt-rPT/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Permitir que a app <xliff:g id="APP_0">%1$s</xliff:g> mostre partes da app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler informações da app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode realizar ações na app <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que a app <xliff:g id="APP">%1$s</xliff:g> mostre partes de qualquer app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Recusar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-pt/strings.xml b/slices/core/src/main/res/values-pt/strings.xml
index 246fc77..b58e786 100644
--- a/slices/core/src/main/res/values-pt/strings.xml
+++ b/slices/core/src/main/res/values-pt/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode realizar ações no app <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> mostre partes de qualquer app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Negar"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ro/strings.xml b/slices/core/src/main/res/values-ro/strings.xml
index f4b1135..2db5e57 100644
--- a/slices/core/src/main/res/values-ro/strings.xml
+++ b/slices/core/src/main/res/values-ro/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Permiteți <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Poate citi informații din <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Poate efectua acțiuni în <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permiteți <xliff:g id="APP">%1$s</xliff:g> să afișeze porțiuni din orice aplicație"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permiteți"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuzați"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ru/strings.xml b/slices/core/src/main/res/values-ru/strings.xml
index 43e261f..89dafbc 100644
--- a/slices/core/src/main/res/values-ru/strings.xml
+++ b/slices/core/src/main/res/values-ru/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Разрешить приложению \"<xliff:g id="APP_0">%1$s</xliff:g>\" показывать фрагменты приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Ему станут доступны данные из приложения \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Оно сможет совершать действия в приложении \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Разрешить приложению \"<xliff:g id="APP">%1$s</xliff:g>\" показывать фрагменты других приложений"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Да"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Нет"</string>
</resources>
diff --git a/slices/core/src/main/res/values-si/strings.xml b/slices/core/src/main/res/values-si/strings.xml
index 70816e1..e5e8478 100644
--- a/slices/core/src/main/res/values-si/strings.xml
+++ b/slices/core/src/main/res/values-si/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට ඉඩ දෙන්නද?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- එයට <xliff:g id="APP">%1$s</xliff:g> වෙතින් තොරතුරු කියවිය හැකිය"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- එයට <xliff:g id="APP">%1$s</xliff:g> ඇතුළත ක්රියාමාර්ග ගත හැකිය"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ඕනෑම යෙදුමකින් කොටස් පෙන්වීමට <xliff:g id="APP">%1$s</xliff:g> හට ඉඩ දෙන්න"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"ඉඩ දෙන්න"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ප්රතික්ෂේප කර."</string>
</resources>
diff --git a/slices/core/src/main/res/values-sk/strings.xml b/slices/core/src/main/res/values-sk/strings.xml
index 4937a6c..8070718 100644
--- a/slices/core/src/main/res/values-sk/strings.xml
+++ b/slices/core/src/main/res/values-sk/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Povoliť aplikácii <xliff:g id="APP_0">%1$s</xliff:g> zobrazovať rezy z aplikácie <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Môže čítať informácie z aplikácie <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Môže vykonávať akcie v aplikácii <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Povoliť aplikácii <xliff:g id="APP">%1$s</xliff:g> zobrazovať rezy z ľubovoľnej aplikácie"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Povoliť"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Zamietnuť"</string>
</resources>
diff --git a/slices/core/src/main/res/values-sl/strings.xml b/slices/core/src/main/res/values-sl/strings.xml
index 30f67c4..b3ab6f4 100644
--- a/slices/core/src/main/res/values-sl/strings.xml
+++ b/slices/core/src/main/res/values-sl/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Ali aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> dovolite prikazovanje izrezov iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– lahko bere podatke v aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– lahko izvaja dejanja v aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dovoli, da aplikacija <xliff:g id="APP">%1$s</xliff:g> prikaže izreze iz poljubne aplikacije"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dovoli"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Zavrni"</string>
</resources>
diff --git a/slices/core/src/main/res/values-sq/strings.xml b/slices/core/src/main/res/values-sq/strings.xml
index 47ce934..9ae77dd 100644
--- a/slices/core/src/main/res/values-sq/strings.xml
+++ b/slices/core/src/main/res/values-sq/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Të lejohet <xliff:g id="APP_0">%1$s</xliff:g> që të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Mund të lexojë informacion nga <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Mund të ndërmarrë veprime brenda <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Lejo <xliff:g id="APP">%1$s</xliff:g> për të shfaqur pjesë nga çdo aplikacion"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Lejo"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuzo"</string>
</resources>
diff --git a/slices/core/src/main/res/values-sr/strings.xml b/slices/core/src/main/res/values-sr/strings.xml
index e059b1c..b7c29c5 100644
--- a/slices/core/src/main/res/values-sr/strings.xml
+++ b/slices/core/src/main/res/values-sr/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Желите ли да дозволите апликацији <xliff:g id="APP_0">%1$s</xliff:g> да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Може да чита податке из апликације <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Може да обавља радње у апликацији <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дозволи апликацији <xliff:g id="APP">%1$s</xliff:g> да приказује исечке из било које апликације"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дозволи"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Одбиј"</string>
</resources>
diff --git a/slices/core/src/main/res/values-sv/strings.xml b/slices/core/src/main/res/values-sv/strings.xml
index e065cb3..9be28b3 100644
--- a/slices/core/src/main/res/values-sv/strings.xml
+++ b/slices/core/src/main/res/values-sv/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Tillåter du att bitar av <xliff:g id="APP_2">%2$s</xliff:g> visas i <xliff:g id="APP_0">%1$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Kan läsa information från <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Kan vidta åtgärder i <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Tillåt att bitar av vilken app som helst visas i <xliff:g id="APP">%1$s</xliff:g>"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Tillåt"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Neka"</string>
</resources>
diff --git a/slices/core/src/main/res/values-sw/strings.xml b/slices/core/src/main/res/values-sw/strings.xml
index 2365547..28459cd 100644
--- a/slices/core/src/main/res/values-sw/strings.xml
+++ b/slices/core/src/main/res/values-sw/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Ungependa kuruhusu <xliff:g id="APP_0">%1$s</xliff:g> ionyeshe vipengee <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Inaweza kusoma maelezo kutoka <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Inaweza kuchukua hatua ndani ya <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Ruhusu <xliff:g id="APP">%1$s</xliff:g> ionyeshe vipengee kutoka programu yoyote"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Ruhusu"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Kataa"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ta/strings.xml b/slices/core/src/main/res/values-ta/strings.xml
index 0c8d874..36be75a 100644
--- a/slices/core/src/main/res/values-ta/strings.xml
+++ b/slices/core/src/main/res/values-ta/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> ஆப்ஸின் விழிப்பூட்டல்களைக் காண்பிக்க, <xliff:g id="APP_0">%1$s</xliff:g> ஆப்ஸை அனுமதிக்கவா?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- இது, <xliff:g id="APP">%1$s</xliff:g> பயன்பாட்டிலிருக்கும் தகவலைப் படிக்கும்"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- இது, <xliff:g id="APP">%1$s</xliff:g> பயன்பாட்டிற்குள் செயல்பாடுகளில் ஈடுபடும்"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"எந்தப் பயன்பாட்டிலிருந்தும் விழிப்பூட்டல்களைக் காண்பிக்க, <xliff:g id="APP">%1$s</xliff:g> ஆப்ஸை அனுமதி"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"அனுமதி"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"நிராகரி"</string>
</resources>
diff --git a/slices/core/src/main/res/values-te/strings.xml b/slices/core/src/main/res/values-te/strings.xml
index 3f89a75..ce51a47 100644
--- a/slices/core/src/main/res/values-te/strings.xml
+++ b/slices/core/src/main/res/values-te/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> స్లైస్లను చూపించడానికి <xliff:g id="APP_0">%1$s</xliff:g>ని అనుమతించాలా?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ఇది <xliff:g id="APP">%1$s</xliff:g> నుండి సమాచారాన్ని చదవగలుగుతుంది"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ఇది <xliff:g id="APP">%1$s</xliff:g> లోపల చర్యలు తీసుకోగలుగుతుంది"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ఏ యాప్ నుండి అయినా స్లైస్లను చూపించడానికి <xliff:g id="APP">%1$s</xliff:g>ని అనుమతించండి"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"అనుమతించు"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"తిరస్కరించు"</string>
</resources>
diff --git a/slices/core/src/main/res/values-th/strings.xml b/slices/core/src/main/res/values-th/strings.xml
index afc42f2..07371a0 100644
--- a/slices/core/src/main/res/values-th/strings.xml
+++ b/slices/core/src/main/res/values-th/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"อนุญาตให้ <xliff:g id="APP_0">%1$s</xliff:g> แสดงส่วนต่างๆ ของ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- อ่านข้อมูลจาก <xliff:g id="APP">%1$s</xliff:g> ได้"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ดำเนินการใน <xliff:g id="APP">%1$s</xliff:g> ได้"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"อนุญาตให้ <xliff:g id="APP">%1$s</xliff:g> แสดงส่วนต่างๆ จากแอปใดก็ได้"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"อนุญาต"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"ปฏิเสธ"</string>
</resources>
diff --git a/slices/core/src/main/res/values-tl/strings.xml b/slices/core/src/main/res/values-tl/strings.xml
index aa9d7b4..e4bf3e0 100644
--- a/slices/core/src/main/res/values-tl/strings.xml
+++ b/slices/core/src/main/res/values-tl/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Payagan ang <xliff:g id="APP_0">%1$s</xliff:g> na ipakita ang mga slice ng <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Nakakabasa ito ng impormasyon mula sa <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Nakakagawa ito ng mga pagkilos sa loob ng <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Payagan ang <xliff:g id="APP">%1$s</xliff:g> na ipakita ang mga slice mula sa anumang app"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Payagan"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Tanggihan"</string>
</resources>
diff --git a/slices/core/src/main/res/values-tr/strings.xml b/slices/core/src/main/res/values-tr/strings.xml
index 216a87c..2cbcfda 100644
--- a/slices/core/src/main/res/values-tr/strings.xml
+++ b/slices/core/src/main/res/values-tr/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> uygulamasının, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermesine izin verilsin mi?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> uygulamasından bilgileri okuyabilir"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> uygulamasında işlem yapabilir"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> uygulamasının tüm uygulamalardan dilimleri göstermesine izin ver"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"İzin ver"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Reddet"</string>
</resources>
diff --git a/slices/core/src/main/res/values-uk/strings.xml b/slices/core/src/main/res/values-uk/strings.xml
index e908f43..b842f5c 100644
--- a/slices/core/src/main/res/values-uk/strings.xml
+++ b/slices/core/src/main/res/values-uk/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Дозволити додатку <xliff:g id="APP_0">%1$s</xliff:g> показувати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Може переглядати інформацію з додатка <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Може виконувати дії в додатку <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дозволити додатку <xliff:g id="APP">%1$s</xliff:g> показувати фрагменти будь-якого додатка"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дозволити"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Заборонити"</string>
</resources>
diff --git a/slices/core/src/main/res/values-ur/strings.xml b/slices/core/src/main/res/values-ur/strings.xml
index 0e97e52..8999e25 100644
--- a/slices/core/src/main/res/values-ur/strings.xml
+++ b/slices/core/src/main/res/values-ur/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> کو <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانے کی اجازت دیں؟"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- یہ <xliff:g id="APP">%1$s</xliff:g> کی معلومات پڑھ سکتا ہے"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- یہ <xliff:g id="APP">%1$s</xliff:g> کے اندر کارروائیاں کر سکتا ہے"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> کو کسی بھی ایپ سے سلائسز دکھانے کی اجازت دیں"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"اجازت دیں"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"مسترد کریں"</string>
</resources>
diff --git a/slices/core/src/main/res/values-uz/strings.xml b/slices/core/src/main/res/values-uz/strings.xml
index 8de5b2e..9fcb4ce 100644
--- a/slices/core/src/main/res/values-uz/strings.xml
+++ b/slices/core/src/main/res/values-uz/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasiga <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatishga ruxsat berilsinmi?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– <xliff:g id="APP">%1$s</xliff:g> ma’lumotlarini o‘qiy oladi"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– <xliff:g id="APP">%1$s</xliff:g> ichida amallar bajara oladi"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ilovasiga boshqa ilovalardan fragmentlarni ko‘rsatishga ruxsat berish"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Ruxsat"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rad etish"</string>
</resources>
diff --git a/slices/core/src/main/res/values-vi/strings.xml b/slices/core/src/main/res/values-vi/strings.xml
index 8ecd246..c067ac8 100644
--- a/slices/core/src/main/res/values-vi/strings.xml
+++ b/slices/core/src/main/res/values-vi/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Cho phép <xliff:g id="APP_0">%1$s</xliff:g> hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Có thể đọc thông tin từ <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Có thể thực hiện hành động bên trong <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Cho phép <xliff:g id="APP">%1$s</xliff:g> hiển thị các lát từ mọi ứng dụng"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Cho phép"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Từ chối"</string>
</resources>
diff --git a/slices/core/src/main/res/values-zh-rCN/strings.xml b/slices/core/src/main/res/values-zh-rCN/strings.xml
index 3a99e82..6ab2d3e 100644
--- a/slices/core/src/main/res/values-zh-rCN/strings.xml
+++ b/slices/core/src/main/res/values-zh-rCN/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"要允许“<xliff:g id="APP_0">%1$s</xliff:g>”显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块吗?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- 可以读取“<xliff:g id="APP">%1$s</xliff:g>”中的信息"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- 可以在“<xliff:g id="APP">%1$s</xliff:g>”内执行操作"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"允许“<xliff:g id="APP">%1$s</xliff:g>”显示任何应用的图块"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"允许"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒绝"</string>
</resources>
diff --git a/slices/core/src/main/res/values-zh-rHK/strings.xml b/slices/core/src/main/res/values-zh-rHK/strings.xml
index bb478fc..5cf6943 100644
--- a/slices/core/src/main/res/values-zh-rHK/strings.xml
+++ b/slices/core/src/main/res/values-zh-rHK/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊嗎?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- 可以讀取「<xliff:g id="APP">%1$s</xliff:g>」中的資料"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- 可以在「<xliff:g id="APP">%1$s</xliff:g>」內執行操作"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"允許「<xliff:g id="APP">%1$s</xliff:g>」顯示任何應用程式的快訊"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"允許"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒絕"</string>
</resources>
diff --git a/slices/core/src/main/res/values-zh-rTW/strings.xml b/slices/core/src/main/res/values-zh-rTW/strings.xml
index caeeff1..df1ab8b 100644
--- a/slices/core/src/main/res/values-zh-rTW/strings.xml
+++ b/slices/core/src/main/res/values-zh-rTW/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊嗎?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- 它可以讀取「<xliff:g id="APP">%1$s</xliff:g>」的資訊"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- 它可以在「<xliff:g id="APP">%1$s</xliff:g>」內執行操作"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"允許「<xliff:g id="APP">%1$s</xliff:g>」顯示任何應用程式的區塊"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"允許"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒絕"</string>
</resources>
diff --git a/slices/core/src/main/res/values-zu/strings.xml b/slices/core/src/main/res/values-zu/strings.xml
index 940334c..d9ada48 100644
--- a/slices/core/src/main/res/values-zu/strings.xml
+++ b/slices/core/src/main/res/values-zu/strings.xml
@@ -21,7 +21,7 @@
<string name="abc_slice_permission_title" msgid="4175332421259324948">"Vumela i-<xliff:g id="APP_0">%1$s</xliff:g> ukuthi ibonise izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Ingafunda ulwazi kusukela ku-<xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Ingenza izenzo ngaphakathi kwe-<xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Vumela i-<xliff:g id="APP">%1$s</xliff:g> ukuthi ikubonise izingcezu kusukela kunoma iluphi uhlelo lokusebenza"</string>
+
<string name="abc_slice_permission_allow" msgid="5024599872061409708">"Vumela"</string>
<string name="abc_slice_permission_deny" msgid="3819478292430407705">"Phika"</string>
</resources>
diff --git a/slices/core/src/main/res/values/strings.xml b/slices/core/src/main/res/values/strings.xml
index 2959af8..6f96605a 100644
--- a/slices/core/src/main/res/values/strings.xml
+++ b/slices/core/src/main/res/values/strings.xml
@@ -28,9 +28,6 @@
<!-- Description of what kind of access is given to a slice host [CHAR LIMIT=NONE] -->
<string name="abc_slice_permission_text_2"> - It can take actions inside <xliff:g id="app" example="Example App">%1$s</xliff:g></string>
- <!-- Text on checkbox allowing the app to show slices from all apps [CHAR LIMIT=NONE] -->
- <string name="abc_slice_permission_checkbox">Allow <xliff:g id="app" example="Example App">%1$s</xliff:g> to show slices from any app</string>
-
<!-- Option to grant the slice permission request on the screen [CHAR LIMIT=15] -->
<string name="abc_slice_permission_allow">Allow</string>
diff --git a/webkit/integration-tests/testapp/build.gradle b/webkit/integration-tests/testapp/build.gradle
index 4caa18a..3442df0 100644
--- a/webkit/integration-tests/testapp/build.gradle
+++ b/webkit/integration-tests/testapp/build.gradle
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("AndroidXPlugin")
@@ -24,21 +23,21 @@
implementation("androidx.appcompat:appcompat:1.1.0")
implementation("androidx.core:core:1.1.0")
implementation(project(":webkit:webkit"))
- implementation(GUAVA_ANDROID)
- implementation(ESPRESSO_IDLING_NET)
- implementation(ESPRESSO_IDLING_RESOURCE)
+ implementation(libs.guavaAndroid)
+ implementation(libs.espressoIdlingNet)
+ implementation(libs.espressoIdlingResource)
- androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
- androidTestImplementation(ANDROIDX_TEST_CORE)
- androidTestImplementation(ANDROIDX_TEST_RUNNER)
- androidTestImplementation(ANDROIDX_TEST_RULES)
- androidTestImplementation(ESPRESSO_CORE, excludes.espresso)
- androidTestImplementation(ESPRESSO_CONTRIB, excludes.espresso)
- androidTestImplementation(ESPRESSO_IDLING_RESOURCE)
- androidTestImplementation(ESPRESSO_WEB, excludes.espresso)
- androidTestImplementation(MOCKITO_CORE, excludes.bytebuddy)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.testRules)
+ androidTestImplementation(libs.espressoCore, excludes.espresso)
+ androidTestImplementation(libs.espressoContrib, excludes.espresso)
+ androidTestImplementation(libs.espressoIdlingResource)
+ androidTestImplementation(libs.espressoWeb, excludes.espresso)
+ androidTestImplementation(libs.mockitoCore, excludes.bytebuddy)
// DexMaker has it"s own MockMaker
- androidTestImplementation(DEXMAKER_MOCKITO, excludes.bytebuddy)
+ androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy)
}
rootProject.tasks.getByName("buildOnServer").dependsOn(project.path + ":assembleRelease")
diff --git a/webkit/webkit/build.gradle b/webkit/webkit/build.gradle
index a52b165..ff5de9e 100644
--- a/webkit/webkit/build.gradle
+++ b/webkit/webkit/build.gradle
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import static androidx.build.dependencies.DependenciesKt.*
import androidx.build.LibraryGroups
import androidx.build.Publish
import androidx.build.SupportConfigKt
@@ -28,15 +27,15 @@
api("androidx.annotation:annotation:1.1.0")
api("androidx.core:core:1.1.0")
- androidTestImplementation(OKHTTP_MOCKWEBSERVER)
- androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
- androidTestImplementation(ANDROIDX_TEST_CORE)
- androidTestImplementation(ANDROIDX_TEST_RUNNER)
- androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(libs.okhttpMockwebserver)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.testRules)
androidTestImplementation("androidx.concurrent:concurrent-futures:1.0.0")
// Hamcrest matchers:
- androidTestImplementation(ESPRESSO_CONTRIB, excludes.espresso)
+ androidTestImplementation(libs.espressoContrib, excludes.espresso)
}
ext {
diff --git a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt
index 24abe4e..477c79d 100644
--- a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt
+++ b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt
@@ -141,7 +141,7 @@
}
/**
- * Wrapper around [<] that also includes the [Executor]
+ * Wrapper around [Consumer<WindowLayoutInfo>] that also includes the [Executor]
* on which the callback should run and the [Activity].
*/
internal class WindowLayoutChangeCallbackWrapper(
diff --git a/window/window/src/main/java/androidx/window/WindowManager.kt b/window/window/src/main/java/androidx/window/WindowManager.kt
index 3c29415..5b6deed 100644
--- a/window/window/src/main/java/androidx/window/WindowManager.kt
+++ b/window/window/src/main/java/androidx/window/WindowManager.kt
@@ -90,7 +90,7 @@
* current bounds that the user has selected for the [Activity][android.app.Activity]'s
* window.
*
- * @see .getMaximumWindowMetrics
+ * @see getMaximumWindowMetrics
* @see android.view.WindowManager.getCurrentWindowMetrics
*/
public fun getCurrentWindowMetrics(): WindowMetrics {
@@ -121,7 +121,7 @@
* the region describing the side of the device the associated [context's][Context]
* window is placed.
*
- * @see .getCurrentWindowMetrics
+ * @see getCurrentWindowMetrics
* @see android.view.WindowManager.getMaximumWindowMetrics
*/
public fun getMaximumWindowMetrics(): WindowMetrics {