Snap for 11296156 from e6b00b98c97da02b4176f7d1891d621ffeb8a539 to mainline-tzdata5-release

Change-Id: Idfd222cf1a7faca3d10dfca3d533ef700c93b28a
diff --git a/apk/res/layout/date_navigation_spinner_item.xml b/apk/res/layout/date_navigation_spinner_item.xml
index 59e2831..4449b8a 100644
--- a/apk/res/layout/date_navigation_spinner_item.xml
+++ b/apk/res/layout/date_navigation_spinner_item.xml
@@ -21,7 +21,7 @@
     android:id="@+id/spinner_item_text"
     style="?android:attr/spinnerDropDownItemStyle"
     android:textAppearance="?attr/textAppearanceLabel"
-    android:textColor="?android:attr/textColorPrimary"
+    android:textColor="@color/spinner_text_color"
     android:singleLine="true"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
diff --git a/apk/res/layout/dialog_custom_layout.xml b/apk/res/layout/dialog_custom_layout.xml
index 357cb3f..382a464 100644
--- a/apk/res/layout/dialog_custom_layout.xml
+++ b/apk/res/layout/dialog_custom_layout.xml
@@ -19,7 +19,7 @@
 
     <LinearLayout
         android:paddingHorizontal="@dimen/spacing_large"
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="?attr/alertDialogTitleGravity"
         android:orientation="vertical">
@@ -29,7 +29,6 @@
             android:layout_width="@dimen/icon_size"
             android:layout_height="@dimen/icon_size"
             android:layout_marginTop="@dimen/spacing_large"
-            android:layout_marginBottom="@dimen/spacing_normal"
             android:visibility="?attr/alertDialogTitleIconVisibility"
             android:tint="?android:attr/colorAccent" />
 
@@ -38,6 +37,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:paddingHorizontal="@dimen/spacing_small"
+            android:layout_marginTop="@dimen/spacing_normal"
             android:maxLines="4"
             android:singleLine="false"
             android:textAlignment="?attr/alertDialogTitleTextAlignment"
diff --git a/apk/res/layout/dialog_message_time_range_picker.xml b/apk/res/layout/dialog_message_time_range_picker.xml
index 75a5319..ddd45b0 100644
--- a/apk/res/layout/dialog_message_time_range_picker.xml
+++ b/apk/res/layout/dialog_message_time_range_picker.xml
@@ -23,6 +23,32 @@
         android:paddingHorizontal="@dimen/spacing_small"
         android:orientation="vertical">
 
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:gravity="?attr/alertDialogTitleGravity">
+            <ImageView
+                android:id="@+id/dialog_icon"
+                android:layout_width="@dimen/icon_size"
+                android:layout_height="@dimen/icon_size"
+                android:layout_marginTop="@dimen/spacing_large"
+                android:layout_marginBottom="@dimen/spacing_normal"
+                android:visibility="?attr/alertDialogTitleIconVisibility"
+                android:tint="?android:attr/colorAccent" />
+
+            <TextView
+                android:id="@+id/dialog_title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/spacing_small"
+                android:layout_marginBottom="@dimen/spacing_normal"
+                android:maxLines="4"
+                android:singleLine="false"
+                android:textAlignment="?attr/alertDialogTitleTextAlignment"
+                android:textAppearance="?attr/textAppearanceHeadline4" />
+        </LinearLayout>
+
         <TextView
             android:id="@+id/time_range_message"
             android:layout_width="match_parent"
diff --git a/apk/res/layout/dialog_message_with_checkbox.xml b/apk/res/layout/dialog_message_with_checkbox.xml
index 7a5b2c7..34b85c2 100644
--- a/apk/res/layout/dialog_message_with_checkbox.xml
+++ b/apk/res/layout/dialog_message_with_checkbox.xml
@@ -21,7 +21,28 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical"
-        android:paddingHorizontal="@dimen/spacing_large">
+        android:paddingHorizontal="@dimen/spacing_large"
+        android:gravity="?attr/alertDialogTitleGravity">
+
+        <ImageView
+            android:id="@+id/dialog_icon"
+            android:layout_width="@dimen/icon_size"
+            android:layout_height="@dimen/icon_size"
+            android:layout_marginTop="@dimen/spacing_large"
+            android:layout_marginBottom="@dimen/spacing_normal"
+            android:visibility="?attr/alertDialogTitleIconVisibility"
+            android:tint="?android:attr/colorAccent" />
+
+        <TextView
+            android:id="@+id/dialog_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingHorizontal="@dimen/spacing_small"
+            android:layout_marginBottom="@dimen/spacing_normal"
+            android:maxLines="4"
+            android:singleLine="false"
+            android:textAlignment="?attr/alertDialogTitleTextAlignment"
+            android:textAppearance="?attr/textAppearanceHeadline4" />
 
         <TextView
             android:id="@+id/dialog_message"
diff --git a/apk/res/layout/fragment_entries_access.xml b/apk/res/layout/fragment_entries_access.xml
index e0cf839..a1da61e 100644
--- a/apk/res/layout/fragment_entries_access.xml
+++ b/apk/res/layout/fragment_entries_access.xml
@@ -40,7 +40,7 @@
             app:tabGravity="fill"
             app:tabMode="fixed"
             app:tabIndicatorHeight="0dp"
-            app:tabSelectedTextColor="?android:attr/textColorPrimary"
+            app:tabSelectedTextColor="@color/settingslib_primary_dark_device_default_settings"
             app:tabTextColor="?android:attr/textColorSecondary"
             app:tabBackground="@drawable/tab_background"
             app:tabTextAppearance="?attr/textAppearanceSubheader" />
diff --git a/apk/res/layout/widget_aggregation_data_card.xml b/apk/res/layout/widget_aggregation_data_card.xml
index c735f9e..c8210c7 100644
--- a/apk/res/layout/widget_aggregation_data_card.xml
+++ b/apk/res/layout/widget_aggregation_data_card.xml
@@ -30,11 +30,11 @@
         android:layout_height="@dimen/icon_size"
         android:layout_marginEnd="@dimen/spacing_small"
         app:tint="?android:attr/textColorPrimary" />
-    <!-- TODO (b/299940574) dynamically set content description using category of aggregation -->
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/title_date_container"
-        android:layout_width="match_parent"
+        android:layout_width="0dp"
+        android:layout_weight="1"
         android:layout_height="wrap_content">
 
         <TextView
@@ -44,6 +44,7 @@
             android:layout_marginEnd="@dimen/spacing_xsmall"
             android:maxLines="1"
             android:ellipsize="end"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
             android:textAppearance="?attr/textAppearanceItem"/>
 
         <TextView
diff --git a/apk/res/layout/widget_app_source_layout.xml b/apk/res/layout/widget_app_source_layout.xml
index e07aa6c..82534b1 100644
--- a/apk/res/layout/widget_app_source_layout.xml
+++ b/apk/res/layout/widget_app_source_layout.xml
@@ -72,20 +72,20 @@
 
     <FrameLayout
         android:id="@+id/action_icon"
-        android:layout_width="wrap_content"
+        android:layout_width="0dp"
         android:layout_height="match_parent"
+        android:layout_weight="0.3"
         android:minWidth="@dimen/button_size"
         android:layout_gravity="center_vertical">
 
         <ImageView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical|center_horizontal"
+            android:layout_gravity="center_vertical|end"
             android:id="@+id/action_icon_background"
             android:minHeight="@dimen/icon_size"
             android:minWidth="@dimen/icon_size"
-            android:background="?attr/priorityItemDragIcon"
-            android:importantForAccessibility="no" />
+            android:background="?attr/priorityItemDragIcon"/>
     </FrameLayout>
 
 </LinearLayout>
\ No newline at end of file
diff --git a/apk/res/layout/widget_date_navigation.xml b/apk/res/layout/widget_date_navigation.xml
index 400c19e..120c792 100644
--- a/apk/res/layout/widget_date_navigation.xml
+++ b/apk/res/layout/widget_date_navigation.xml
@@ -31,7 +31,9 @@
         android:padding="@dimen/spacing_normal"
         android:src="@drawable/ic_left_arrow"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+        app:layout_constraintEnd_toStartOf="@id/selected_date"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
 
     <ImageButton
         android:id="@+id/navigation_next_day"
@@ -42,32 +44,26 @@
         android:contentDescription="@string/navigation_next_day"
         android:padding="@dimen/spacing_normal"
         android:src="@drawable/ic_right_arrow"
+        app:layout_constraintStart_toEndOf="@id/selected_date"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_centerInParent="true"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="@+id/navigation_next_day"
-        app:layout_constraintStart_toStartOf="@+id/navigation_previous_day"
         app:layout_constraintTop_toTopOf="parent"
-        android:layoutDirection="locale"
-        android:orientation="horizontal">
+        app:layout_constraintBottom_toBottomOf="parent"/>
 
-        <TextView
-            android:id="@+id/selected_date"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_centerInParent="true"
-            style="?attr/spinnerStyle"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            android:contentDescription="@string/navigation_selected_day"
-            tools:text="Jan 16, 2023" />
 
-    </androidx.constraintlayout.widget.ConstraintLayout>
+    <TextView
+        android:id="@+id/selected_date"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_marginStart="@dimen/spacing_normal"
+        android:layout_marginEnd="@dimen/spacing_normal"
+        android:padding="@dimen/spacing_xsmall"
+        style="?attr/spinnerStyle"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/navigation_next_day"
+        app:layout_constraintStart_toEndOf="@id/navigation_previous_day"
+        app:layout_constrainedWidth = "true"
+        app:layout_constraintTop_toTopOf="parent"
+        android:contentDescription="@string/navigation_selected_day"
+        tools:text="Jan 16, 2023" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/apk/res/values-af/strings.xml b/apk/res/values-af/strings.xml
index 8bc248e..e540b63 100644
--- a/apk/res/values-af/strings.xml
+++ b/apk/res/values-af/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Skryf: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Bestuur toestemmings"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tyd: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktiwiteit"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktiwiteit"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Liggaamsmetings"</string>
diff --git a/apk/res/values-am/strings.xml b/apk/res/values-am/strings.xml
index 87335ba..b106054 100644
--- a/apk/res/values-am/strings.xml
+++ b/apk/res/values-am/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"ይህን ጽፏል፦ %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">"፣ "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"ፈቃዶችን አስተዳድር"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"ጊዜ:- %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"እንቅስቃሴ"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"እንቅስቃሴ"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"የሰውነት አካል መለኪያዎች"</string>
diff --git a/apk/res/values-ar/strings.xml b/apk/res/values-ar/strings.xml
index 5b27ae5..f6c35f4 100644
--- a/apk/res/values-ar/strings.xml
+++ b/apk/res/values-ar/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"الكتابة: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">"، "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"إدارة الأذونات"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"الوقت: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"النشاط"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"النشاط"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"قياسات الجسم"</string>
diff --git a/apk/res/values-as/strings.xml b/apk/res/values-as/strings.xml
index 1ba2a04..b0af940 100644
--- a/apk/res/values-as/strings.xml
+++ b/apk/res/values-as/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"লিখক: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"অনুমতি পৰিচালনা কৰক"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"সময়: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"কাৰ্যকলাপ"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"কাৰ্যকলাপ"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"শৰীৰৰ জোখ-মাখ"</string>
diff --git a/apk/res/values-az/strings.xml b/apk/res/values-az/strings.xml
index c486ccb..a75e9e6 100644
--- a/apk/res/values-az/strings.xml
+++ b/apk/res/values-az/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Yazmaq: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"İcazələri idarə edin"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Vaxt: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Fəaliyyət"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"fəaliyyət"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Bədən ölçüləri"</string>
diff --git a/apk/res/values-b+sr+Latn/strings.xml b/apk/res/values-b+sr+Latn/strings.xml
index af57f2a..4c2bc0e 100644
--- a/apk/res/values-b+sr+Latn/strings.xml
+++ b/apk/res/values-b+sr+Latn/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Pisanje: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Upravljaj dozvolama"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Vreme: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivnosti"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktivnosti"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Telesne mere"</string>
diff --git a/apk/res/values-be/strings.xml b/apk/res/values-be/strings.xml
index 1d29a21..29484a7 100644
--- a/apk/res/values-be/strings.xml
+++ b/apk/res/values-be/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Запісана: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Кіраваць дазволамі"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Час: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Актыўнасць"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"актыўнасць"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Параметры цела"</string>
diff --git a/apk/res/values-bg/strings.xml b/apk/res/values-bg/strings.xml
index 37a8a9a..8ab1b18 100644
--- a/apk/res/values-bg/strings.xml
+++ b/apk/res/values-bg/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Записване: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Управление на разрешенията"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Час: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Активност"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"активност"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Мерки на тялото"</string>
diff --git a/apk/res/values-bn/strings.xml b/apk/res/values-bn/strings.xml
index fd5dd86..3ad69f2 100644
--- a/apk/res/values-bn/strings.xml
+++ b/apk/res/values-bn/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"লেখা: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"অনুমতি ম্যানেজ করুন"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"সময়: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"অ্যাক্টিভিটি"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"অ্যাক্টিভিটি"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"শরীরের পরিমাপ সংক্রান্ত ডেটা"</string>
diff --git a/apk/res/values-bs/strings.xml b/apk/res/values-bs/strings.xml
index d0bffe4..59a1fb5 100644
--- a/apk/res/values-bs/strings.xml
+++ b/apk/res/values-bs/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Zapisivanje: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Upravljajte odobrenjima"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Vrijeme: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivnost"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktivnost"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Tjelesne mjere"</string>
diff --git a/apk/res/values-ca/strings.xml b/apk/res/values-ca/strings.xml
index 916b6cf..c6628fb 100644
--- a/apk/res/values-ca/strings.xml
+++ b/apk/res/values-ca/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Ha escrit: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Gestiona els permisos"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Temps: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Activitat"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"activitat"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Mesures corporals"</string>
diff --git a/apk/res/values-cs/strings.xml b/apk/res/values-cs/strings.xml
index 670001a..67d0b93 100644
--- a/apk/res/values-cs/strings.xml
+++ b/apk/res/values-cs/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Zápis: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Spravovat oprávnění"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Čas: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivita"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktivita"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Tělesné míry"</string>
diff --git a/apk/res/values-da/strings.xml b/apk/res/values-da/strings.xml
index 8536a6b..cd1554a 100644
--- a/apk/res/values-da/strings.xml
+++ b/apk/res/values-da/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Skriver: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Administrer tilladelser"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tid: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivitet"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktivitet"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Kropsmål"</string>
diff --git a/apk/res/values-de/strings.xml b/apk/res/values-de/strings.xml
index 5a89f78..1441548 100644
--- a/apk/res/values-de/strings.xml
+++ b/apk/res/values-de/strings.xml
@@ -45,6 +45,8 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Schreiben: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Berechtigungen verwalten"</string>
+    <!-- no translation found for recent_access_time_content_descritption (1709675393952226273) -->
+    <skip />
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivität"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"Aktivität"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Körperwerte"</string>
diff --git a/apk/res/values-el/strings.xml b/apk/res/values-el/strings.xml
index 805697b..7d7b5e4 100644
--- a/apk/res/values-el/strings.xml
+++ b/apk/res/values-el/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Εγγραφή: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Διαχείριση αδειών"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Χρόνος: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Δραστηριότητα"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"δραστηριότητα"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Μετρήσεις σώματος"</string>
diff --git a/apk/res/values-en-rAU/strings.xml b/apk/res/values-en-rAU/strings.xml
index 326bd15..d91a7bd 100644
--- a/apk/res/values-en-rAU/strings.xml
+++ b/apk/res/values-en-rAU/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Write: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Manage permissions"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Time: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Activity"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"activity"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Body measurements"</string>
diff --git a/apk/res/values-en-rCA/strings.xml b/apk/res/values-en-rCA/strings.xml
index 7b20f19..43bb21e 100644
--- a/apk/res/values-en-rCA/strings.xml
+++ b/apk/res/values-en-rCA/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Write: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Manage permissions"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Time: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Activity"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"activity"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Body measurements"</string>
diff --git a/apk/res/values-en-rGB/strings.xml b/apk/res/values-en-rGB/strings.xml
index 326bd15..d91a7bd 100644
--- a/apk/res/values-en-rGB/strings.xml
+++ b/apk/res/values-en-rGB/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Write: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Manage permissions"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Time: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Activity"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"activity"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Body measurements"</string>
diff --git a/apk/res/values-en-rIN/strings.xml b/apk/res/values-en-rIN/strings.xml
index 326bd15..d91a7bd 100644
--- a/apk/res/values-en-rIN/strings.xml
+++ b/apk/res/values-en-rIN/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Write: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Manage permissions"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Time: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Activity"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"activity"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Body measurements"</string>
diff --git a/apk/res/values-en-rXC/strings.xml b/apk/res/values-en-rXC/strings.xml
index 8dedced..c98784f 100644
--- a/apk/res/values-en-rXC/strings.xml
+++ b/apk/res/values-en-rXC/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‎Write: %s‎‏‎‎‏‎"</string>
     <string name="data_type_separator" msgid="1299848322898210658">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎, ‎‏‎‎‏‎ "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎Manage permissions‎‏‎‎‏‎"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎Time: %s‎‏‎‎‏‎"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‎Activity‎‏‎‎‏‎"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‎‏‎‏‏‎‎‎‏‏‎‎‏‎activity‎‏‎‎‏‎"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‎Body measurements‎‏‎‎‏‎"</string>
diff --git a/apk/res/values-es-rUS/strings.xml b/apk/res/values-es-rUS/strings.xml
index 041c8f8..3c67a90 100644
--- a/apk/res/values-es-rUS/strings.xml
+++ b/apk/res/values-es-rUS/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Escribir: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Administrar permisos"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tiempo: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Actividad"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"actividad"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Medidas corporales"</string>
diff --git a/apk/res/values-es/strings.xml b/apk/res/values-es/strings.xml
index 48fdd56..f079683 100644
--- a/apk/res/values-es/strings.xml
+++ b/apk/res/values-es/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Escritura: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Gestionar permisos"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tiempo: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Actividad"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"actividad"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Medidas corporales"</string>
diff --git a/apk/res/values-et/strings.xml b/apk/res/values-et/strings.xml
index 5f31814..85654ad 100644
--- a/apk/res/values-et/strings.xml
+++ b/apk/res/values-et/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Kirjutamine: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Lubade haldamine"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Aeg: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Tegevus"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"tegevus"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Keha mõõtmisandmed"</string>
@@ -683,7 +684,7 @@
     <string name="check_for_updates_description" msgid="1347667778199095160">"Veenduge, et installitud rakendused oleksid ajakohased"</string>
     <string name="see_all_compatible_apps" msgid="6791146164535475726">"Vaadake kõiki ühilduvaid rakendusi"</string>
     <string name="see_all_compatible_apps_description" msgid="2092325337403254491">"Otsige rakendusi Google Playst"</string>
-    <string name="send_feedback" msgid="7756927746070096780">"Tagasiside saatmine"</string>
+    <string name="send_feedback" msgid="7756927746070096780">"Saatke tagasisidet"</string>
     <string name="send_feedback_description" msgid="2887207112856240778">"Öelge meile, milliseid tervise- ja treeningurakendusi soovite Health Connectiga kasutada"</string>
     <string name="playstore_app_title" msgid="4138464328693481809">"Play pood"</string>
     <string name="auto_delete_button" msgid="8536451792268513619">"Automaatne kustutamine"</string>
diff --git a/apk/res/values-eu/strings.xml b/apk/res/values-eu/strings.xml
index dc793ee..60d65c1 100644
--- a/apk/res/values-eu/strings.xml
+++ b/apk/res/values-eu/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Idatzi: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Kudeatu baimenak"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Denbora: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Jarduerak"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"jarduerak"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Gorputzaren neurketak"</string>
diff --git a/apk/res/values-fa/strings.xml b/apk/res/values-fa/strings.xml
index df57cad..c3b04bb 100644
--- a/apk/res/values-fa/strings.xml
+++ b/apk/res/values-fa/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"دسترسی نوشتن: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">"، "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"مدیریت اجازه‌ها"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"زمان: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"فعالیت"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"فعالیت"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"سنجه‌های بدن"</string>
diff --git a/apk/res/values-fi/strings.xml b/apk/res/values-fi/strings.xml
index db4d336..34b99f7 100644
--- a/apk/res/values-fi/strings.xml
+++ b/apk/res/values-fi/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Kirjoitettu: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Muuta lupia"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Aika: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Treeni"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"treeni"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Kehon mittaustulokset"</string>
diff --git a/apk/res/values-fr-rCA/strings.xml b/apk/res/values-fr-rCA/strings.xml
index 3b694e0..d0b3008 100644
--- a/apk/res/values-fr-rCA/strings.xml
+++ b/apk/res/values-fr-rCA/strings.xml
@@ -45,6 +45,8 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Écriture : %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Gérer les autorisations"</string>
+    <!-- no translation found for recent_access_time_content_descritption (1709675393952226273) -->
+    <skip />
     <string name="activity_category_uppercase" msgid="136628843341377088">"Activité"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"activité"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Mensurations"</string>
diff --git a/apk/res/values-fr/strings.xml b/apk/res/values-fr/strings.xml
index 992ce9e..a9a1215 100644
--- a/apk/res/values-fr/strings.xml
+++ b/apk/res/values-fr/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Écriture : %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Gérer les autorisations"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Temps : %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Activité"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"activité"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Données corporelles"</string>
diff --git a/apk/res/values-gl/strings.xml b/apk/res/values-gl/strings.xml
index 6f59eae..57e5dab 100644
--- a/apk/res/values-gl/strings.xml
+++ b/apk/res/values-gl/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Escritura: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Xestionar permisos"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tempo: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Actividade"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"actividade"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Medicións corporais"</string>
diff --git a/apk/res/values-gu/strings.xml b/apk/res/values-gu/strings.xml
index 6250175..76bad3b 100644
--- a/apk/res/values-gu/strings.xml
+++ b/apk/res/values-gu/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"લખો: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"પરવાનગીઓને મેનેજ કરો"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"સમય: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"પ્રવૃત્તિ"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"પ્રવૃત્તિ"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"શરીરના માપ"</string>
diff --git a/apk/res/values-hi/strings.xml b/apk/res/values-hi/strings.xml
index 4bbbf13..7577cb2 100644
--- a/apk/res/values-hi/strings.xml
+++ b/apk/res/values-hi/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"ऐप्लिकेशन इस डेटा में बदलाव कर सकता है: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"अनुमतियां मैनेज करें"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"समय: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"गतिविधि"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"गतिविधि"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"शरीर की माप का डेटा"</string>
diff --git a/apk/res/values-hr/strings.xml b/apk/res/values-hr/strings.xml
index 06263bb..fbdd99c 100644
--- a/apk/res/values-hr/strings.xml
+++ b/apk/res/values-hr/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Zapiši: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Upravljajte dopuštenjima"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Vrijeme: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivnost"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktivnost"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Tjelesne mjere"</string>
diff --git a/apk/res/values-hu/strings.xml b/apk/res/values-hu/strings.xml
index 779b4bf..9514b46 100644
--- a/apk/res/values-hu/strings.xml
+++ b/apk/res/values-hu/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"%s írása"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Engedélyek kezelése"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Idő: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Tevékenységek"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"tevékenységek"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Testméretek"</string>
diff --git a/apk/res/values-hy/strings.xml b/apk/res/values-hy/strings.xml
index 430268f..ff04694 100644
--- a/apk/res/values-hy/strings.xml
+++ b/apk/res/values-hy/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Գրանցել՝ %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Կառավարել թույլտվությունները"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Ժամանակը՝ %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Ակտիվություն"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"ակտիվություն"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Մարմնի պարամետրեր"</string>
diff --git a/apk/res/values-in/strings.xml b/apk/res/values-in/strings.xml
index 62faa90..d3606fd 100644
--- a/apk/res/values-in/strings.xml
+++ b/apk/res/values-in/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Menulis: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Kelola izin"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Waktu: %dtk"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivitas"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktivitas"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Pengukuran tubuh"</string>
diff --git a/apk/res/values-is/strings.xml b/apk/res/values-is/strings.xml
index 5b9bc2e..80d8f59 100644
--- a/apk/res/values-is/strings.xml
+++ b/apk/res/values-is/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Skrifa: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Stjórna heimildum"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tími: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Hreyfing"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"hreyfing"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Líkamsmælingar"</string>
diff --git a/apk/res/values-it/strings.xml b/apk/res/values-it/strings.xml
index f3037bb..17e6345 100644
--- a/apk/res/values-it/strings.xml
+++ b/apk/res/values-it/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Scrittura: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Gestisci autorizzazioni"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tempo: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Attività"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"attività"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Misurazioni corporee"</string>
diff --git a/apk/res/values-iw/strings.xml b/apk/res/values-iw/strings.xml
index 98ba16d..4b07c7c 100644
--- a/apk/res/values-iw/strings.xml
+++ b/apk/res/values-iw/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"כתיבה: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"ניהול הרשאות"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"זמן: ‎%s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"פעילות"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"פעילות"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"מדדים גופניים"</string>
diff --git a/apk/res/values-ja/strings.xml b/apk/res/values-ja/strings.xml
index 632d21c..2d3e7fd 100644
--- a/apk/res/values-ja/strings.xml
+++ b/apk/res/values-ja/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"書き込み: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">"、 "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"権限を管理"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"時間: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"アクティビティ"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"アクティビティ"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"身体測定"</string>
@@ -681,7 +682,7 @@
     <string name="things_to_try" msgid="8200374691546152703">"おすすめの方法"</string>
     <string name="check_for_updates" msgid="3841090978657783101">"アップデートを確認"</string>
     <string name="check_for_updates_description" msgid="1347667778199095160">"インストール済みのアプリについて、アップデートがないか確認できます"</string>
-    <string name="see_all_compatible_apps" msgid="6791146164535475726">"対応するアプリをすべて表示"</string>
+    <string name="see_all_compatible_apps" msgid="6791146164535475726">"対応アプリの一覧をチェック"</string>
     <string name="see_all_compatible_apps_description" msgid="2092325337403254491">"Google Play でアプリを探します"</string>
     <string name="send_feedback" msgid="7756927746070096780">"フィードバックを送信"</string>
     <string name="send_feedback_description" msgid="2887207112856240778">"ヘルスコネクトで使用したい健康アプリやフィットネス アプリをリクエストできます"</string>
diff --git a/apk/res/values-ka/strings.xml b/apk/res/values-ka/strings.xml
index a0f6aa8..a88f09d 100644
--- a/apk/res/values-ka/strings.xml
+++ b/apk/res/values-ka/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"ჩაწერეთ: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"ნებართვების მართვა"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"დრო: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"აქტივობა"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"აქტივობა"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"სხეულის ზომები"</string>
diff --git a/apk/res/values-kk/strings.xml b/apk/res/values-kk/strings.xml
index e23ad05..932f275 100644
--- a/apk/res/values-kk/strings.xml
+++ b/apk/res/values-kk/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Жазу: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Рұқсаттарды басқару"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Уақыт: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Қимыл"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"қимыл"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Дене өлшемдері"</string>
diff --git a/apk/res/values-km/strings.xml b/apk/res/values-km/strings.xml
index d1663f4..b5e1f75 100644
--- a/apk/res/values-km/strings.xml
+++ b/apk/res/values-km/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"សរសេរ​៖ %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"គ្រប់គ្រង​ការអនុញ្ញាត"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"ពេលវេលា៖ %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"សកម្មភាព"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"សកម្មភាព"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"រង្វាស់រាងកាយ"</string>
diff --git a/apk/res/values-kn/strings.xml b/apk/res/values-kn/strings.xml
index 1fd17cd..54d3645 100644
--- a/apk/res/values-kn/strings.xml
+++ b/apk/res/values-kn/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"ಬರೆದಿರುವುದು: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"ಅನುಮತಿಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"ಸಮಯ: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"ಚಟುವಟಿಕೆ"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"ಚಟುವಟಿಕೆ"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"ದೇಹದ ಅಳತೆಗಳು"</string>
@@ -693,7 +694,7 @@
     <string name="auto_delete_section" msgid="7732381000331475082">"ಡೇಟಾವನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅಳಿಸಿ"</string>
     <string name="range_after_x_months" msgid="3340127072680117121">"{count,plural, =1{# ತಿಂಗಳ ನಂತರ}one{# ತಿಂಗಳುಗಳ ನಂತರ}other{# ತಿಂಗಳುಗಳ ನಂತರ}}"</string>
     <string name="range_never" msgid="4429478261788361233">"ಎಂದಿಗೂ ಬೇಡ"</string>
-    <string name="range_off" msgid="8178520557618184215">"ಆಫ್ ಮಾಡಿ"</string>
+    <string name="range_off" msgid="8178520557618184215">"ಆಫ್"</string>
     <string name="auto_delete_rationale" msgid="5255442126521464878">"ನೀವು ಈ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಿದಾಗ, ನಿಮ್ಮ ಹೊಸ ಆದ್ಯತೆಗಳನ್ನು ಅನ್ವಯಿಸುವುದಕ್ಕಾಗಿ Health Connect ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಡೇಟಾವನ್ನು ಅಳಿಸುತ್ತದೆ"</string>
     <string name="confirming_question_x_months" msgid="8204363800605282103">"{count,plural, =1{# ತಿಂಗಳ ನಂತರ ಡೇಟಾವನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅಳಿಸಬೇಕೆ?}one{# ತಿಂಗಳುಗಳ ನಂತರ ಡೇಟಾವನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅಳಿಸಬೇಕೆ?}other{# ತಿಂಗಳುಗಳ ನಂತರ ಡೇಟಾವನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅಳಿಸಬೇಕೆ?}}"</string>
     <string name="confirming_message_x_months" msgid="4798474593741471977">"{count,plural, =1{# ತಿಂಗಳ ನಂತರ Health Connect ಹೊಸ ಡೇಟಾವನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅಳಿಸುತ್ತದೆ. ಇದನ್ನು ಸೆಟ್ ಮಾಡುವುದರಿಂದ # ತಿಂಗಳಿಗಿಂತ ಹಳೆಯದಾದ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಡೇಟಾವನ್ನು ಸಹ ಅಳಿಸುತ್ತದೆ.}one{# ತಿಂಗಳುಗಳ ನಂತರ Health Connect ಹೊಸ ಡೇಟಾವನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅಳಿಸುತ್ತದೆ. ಇದನ್ನು ಸೆಟ್ ಮಾಡುವುದರಿಂದ # ತಿಂಗಳುಗಳಿಗಿಂತ ಹಳೆಯದಾದ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಡೇಟಾವನ್ನು ಸಹ ಅಳಿಸುತ್ತದೆ.}other{# ತಿಂಗಳುಗಳ ನಂತರ Health Connect ಹೊಸ ಡೇಟಾವನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅಳಿಸುತ್ತದೆ. ಇದನ್ನು ಸೆಟ್ ಮಾಡುವುದರಿಂದ # ತಿಂಗಳುಗಳಿಗಿಂತ ಹಳೆಯದಾದ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಡೇಟಾವನ್ನು ಸಹ ಅಳಿಸುತ್ತದೆ.}}"</string>
diff --git a/apk/res/values-ko/strings.xml b/apk/res/values-ko/strings.xml
index 84ed660..4e2e3ad 100644
--- a/apk/res/values-ko/strings.xml
+++ b/apk/res/values-ko/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"쓰기: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"권한 관리"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"시간: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"활동"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"활동"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"신체 측정"</string>
diff --git a/apk/res/values-ky/strings.xml b/apk/res/values-ky/strings.xml
index dde2c44..9e55c07 100644
--- a/apk/res/values-ky/strings.xml
+++ b/apk/res/values-ky/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Жазуу: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Уруксаттарды башкаруу"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Убакыт: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Кыймылдуулук"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"кыймылдуулук"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Дене-бой өлчөмдөрү"</string>
diff --git a/apk/res/values-lo/strings.xml b/apk/res/values-lo/strings.xml
index b02202c..6bc4351 100644
--- a/apk/res/values-lo/strings.xml
+++ b/apk/res/values-lo/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"ຂຽນ: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"ຈັດການການອະນຸຍາດ"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"ເວລາ: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"ກິດຈະກຳ"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"ກິດຈະກຳ"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"ການວັດແທກຮ່າງກາຍ"</string>
diff --git a/apk/res/values-lt/strings.xml b/apk/res/values-lt/strings.xml
index 3e1d177..99f5827 100644
--- a/apk/res/values-lt/strings.xml
+++ b/apk/res/values-lt/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Rašyti: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Tvarkyti leidimus"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Laikas: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Veikla"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"veikla"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Kūno matavimai"</string>
diff --git a/apk/res/values-lv/strings.xml b/apk/res/values-lv/strings.xml
index a310af9..50a50b9 100644
--- a/apk/res/values-lv/strings.xml
+++ b/apk/res/values-lv/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Rakstīt: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Pārvaldīt atļaujas"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Laiks: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivitāte"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktivitāte"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Ķermeņa rādītāji"</string>
diff --git a/apk/res/values-mk/strings.xml b/apk/res/values-mk/strings.xml
index f0a5285..9eb72bc 100644
--- a/apk/res/values-mk/strings.xml
+++ b/apk/res/values-mk/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Запишување: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Управувајте со дозволите"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Време: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Активност"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"активност"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Телесни мерења"</string>
diff --git a/apk/res/values-ml/strings.xml b/apk/res/values-ml/strings.xml
index f14e98c..0973b18 100644
--- a/apk/res/values-ml/strings.xml
+++ b/apk/res/values-ml/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"റൈറ്റ് ചെയ്യുക: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"അനുമതികൾ മാനേജ് ചെയ്യുക"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"സമയം: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"ആക്റ്റിവിറ്റി"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"ആക്റ്റിവിറ്റി"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"ശാരീരിക അളവുകൾ"</string>
diff --git a/apk/res/values-mn/strings.xml b/apk/res/values-mn/strings.xml
index f6b01b2..9c86cc5 100644
--- a/apk/res/values-mn/strings.xml
+++ b/apk/res/values-mn/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Бичих: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Зөвшөөрлийг удирдах"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Хугацаа: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Дасгал, хөдөлгөөн"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"дасгал, хөдөлгөөн"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Биеийн хэмжээ"</string>
diff --git a/apk/res/values-mr/strings.xml b/apk/res/values-mr/strings.xml
index ad0b247..9b5eb11 100644
--- a/apk/res/values-mr/strings.xml
+++ b/apk/res/values-mr/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"राइट करा: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"परवानग्या व्यवस्थापित करा"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"वेळ: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"ॲक्टिव्हिटी"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"ॲक्टिव्हिटी"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"शरीराचे मोजमाप"</string>
diff --git a/apk/res/values-ms/strings.xml b/apk/res/values-ms/strings.xml
index 6101178..8ec9dcb 100644
--- a/apk/res/values-ms/strings.xml
+++ b/apk/res/values-ms/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Tulis: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Urus kebenaran"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Masa: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktiviti"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktiviti"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Ukuran badan"</string>
diff --git a/apk/res/values-my/strings.xml b/apk/res/values-my/strings.xml
index a376870..55ca736 100644
--- a/apk/res/values-my/strings.xml
+++ b/apk/res/values-my/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"ရေးရန်- %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">"၊ "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"ခွင့်ပြုချက်များကို စီမံရန်"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"အချိန်- %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"လှုပ်ရှားမှု"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"လှုပ်ရှားမှု"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"ခန္ဓာကိုယ် တိုင်းတာချက်များ"</string>
diff --git a/apk/res/values-nb/strings.xml b/apk/res/values-nb/strings.xml
index a0a2f16..2373c9d 100644
--- a/apk/res/values-nb/strings.xml
+++ b/apk/res/values-nb/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Skrevet: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Administrer tillatelser"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tid: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivitet"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktivitet"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Kroppsmålinger"</string>
diff --git a/apk/res/values-ne/strings.xml b/apk/res/values-ne/strings.xml
index fb33951..1d1d432 100644
--- a/apk/res/values-ne/strings.xml
+++ b/apk/res/values-ne/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"राइट गर्ने अनुमति: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"अनुमतिहरू व्यवस्थापन गर्नुहोस्"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"समय: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"क्रियाकलाप"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"क्रियाकलाप"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"शारीरिक नाप"</string>
diff --git a/apk/res/values-nl/strings.xml b/apk/res/values-nl/strings.xml
index 388d1a8..113b8a1 100644
--- a/apk/res/values-nl/strings.xml
+++ b/apk/res/values-nl/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Schrijven: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Rechten beheren"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tijd: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Activiteit"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"activiteit"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Lichaamsmetingen"</string>
diff --git a/apk/res/values-or/strings.xml b/apk/res/values-or/strings.xml
index d6fcc13..f08d662 100644
--- a/apk/res/values-or/strings.xml
+++ b/apk/res/values-or/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"ଲେଖି ପାରିବ: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"ଅନୁମତିଗୁଡ଼ିକୁ ପରିଚାଳନା କରନ୍ତୁ"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"ସମୟ: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"କାର୍ଯ୍ୟକଳାପ"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"କାର୍ଯ୍ୟକଳାପ"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"ଶରୀର ପରିମାପ"</string>
diff --git a/apk/res/values-pa/strings.xml b/apk/res/values-pa/strings.xml
index b37f4fd..a6a8d72 100644
--- a/apk/res/values-pa/strings.xml
+++ b/apk/res/values-pa/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"ਐਪ ਇਸ ਡਾਟੇ ਵਿਚ ਬਦਲਾਵ ਕਰ ਸਕਦੀ ਹੈ: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"ਇਜਾਜ਼ਤਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"ਸਮਾਂ: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"ਸਰਗਰਮੀ"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"ਸਰਗਰਮੀ"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"ਸਰੀਰਕ ਮਾਪ"</string>
diff --git a/apk/res/values-pl/strings.xml b/apk/res/values-pl/strings.xml
index 8b4eb92..0624864 100644
--- a/apk/res/values-pl/strings.xml
+++ b/apk/res/values-pl/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Zapisywanie: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Zarządzaj uprawnieniami"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Czas: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktywność"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktywność"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Pomiary ciała"</string>
diff --git a/apk/res/values-pt-rPT/strings.xml b/apk/res/values-pt-rPT/strings.xml
index 94ad388..f8d92c9 100644
--- a/apk/res/values-pt-rPT/strings.xml
+++ b/apk/res/values-pt-rPT/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Escrita: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Gerir autorizações"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tempo: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Atividade"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"atividade"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Medições corporais"</string>
diff --git a/apk/res/values-pt/strings.xml b/apk/res/values-pt/strings.xml
index 4403b81..cb8c16f 100644
--- a/apk/res/values-pt/strings.xml
+++ b/apk/res/values-pt/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Gravação: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Gerenciar permissões"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tempo: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Atividade"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"atividade"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Medidas corporais"</string>
diff --git a/apk/res/values-ro/strings.xml b/apk/res/values-ro/strings.xml
index a6a6d22..3e2c926 100644
--- a/apk/res/values-ro/strings.xml
+++ b/apk/res/values-ro/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Scriere: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Gestionează permisiunile"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Ora: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Activitate"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"activitate"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Măsurători corporale"</string>
diff --git a/apk/res/values-ru/strings.xml b/apk/res/values-ru/strings.xml
index 52a2524..79d561f 100644
--- a/apk/res/values-ru/strings.xml
+++ b/apk/res/values-ru/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Запись данных: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Настроить разрешения"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Время: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Активность"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"активность"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Физические параметры"</string>
diff --git a/apk/res/values-si/strings.xml b/apk/res/values-si/strings.xml
index 55833d6..e502356 100644
--- a/apk/res/values-si/strings.xml
+++ b/apk/res/values-si/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"ලියන්න: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"අවසර කළමනාකරණය කරන්න"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"කාලය: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"ක්‍රියාකාරකම"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"ක්‍රියාකාරකම"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"ශරීර මිනුම්"</string>
diff --git a/apk/res/values-sk/strings.xml b/apk/res/values-sk/strings.xml
index 5df59a0..bce2c9e 100644
--- a/apk/res/values-sk/strings.xml
+++ b/apk/res/values-sk/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Zapisuje: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Spravovať povolenia"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Čas: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivita"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktivita"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Telesné miery"</string>
diff --git a/apk/res/values-sl/strings.xml b/apk/res/values-sl/strings.xml
index 2a97c48..e8d810d 100644
--- a/apk/res/values-sl/strings.xml
+++ b/apk/res/values-sl/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Zapisovanje: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Upravljanje dovoljenj"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Čas: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Dejavnost"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"dejavnost"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Telesne meritve"</string>
diff --git a/apk/res/values-sq/strings.xml b/apk/res/values-sq/strings.xml
index 55f62b2..194daeb 100644
--- a/apk/res/values-sq/strings.xml
+++ b/apk/res/values-sq/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Të shkruajë: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Menaxho lejet"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Koha: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktiviteti"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktiviteti"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Matjet e trupit"</string>
diff --git a/apk/res/values-sr/strings.xml b/apk/res/values-sr/strings.xml
index 6434c73..ea66fb1 100644
--- a/apk/res/values-sr/strings.xml
+++ b/apk/res/values-sr/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Писање: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Управљај дозволама"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Време: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Активности"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"активности"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Телесне мере"</string>
diff --git a/apk/res/values-sv/strings.xml b/apk/res/values-sv/strings.xml
index ed0718d..1aaa63b 100644
--- a/apk/res/values-sv/strings.xml
+++ b/apk/res/values-sv/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Skriv: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Hantera behörigheter"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Tid: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktivitet"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktivitet"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Kroppsmått"</string>
diff --git a/apk/res/values-sw/strings.xml b/apk/res/values-sw/strings.xml
index 81532f8..7569bbe 100644
--- a/apk/res/values-sw/strings.xml
+++ b/apk/res/values-sw/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Imeandika: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Dhibiti ruhusa"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Saa: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Shughuli"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"shughuli"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Vipimo vya mwili"</string>
diff --git a/apk/res/values-ta/strings.xml b/apk/res/values-ta/strings.xml
index 2b3298a..3d01298 100644
--- a/apk/res/values-ta/strings.xml
+++ b/apk/res/values-ta/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"எழுதும் அணுகல்: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"அனுமதிகளை நிர்வகியுங்கள்"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"நேரம்: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"செயல்பாடு"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"செயல்பாடு"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"உடல் அளவீடுகள்"</string>
diff --git a/apk/res/values-te/strings.xml b/apk/res/values-te/strings.xml
index 334d696..920331e 100644
--- a/apk/res/values-te/strings.xml
+++ b/apk/res/values-te/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"రాసిన విషయాలు: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"అనుమతులను మేనేజ్ చేయండి"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"సమయం: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"యాక్టివిటీ"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"యాక్టివిటీ"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"శారీరక సమాచారం"</string>
diff --git a/apk/res/values-th/strings.xml b/apk/res/values-th/strings.xml
index 81ba945..a69b79e 100644
--- a/apk/res/values-th/strings.xml
+++ b/apk/res/values-th/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"เขียน: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"จัดการสิทธิ์"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"เวลา: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"กิจกรรม"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"กิจกรรม"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"การตรวจวัดร่างกาย"</string>
diff --git a/apk/res/values-tl/strings.xml b/apk/res/values-tl/strings.xml
index fe3365c..8ee6619 100644
--- a/apk/res/values-tl/strings.xml
+++ b/apk/res/values-tl/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Na-write: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Pamahalaan ang mga pahintulot"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Oras: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Aktibidad"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"aktibidad"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Mga sukat ng katawan"</string>
diff --git a/apk/res/values-tr/strings.xml b/apk/res/values-tr/strings.xml
index 20f61ea..cee0fd2 100644
--- a/apk/res/values-tr/strings.xml
+++ b/apk/res/values-tr/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Yazma erişimi verilenler: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"İzinleri yönetin"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Zaman: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Etkinlik"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"etkinlik"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Vücut ölçümleri"</string>
diff --git a/apk/res/values-uk/strings.xml b/apk/res/values-uk/strings.xml
index c924215..d18885e 100644
--- a/apk/res/values-uk/strings.xml
+++ b/apk/res/values-uk/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Записано: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Керувати дозволами"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Час: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Активність"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"активність"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Параметри тіла"</string>
diff --git a/apk/res/values-ur/strings.xml b/apk/res/values-ur/strings.xml
index fc63566..9eda2e6 100644
--- a/apk/res/values-ur/strings.xml
+++ b/apk/res/values-ur/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"لکھا: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">"، "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"اجازتوں کا نظم کریں"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"وقت: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"سرگرمی"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"سرگرمی"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"جسمانی پیمائشوں سے متعلق ڈیٹا"</string>
diff --git a/apk/res/values-uz/strings.xml b/apk/res/values-uz/strings.xml
index d331b7a..44f69ed 100644
--- a/apk/res/values-uz/strings.xml
+++ b/apk/res/values-uz/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Yozish: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Ruxsatlarni boshqarish"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Vaqt: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Faollik"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"faollik"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Jismoniy koʻrsatkichlar"</string>
diff --git a/apk/res/values-vi/strings.xml b/apk/res/values-vi/strings.xml
index 0997d09..9071471 100644
--- a/apk/res/values-vi/strings.xml
+++ b/apk/res/values-vi/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Ghi: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Quản lý quyền"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Thời gian: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Hoạt động"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"hoạt động"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Số đo cơ thể"</string>
diff --git a/apk/res/values-zh-rCN/strings.xml b/apk/res/values-zh-rCN/strings.xml
index 7b77df6..eb3557e 100644
--- a/apk/res/values-zh-rCN/strings.xml
+++ b/apk/res/values-zh-rCN/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"写入:%s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">"、 "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"管理权限"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"时间:%s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"活动"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"活动"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"身体测量数据"</string>
diff --git a/apk/res/values-zh-rHK/strings.xml b/apk/res/values-zh-rHK/strings.xml
index 0fabfcd..e0d4dd4 100644
--- a/apk/res/values-zh-rHK/strings.xml
+++ b/apk/res/values-zh-rHK/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"寫入:%s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">"、 "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"管理權限"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"時間:%s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"活動"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"活動"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"身體測量數據"</string>
@@ -252,7 +253,7 @@
     <string name="request_permissions_privacy_policy" msgid="228503452643555737">"私隱權政策"</string>
     <string name="permissions_disconnect_dialog_title" msgid="7355211540619034695">"要移除所有權限嗎?"</string>
     <string name="permissions_disconnect_dialog_disconnect" msgid="8854787587948224752">"全部移除"</string>
-    <string name="permissions_disconnect_dialog_message" msgid="8679363015400954541">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」將無法在 Health Connect 中讀取或寫入任何資料。\n\n這不會影響這個應用程式可能擁有的其他權限,例如位置、相機或麥克風的存取權。"</string>
+    <string name="permissions_disconnect_dialog_message" msgid="8679363015400954541">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」將無法在「健康資料同步」中讀取或寫入任何資料。\n\n這不會影響這個應用程式可能擁有的其他權限,例如位置、相機或麥克風的存取權。"</string>
     <string name="permissions_disconnect_dialog_checkbox" msgid="8646951566431872823">"同時從 Health Connect 刪除「<xliff:g id="APP_NAME">%1$s</xliff:g>」的資料"</string>
     <string name="navigation_next_day" msgid="8853443471183944219">"下一天"</string>
     <string name="navigation_selected_day" msgid="2510843479734091348">"所選日期"</string>
@@ -712,8 +713,8 @@
     <string name="search_keywords_home" msgid="5386515593026555327">"健身, 健康"</string>
     <string name="search_keywords_permissions" msgid="7821010295153350533">"權限"</string>
     <string name="search_keywords_data" msgid="5359602744325490523">"health connect, 健康資料, 健康類型, 資料存取權, 活動, 身體測量數據, 經期追蹤, 營養, 睡眠, 健康數據"</string>
-    <string name="search_breadcrumbs_permissions" msgid="2667471090347475796">"Health Connect &gt; [應用程式權限]"</string>
-    <string name="search_breadcrumbs_data" msgid="6635428480372024040">"Health Connect &gt; [資料和存取權]"</string>
+    <string name="search_breadcrumbs_permissions" msgid="2667471090347475796">"健康資料同步 &gt; [應用程式權限]"</string>
+    <string name="search_breadcrumbs_data" msgid="6635428480372024040">"健康資料同步 &gt; [資料和存取權]"</string>
     <string name="search_connected_apps" msgid="8180770761876928851">"搜尋應用程式"</string>
     <string name="no_results" msgid="4007426147286897998">"沒有任何結果"</string>
     <string name="help" msgid="6028777453152686162">"說明"</string>
@@ -792,7 +793,7 @@
     <string name="edit_data_sources" msgid="79641360876849547">"編輯應用程式來源清單"</string>
     <string name="default_app_summary" msgid="6183876151011837062">"裝置預設設定"</string>
     <string name="app_data_title" msgid="6499967982291000837">"應用程式資料"</string>
-    <string name="no_data_footer" msgid="4777297654713673100">"有 Health Connect 存取權的應用程式所提供的資料會在這裡顯示"</string>
+    <string name="no_data_footer" msgid="4777297654713673100">"有「健康資料同步」存取權的應用程式所提供的資料會在這裡顯示"</string>
     <string name="date_picker_day" msgid="3076687507968958991">"日"</string>
     <string name="date_picker_week" msgid="1038805538316142229">"週"</string>
     <string name="date_picker_month" msgid="3560692391260778560">"月"</string>
diff --git a/apk/res/values-zh-rTW/strings.xml b/apk/res/values-zh-rTW/strings.xml
index 4ba3892..997f4d7 100644
--- a/apk/res/values-zh-rTW/strings.xml
+++ b/apk/res/values-zh-rTW/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="4768580772453324183">"Health Connect"</string>
+    <string name="app_label" msgid="4768580772453324183">"健康資料同步"</string>
     <string name="health_connect_summary" msgid="6401520186678972547">"管理應用程式的健康資料存取權"</string>
     <string name="permissions_and_data_header" msgid="4406105506837487805">"權限和資料"</string>
     <string name="home_subtitle" msgid="1750033322147357163">"管理手機上的健康與健身資料,並控管哪些應用程式可以存取這類資料"</string>
@@ -36,7 +36,7 @@
     <string name="data_sources_and_priority_title" msgid="2360222350913604558">"資料來源與優先順序"</string>
     <string name="set_units_title" msgid="2657822539603758029">"設定單位"</string>
     <string name="recent_access_header" msgid="7623497371790225888">"近期存取記錄"</string>
-    <string name="no_recent_access" msgid="4724297929902441784">"最近沒有任何應用程式存取 Health Connect"</string>
+    <string name="no_recent_access" msgid="4724297929902441784">"最近沒有任何應用程式存取「健康資料同步」"</string>
     <string name="show_recent_access_entries_button_title" msgid="3483460066767350419">"查看所有近期存取記錄"</string>
     <string name="recent_access_screen_description" msgid="331101209889185402">"查看過去 24 小時內有哪些應用程式曾存取你的資料"</string>
     <string name="today_header" msgid="1006837293203834373">"今天"</string>
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"寫入:%s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">"、 "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"管理權限"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"時間:%s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"活動"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"活動"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"身體測量資料"</string>
@@ -61,7 +62,7 @@
     <string name="manage_data_section" msgid="5859629270946511903">"管理資料"</string>
     <string name="export_data_button" msgid="7783329820434117744">"匯出資料"</string>
     <string name="delete_all_data_button" msgid="7238755635416521487">"刪除所有資料"</string>
-    <string name="no_categories" msgid="2636778482437506241">"你在 Health Connect 中沒有任何資料"</string>
+    <string name="no_categories" msgid="2636778482437506241">"你在「健康資料同步」平台沒有任何資料"</string>
     <string name="permission_types_title" msgid="7698058200557389436">"你的資料"</string>
     <string name="app_priority_button" msgid="3126133977893705098">"應用程式優先順序"</string>
     <string name="delete_category_data_button" msgid="2324773398768267043">"刪除<xliff:g id="CATEGORY">%s</xliff:g>資料"</string>
@@ -69,13 +70,13 @@
     <string name="can_read" msgid="4568261079308309564">"可以讀取<xliff:g id="PERMISSION_TYPE">%s</xliff:g>資料"</string>
     <string name="can_write" msgid="5082414937218423823">"可以寫入<xliff:g id="PERMISSION_TYPE">%s</xliff:g>資料"</string>
     <string name="inactive_apps" msgid="8956546286760797760">"已停用的應用程式"</string>
-    <string name="inactive_apps_message" msgid="4666501359079362486">"這些應用程式無法再寫入<xliff:g id="DATA_TYPE">%s</xliff:g>資料,但仍有資料儲存在 Health Connect 中"</string>
-    <string name="data_access_empty_message" msgid="9084350402254264452">"應用程式無法再讀取或寫入<xliff:g id="DATA_TYPE_0">%1$s</xliff:g>資料,而且 Health Connect 也未儲存<xliff:g id="DATA_TYPE_2">%2$s</xliff:g>資料。"</string>
+    <string name="inactive_apps_message" msgid="4666501359079362486">"這些應用程式無法再寫入<xliff:g id="DATA_TYPE">%s</xliff:g>資料,但仍有資料儲存在「健康資料同步」中"</string>
+    <string name="data_access_empty_message" msgid="9084350402254264452">"應用程式無法再讀取或寫入<xliff:g id="DATA_TYPE_0">%1$s</xliff:g>資料,而且「健康資料同步」也未儲存<xliff:g id="DATA_TYPE_2">%2$s</xliff:g>資料。"</string>
     <string name="data_access_exercise_description" msgid="6868583522699443570">"這類資料包括活動時間、運動類型、圈數、重複次數、時段或泳姿等資訊"</string>
     <string name="data_access_sleep_description" msgid="74293126050011153">"這類資料包括睡眠階段和睡眠時段"</string>
     <string name="all_entries_button" msgid="5109091107239135235">"查看所有資料"</string>
     <string name="delete_permission_type_data_button" msgid="2270819954943391797">"刪除這類資料"</string>
-    <string name="permgrouplab_health" msgid="468961137496587966">"Health Connect"</string>
+    <string name="permgrouplab_health" msgid="468961137496587966">"健康資料同步"</string>
     <string name="permgroupdesc_health" msgid="252080476917407273">"存取你的健康資料"</string>
     <string name="permlab_readCaloriesBurned" msgid="8998140381590624692">"讀取卡路里燃燒量資料"</string>
     <string name="permdesc_readCaloriesBurned" msgid="9012595355389868570">"允許應用程式讀取卡路里燃燒量資料"</string>
@@ -245,21 +246,21 @@
     <string name="request_permissions_allow" msgid="4201324235711040631">"允許"</string>
     <string name="request_permissions_allow_all" msgid="3419414351406638770">"全部允許"</string>
     <string name="request_permissions_dont_allow" msgid="6375307410951549030">"不允許"</string>
-    <string name="request_permissions_header_desc" msgid="5561173070722750153">"選擇要讓這個應用程式讀取或寫入 Health Connect 的資料"</string>
+    <string name="request_permissions_header_desc" msgid="5561173070722750153">"選擇要讓這個應用程式讀取或寫入「健康資料同步」的資料"</string>
     <string name="request_permissions_header_time_frame_desc" msgid="4617392728203291453">"如果授予讀取權限,這個應用程式將可讀取新資料和過去 30 天內的資料"</string>
-    <string name="request_permissions_header_title" msgid="4264236128614363479">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」存取 Health Connect 嗎?"</string>
+    <string name="request_permissions_header_title" msgid="4264236128614363479">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」存取「健康資料同步」嗎?"</string>
     <string name="request_permissions_rationale" msgid="6154280355215802538">"你可以參考「<xliff:g id="APP_NAME">%1$s</xliff:g>」開發人員的<xliff:g id="PRIVACY_POLICY_LINK">%2$s</xliff:g>,瞭解該應用程式如何處理你的資料"</string>
     <string name="request_permissions_privacy_policy" msgid="228503452643555737">"隱私權政策"</string>
     <string name="permissions_disconnect_dialog_title" msgid="7355211540619034695">"要移除所有權限嗎?"</string>
     <string name="permissions_disconnect_dialog_disconnect" msgid="8854787587948224752">"全部移除"</string>
-    <string name="permissions_disconnect_dialog_message" msgid="8679363015400954541">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」將無法在 Health Connect 中讀取或寫入任何資料。\n\n這不會影響這個應用程式可能擁有的其他權限,例如位置資訊、相機或麥克風的存取權。"</string>
-    <string name="permissions_disconnect_dialog_checkbox" msgid="8646951566431872823">"一併刪除 Health Connect 內的「<xliff:g id="APP_NAME">%1$s</xliff:g>」資料"</string>
+    <string name="permissions_disconnect_dialog_message" msgid="8679363015400954541">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」將無法在「健康資料同步」中讀取或寫入任何資料。\n\n這不會影響這個應用程式可能擁有的其他權限,例如位置資訊、相機或麥克風的存取權。"</string>
+    <string name="permissions_disconnect_dialog_checkbox" msgid="8646951566431872823">"一併刪除「健康資料同步」內的「<xliff:g id="APP_NAME">%1$s</xliff:g>」資料"</string>
     <string name="navigation_next_day" msgid="8853443471183944219">"後一天"</string>
     <string name="navigation_selected_day" msgid="2510843479734091348">"所選日期"</string>
     <string name="navigation_previous_day" msgid="718353386484938584">"前一天"</string>
     <string name="default_error" msgid="7966868260616403475">"發生錯誤,請再試一次。"</string>
     <string name="health_permission_header_description" msgid="7497601695462373927">"具有這項權限的應用程式可以讀取及寫入你的健康與健身資料。"</string>
-    <string name="connected_apps_text" msgid="1177626440966855831">"控制哪些應用程式可以存取 Health Connect 中儲存的資料。輕觸應用程式即可查看該應用程式可讀取或寫入的資料。"</string>
+    <string name="connected_apps_text" msgid="1177626440966855831">"控制哪些應用程式可以存取「健康資料同步」中儲存的資料。輕觸應用程式即可查看該應用程式可讀取或寫入的資料。"</string>
     <string name="connected_apps_section_title" msgid="2415288099612126258">"已允許存取"</string>
     <string name="not_connected_apps_section_title" msgid="452718769894103039">"不允許存取"</string>
     <string name="settings_and_help_header" msgid="5749710693017621168">"設定與說明"</string>
@@ -269,13 +270,13 @@
     <string name="no_apps_allowed" msgid="5794833581324128108">"未允許任何應用程式"</string>
     <string name="no_apps_denied" msgid="743327680286446017">"未拒絕任何應用程式"</string>
     <string name="permissions_disconnect_all_dialog_title" msgid="27474286046207122">"要移除所有應用程式的存取權嗎?"</string>
-    <string name="permissions_disconnect_all_dialog_message" msgid="3151109627457270499">"所有應用程式都將無法存取 Health Connect,也無法在當中新增資料。請注意,這項操作不會刪除任何現有資料。\n\n此操作也不會影響這個應用程式可能具備的其他權限,例如位置、相機或麥克風。"</string>
+    <string name="permissions_disconnect_all_dialog_message" msgid="3151109627457270499">"所有應用程式都將無法存取「健康資料同步」,也無法在當中新增資料。請注意,這項操作不會刪除任何現有資料。\n\n此操作也不會影響這個應用程式可能具備的其他權限,例如位置、相機或麥克風。"</string>
     <string name="permissions_disconnect_all_dialog_disconnect" msgid="2134136493310257746">"全部移除"</string>
     <string name="manage_permissions_manage_app_header" msgid="6356348062088358761">"管理應用程式"</string>
     <string name="see_app_data" msgid="3951030076195119476">"查看應用程式資料"</string>
     <string name="delete_app_data" msgid="6890357774873859952">"刪除應用程式資料"</string>
     <string name="inactive_apps_section_title" msgid="7492812973696378690">"已停用的應用程式"</string>
-    <string name="inactive_apps_section_message" msgid="2610789262055974739">"這些應用程式已無存取權,但仍有資料儲存在 Health Connect 中"</string>
+    <string name="inactive_apps_section_message" msgid="2610789262055974739">"這些應用程式已無存取權,但仍有資料儲存在「健康資料同步」中"</string>
     <string name="manage_permissions_time_frame" msgid="1299483940842401923">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」可以讀取 <xliff:g id="DATA_ACCESS_DATE">%2$s</xliff:g>後新增的資料"</string>
     <string name="other_android_permissions" msgid="8051485761573324702">"如要管理這個應用程式可以存取的其他 Android 權限,請依序前往「設定」&gt;「應用程式」"</string>
     <string name="manage_permissions_rationale" msgid="9183689798847740274">"你與「<xliff:g id="APP_NAME">%1$s</xliff:g>」分享的資料受該應用程式的隱私權政策規範"</string>
@@ -285,18 +286,18 @@
     <string name="app_access_title" msgid="7137018424885371763">"應用程式存取權"</string>
     <string name="connected_apps_empty_list_section_title" msgid="6821215432694207342">"你目前未安裝任何相容的應用程式"</string>
     <string name="denied_apps_banner_title" msgid="1997745063608657965">"應用程式權限已移除"</string>
-    <string name="denied_apps_banner_message_one_app" msgid="17659513485678315">"Health Connect 已移除「<xliff:g id="APP_DATA">%s</xliff:g>」的權限"</string>
-    <string name="denied_apps_banner_message_two_apps" msgid="1147216810892373640">"Health Connect 已移除「<xliff:g id="APP_DATA_0">%1$s</xliff:g>」和「<xliff:g id="APP_DATA_TWO">%2$s</xliff:g>」的權限"</string>
-    <string name="denied_apps_banner_message_three_apps" msgid="7978499051473471633">"Health Connect 已移除「<xliff:g id="APP_DATA_0">%1$s</xliff:g>」、「<xliff:g id="APP_DATA_TWO">%2$s</xliff:g>」和「<xliff:g id="APP_DATA_THREE">%3$s</xliff:g>」的權限"</string>
-    <string name="denied_apps_banner_message_many_apps" msgid="7249805432604650982">"Health Connect 已移除「<xliff:g id="APP_DATA_0">%1$s</xliff:g>」、「<xliff:g id="APP_DATA_TWO">%2$s</xliff:g>」、「<xliff:g id="APP_DATA_THREE">%3$s</xliff:g>」和其他應用程式的權限"</string>
+    <string name="denied_apps_banner_message_one_app" msgid="17659513485678315">"「健康資料同步」已移除「<xliff:g id="APP_DATA">%s</xliff:g>」的權限"</string>
+    <string name="denied_apps_banner_message_two_apps" msgid="1147216810892373640">"「健康資料同步」已移除「<xliff:g id="APP_DATA_0">%1$s</xliff:g>」和「<xliff:g id="APP_DATA_TWO">%2$s</xliff:g>」的權限"</string>
+    <string name="denied_apps_banner_message_three_apps" msgid="7978499051473471633">"「健康資料同步」已移除「<xliff:g id="APP_DATA_0">%1$s</xliff:g>」、「<xliff:g id="APP_DATA_TWO">%2$s</xliff:g>」和「<xliff:g id="APP_DATA_THREE">%3$s</xliff:g>」的權限"</string>
+    <string name="denied_apps_banner_message_many_apps" msgid="7249805432604650982">"「健康資料同步」已移除「<xliff:g id="APP_DATA_0">%1$s</xliff:g>」、「<xliff:g id="APP_DATA_TWO">%2$s</xliff:g>」、「<xliff:g id="APP_DATA_THREE">%3$s</xliff:g>」和其他應用程式的權限"</string>
     <string name="denied_apps_banner_button" msgid="4438480389769298412">"查看詳細資料"</string>
-    <string name="denied_apps_dialog_title" msgid="7470227827315635099">"Health Connect 移除應用程式權限的原因"</string>
-    <string name="denied_apps_dialog_message" msgid="7876664965504466099">"如果應用程式在 Google Play 中遭到停權或移除,Health Connect 會自動移除該應用程式的權限。\n\n這表示該應用程式無法再存取 Health Connect 中儲存的資料。如果應用程式曾寫入資料,就會列在已停用應用程式的清單中。"</string>
+    <string name="denied_apps_dialog_title" msgid="7470227827315635099">"「健康資料同步」移除應用程式權限的原因"</string>
+    <string name="denied_apps_dialog_message" msgid="7876664965504466099">"如果應用程式在 Google Play 中遭到停權或移除,「健康資料同步」會自動移除該應用程式的權限。\n\n這表示該應用程式無法再存取「健康資料同步」中儲存的資料。如果應用程式曾寫入資料,就會列在已停用應用程式的清單中。"</string>
     <string name="denied_apps_dialog_got_it_button" msgid="4698003516923683959">"我知道了"</string>
-    <string name="onboarding_title" msgid="7930941018430608076">"開始使用 Health Connect"</string>
-    <string name="onboarding_description" msgid="4873129122057931161">"Health Connect 會儲存健康與健身資料,方便你在手機上同步不同應用程式的資料"</string>
+    <string name="onboarding_title" msgid="7930941018430608076">"開始使用「健康資料同步」"</string>
+    <string name="onboarding_description" msgid="4873129122057931161">"「健康資料同步」會儲存健康與健身資料,方便你在手機上同步不同應用程式的資料"</string>
     <string name="share_data" msgid="3481932156368883946">"與應用程式分享資料"</string>
-    <string name="share_data_description" msgid="2919871301634375092">"選擇每個應用程式可讀取或寫入 Health Connect 的資料"</string>
+    <string name="share_data_description" msgid="2919871301634375092">"選擇每個應用程式可讀取或寫入「健康資料同步」的資料"</string>
     <string name="manage_your_settings" msgid="7391184508015127137">"管理設定和隱私權"</string>
     <string name="manage_your_settings_description" msgid="557943168930365334">"你隨時可以變更應用程式權限及管理資料"</string>
     <string name="onboarding_go_back_button_text" msgid="5020083846511184625">"返回"</string>
@@ -304,10 +305,10 @@
     <string name="delete_button_content_description" msgid="9125115327455379618">"刪除資料"</string>
     <string name="time_range_title" msgid="6831605283322600165">"選擇要刪除的資料"</string>
     <string name="time_range_next_button" msgid="5849096934896557888">"繼續"</string>
-    <string name="time_range_message_all" msgid="7280888587242744729">"這麼做會永久刪除在指定時間範圍內新增到 Health Connect 的所有資料"</string>
-    <string name="time_range_message_data_type" msgid="1896125004829258195">"這麼做會永久刪除在指定時間範圍內新增到 Health Connect 的<xliff:g id="DATA_TYPE">%s</xliff:g>資料"</string>
-    <string name="time_range_message_category" msgid="1136451418397326356">"這麼做會永久刪除在指定時間範圍內新增到 Health Connect 的<xliff:g id="CATEGORY">%s</xliff:g>資料"</string>
-    <string name="time_range_message_app_data" msgid="2590800457710603556">"這麼做會永久刪除在指定時間範圍內新增到 Health Connect 的「<xliff:g id="APP_DATA">%s</xliff:g>」資料"</string>
+    <string name="time_range_message_all" msgid="7280888587242744729">"這麼做會永久刪除在指定時間範圍內新增到「健康資料同步」的所有資料"</string>
+    <string name="time_range_message_data_type" msgid="1896125004829258195">"這麼做會永久刪除在指定時間範圍內新增到「健康資料同步」的<xliff:g id="DATA_TYPE">%s</xliff:g>資料"</string>
+    <string name="time_range_message_category" msgid="1136451418397326356">"這麼做會永久刪除在指定時間範圍內新增到「健康資料同步」的<xliff:g id="CATEGORY">%s</xliff:g>資料"</string>
+    <string name="time_range_message_app_data" msgid="2590800457710603556">"這麼做會永久刪除在指定時間範圍內新增到「健康資料同步」的「<xliff:g id="APP_DATA">%s</xliff:g>」資料"</string>
     <string name="time_range_one_day" msgid="7162709826595446727">"刪除過去 24 小時的資料"</string>
     <string name="time_range_one_week" msgid="8754523384275645434">"刪除過去 7 天的資料"</string>
     <string name="time_range_one_month" msgid="3034747870231999766">"刪除過去 30 天的資料"</string>
@@ -328,23 +329,23 @@
     <string name="confirming_question_app_data_one_day" msgid="444028969015975031">"要永久刪除過去 24 小時的「<xliff:g id="APP_DATA">%s</xliff:g>」資料嗎?"</string>
     <string name="confirming_question_app_data_one_week" msgid="2096555081811730496">"要永久刪除過去 7 天的「<xliff:g id="APP_DATA">%s</xliff:g>」資料嗎?"</string>
     <string name="confirming_question_app_data_one_month" msgid="6438241250825892892">"要永久刪除過去 30 天的「<xliff:g id="APP_DATA">%s</xliff:g>」資料嗎?"</string>
-    <string name="confirming_question_app_remove_all_permissions" msgid="4170343072352701421">"一併移除「<xliff:g id="APP_WITH_PERMISSIONS">%s</xliff:g>」在 Health Connect 中的所有權限"</string>
+    <string name="confirming_question_app_remove_all_permissions" msgid="4170343072352701421">"一併移除「<xliff:g id="APP_WITH_PERMISSIONS">%s</xliff:g>」在「健康資料同步」中的所有權限"</string>
     <string name="confirming_question_data_type_from_app_all" msgid="8361163993548510509">"要永久刪除「<xliff:g id="APP_DATA">%2$s</xliff:g>」新增的所有<xliff:g id="DATA_TYPE">%1$s</xliff:g>資料嗎?"</string>
     <string name="confirming_question_single_entry" msgid="330919962071369305">"要永久刪除這個項目嗎?"</string>
-    <string name="confirming_question_message" msgid="2934249835529079545">"已連結的應用程式將無法再從 Health Connect 存取這類資料"</string>
+    <string name="confirming_question_message" msgid="2934249835529079545">"已連結的應用程式將無法再從「健康資料同步」存取這類資料"</string>
     <string name="confirming_question_message_menstruation" msgid="5286956266565962430">"這將刪除從 <xliff:g id="START_DATE">%1$s</xliff:g>到 <xliff:g id="END_DATE">%2$s</xliff:g>的經期紀錄。"</string>
     <string name="confirming_question_delete_button" msgid="1999996759507959985">"刪除"</string>
     <string name="confirming_question_go_back_button" msgid="9037523726124648221">"返回"</string>
     <string name="delete_dialog_success_got_it_button" msgid="8047812840310612293">"完成"</string>
     <string name="delete_dialog_failure_close_button" msgid="4376647579348193224">"關閉"</string>
     <string name="delete_dialog_success_title" msgid="5009733262743173477">"資料已刪除"</string>
-    <string name="delete_dialog_success_message" msgid="2451953113522118128">"Health Connect 中已經沒有此資料。"</string>
+    <string name="delete_dialog_success_message" msgid="2451953113522118128">"「健康資料同步」中已經沒有此資料。"</string>
     <string name="delete_progress_indicator" msgid="5799502879065833417">"正在刪除資料"</string>
     <string name="delete_dialog_failure_title" msgid="1959020721355789496">"無法刪除資料"</string>
-    <string name="delete_dialog_failure_message" msgid="7473241488471319963">"發生錯誤,Health Connect 無法刪除資料"</string>
+    <string name="delete_dialog_failure_message" msgid="7473241488471319963">"發生錯誤,「健康資料同步」無法刪除資料"</string>
     <string name="delete_dialog_failure_try_again_button" msgid="4323865124609424838">"再試一次"</string>
-    <string name="delete_data_notification_title" msgid="7740230240986343347">"正在刪除 Health Connect 資料"</string>
-    <string name="delete_data_notification_ticker_text" msgid="2604051567679235822">"正在刪除 Health Connect 資料"</string>
+    <string name="delete_data_notification_title" msgid="7740230240986343347">"正在刪除「健康資料同步」資料"</string>
+    <string name="delete_data_notification_ticker_text" msgid="2604051567679235822">"正在刪除「健康資料同步」資料"</string>
     <string name="delete_data_notification_channel_name" msgid="4499713830012802095">"資料刪除"</string>
     <string name="data_point_action_content_description" msgid="7872439279343967754">"刪除資料項目"</string>
     <string name="delete_data_point" msgid="4234569507133768630">"刪除項目"</string>
@@ -677,33 +678,33 @@
     <string name="temperature_unit_fahrenheit_label" msgid="6590261955872562854">"華氏"</string>
     <string name="temperature_unit_kelvin_label" msgid="3786210768294615821">"克耳文"</string>
     <string name="help_and_feedback" msgid="4772169905005369871">"說明與意見回饋"</string>
-    <string name="cant_see_all_your_apps_description" msgid="7344859063463536472">"如果沒看到已安裝的應用程式,可能是該應用程式與 Health Connect 尚不相容"</string>
+    <string name="cant_see_all_your_apps_description" msgid="7344859063463536472">"如果沒看到已安裝的應用程式,可能是該應用程式與「健康資料同步」尚不相容"</string>
     <string name="things_to_try" msgid="8200374691546152703">"試試其他選項"</string>
     <string name="check_for_updates" msgid="3841090978657783101">"檢查更新"</string>
     <string name="check_for_updates_description" msgid="1347667778199095160">"確認所有安裝的應用程式均為最新版本"</string>
     <string name="see_all_compatible_apps" msgid="6791146164535475726">"顯示所有相容的應用程式"</string>
     <string name="see_all_compatible_apps_description" msgid="2092325337403254491">"在 Google Play 上搜尋應用程式"</string>
     <string name="send_feedback" msgid="7756927746070096780">"提供意見"</string>
-    <string name="send_feedback_description" msgid="2887207112856240778">"告訴我們你希望哪些健康與健身應用程式可搭配 Health Connect 使用"</string>
+    <string name="send_feedback_description" msgid="2887207112856240778">"告訴我們你希望哪些健康與健身應用程式可搭配「健康資料同步」使用"</string>
     <string name="playstore_app_title" msgid="4138464328693481809">"Play 商店"</string>
     <string name="auto_delete_button" msgid="8536451792268513619">"自動刪除"</string>
     <string name="auto_delete_title" msgid="8761742828224207826">"自動刪除"</string>
-    <string name="auto_delete_header" msgid="4258649705159293715">"如要控制 Health Connect 的資料保留期限,可以安排系統在一段時間後刪除資料"</string>
+    <string name="auto_delete_header" msgid="4258649705159293715">"如要控制「健康資料同步」的資料保留期限,可以安排系統在一段時間後刪除資料"</string>
     <string name="auto_delete_learn_more" msgid="7416469042791307994">"進一步瞭解自動刪除功能"</string>
     <string name="auto_delete_section" msgid="7732381000331475082">"自動刪除資料"</string>
     <string name="range_after_x_months" msgid="3340127072680117121">"{count,plural, =1{# 個月後}other{# 個月後}}"</string>
     <string name="range_never" msgid="4429478261788361233">"永不"</string>
     <string name="range_off" msgid="8178520557618184215">"已關閉"</string>
-    <string name="auto_delete_rationale" msgid="5255442126521464878">"如果變更這些設定,Health Connect 將根據新的偏好設定刪除現有資料"</string>
+    <string name="auto_delete_rationale" msgid="5255442126521464878">"如果變更這些設定,「健康資料同步」將根據新的偏好設定刪除現有資料"</string>
     <string name="confirming_question_x_months" msgid="8204363800605282103">"{count,plural, =1{要在 # 個月後自動刪除資料嗎?}other{要在 # 個月後自動刪除資料嗎?}}"</string>
-    <string name="confirming_message_x_months" msgid="4798474593741471977">"{count,plural, =1{Health Connect 將只保存 # 個月內的資料。如果選擇這項設定,系統將一併刪除 # 個月前的現有資料。}other{Health Connect 將只保存 # 個月內的資料。如果選擇這項設定,系統將一併刪除 # 個月前的現有資料。}}"</string>
+    <string name="confirming_message_x_months" msgid="4798474593741471977">"{count,plural, =1{「健康資料同步」將只保存 # 個月內的資料。如果選擇這項設定,系統將一併刪除 # 個月前的現有資料。}other{「健康資料同步」將只保存 # 個月內的資料。如果選擇這項設定,系統將一併刪除 # 個月前的現有資料。}}"</string>
     <string name="set_auto_delete_button" msgid="268450418318199197">"設定自動刪除功能"</string>
     <string name="deletion_started_title" msgid="1177766097121885025">"系統將刪除現有資料"</string>
-    <string name="deletion_started_x_months" msgid="6567199107249615612">"{count,plural, =1{Health Connect 將刪除 # 個月前的所有資料。可能需要一天的時間,連結的應用程式才會反映這些變更。}other{Health Connect 將刪除 # 個月前的所有資料。可能需要一天的時間,連結的應用程式才會反映這些變更。}}"</string>
+    <string name="deletion_started_x_months" msgid="6567199107249615612">"{count,plural, =1{「健康資料同步」將刪除 # 個月前的所有資料。可能需要一天的時間,連結的應用程式才會反映這些變更。}other{「健康資料同步」將刪除 # 個月前的所有資料。可能需要一天的時間,連結的應用程式才會反映這些變更。}}"</string>
     <string name="deletion_started_category_list_section" msgid="3052940611815658991">"系統會刪除以下類別的資料"</string>
     <string name="deletion_started_done_button" msgid="1232018689825054257">"完成"</string>
     <string name="priority_dialog_title" msgid="7360654442596118085">"設定應用程式優先順序"</string>
-    <string name="priority_dialog_message" msgid="6971250365335018184">"如有多個應用程式新增<xliff:g id="DATA_TYPE">%s</xliff:g>資料,Health Connect 會根據應用程式在這份清單的先後順序判斷重要性 (位置越高就表示越重要)。拖曳應用程式即可重新排序。"</string>
+    <string name="priority_dialog_message" msgid="6971250365335018184">"如有多個應用程式新增<xliff:g id="DATA_TYPE">%s</xliff:g>資料,「健康資料同步」會根據應用程式在這份清單的先後順序判斷重要性 (位置越高就表示越重要)。拖曳應用程式即可重新排序。"</string>
     <string name="priority_dialog_positive_button" msgid="2503570694373675092">"儲存"</string>
     <string name="action_drag_label_move_up" msgid="4221641798253080966">"向上移"</string>
     <string name="action_drag_label_move_down" msgid="3448000958912947588">"向下移"</string>
@@ -711,76 +712,76 @@
     <string name="action_drag_label_move_bottom" msgid="3117764196696569512">"移至底部"</string>
     <string name="search_keywords_home" msgid="5386515593026555327">"健身, 健康"</string>
     <string name="search_keywords_permissions" msgid="7821010295153350533">"權限"</string>
-    <string name="search_keywords_data" msgid="5359602744325490523">"Health Connect, 健康資料, 健康類別, 資料存取, 活動, 身體測量資料, 經期追蹤, 營養, 睡眠, 生命徵象"</string>
-    <string name="search_breadcrumbs_permissions" msgid="2667471090347475796">"Health Connect &gt; 應用程式權限"</string>
-    <string name="search_breadcrumbs_data" msgid="6635428480372024040">"Health Connect &gt; 資料和存取權"</string>
+    <string name="search_keywords_data" msgid="5359602744325490523">"健康資料同步, 健康資料, 健康類別, 資料存取, 活動, 身體測量資料, 經期追蹤, 營養, 睡眠, 生命徵象"</string>
+    <string name="search_breadcrumbs_permissions" msgid="2667471090347475796">"健康資料同步 &gt; 應用程式權限"</string>
+    <string name="search_breadcrumbs_data" msgid="6635428480372024040">"健康資料同步 &gt; 資料和存取權"</string>
     <string name="search_connected_apps" msgid="8180770761876928851">"搜尋應用程式"</string>
     <string name="no_results" msgid="4007426147286897998">"找不到相符的搜尋結果"</string>
     <string name="help" msgid="6028777453152686162">"說明"</string>
-    <string name="request_route_header_title" msgid="6599707039845646714">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」存取 Health Connect 中的這條運動路線嗎?"</string>
+    <string name="request_route_header_title" msgid="6599707039845646714">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」存取「健康資料同步」中的這條運動路線嗎?"</string>
     <string name="request_route_disclaimer_notice" msgid="8060511384737662598">"這個應用程式將可讀取路線中的過去位置資訊"</string>
     <string name="date_owner_format" msgid="4431196384037157320">"<xliff:g id="DATE">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
     <string name="request_route_info_header_title" msgid="4149969049719763190">"運動路線包含位置資訊"</string>
     <string name="request_route_info_who_can_see_data_title" msgid="858355329937113994">"誰可以查看這項資料?"</string>
     <string name="request_route_info_who_can_see_data_summary" msgid="2439434359808367150">"只有你允許的應用程式能存取運動路線"</string>
     <string name="request_route_info_access_management_title" msgid="3222594923675464852">"如何管理存取權?"</string>
-    <string name="request_route_info_access_management_summary" msgid="2606548838292829495">"你可以在 Health Connect 設定中管理應用程式的運動路線存取權"</string>
+    <string name="request_route_info_access_management_summary" msgid="2606548838292829495">"你可以在「健康資料同步」設定中管理應用程式的運動路線存取權"</string>
     <string name="back_button" msgid="780519527385993407">"返回"</string>
     <string name="loading" msgid="2526615755685950317">"載入中…"</string>
     <string name="migration_in_progress_screen_title" msgid="6564515269988205874">"整合中"</string>
-    <string name="migration_in_progress_screen_integration_details" msgid="5916989113111973466">"Health Connect 正在與 Android 系統整合。\n\n系統轉移資料和權限時可能需要一些時間。"</string>
+    <string name="migration_in_progress_screen_integration_details" msgid="5916989113111973466">"「健康資料同步」正在與 Android 系統整合。\n\n系統轉移資料和權限時可能需要一些時間。"</string>
     <string name="migration_in_progress_screen_integration_dont_close" msgid="2095732208438772444">"收到程序完成的通知之前,請不要關閉應用程式。"</string>
-    <string name="migration_in_progress_notification_title" msgid="8873411008158407737">"Health Connect 整合中"</string>
+    <string name="migration_in_progress_notification_title" msgid="8873411008158407737">"「健康資料同步」整合中"</string>
     <string name="migration_update_needed_screen_title" msgid="3260466598312877429">"需要更新"</string>
-    <string name="migration_update_needed_screen_details" msgid="7984745102006782603">"Health Connect 正在與 Android 系統整合,以便你直接從設定頁面存取。"</string>
+    <string name="migration_update_needed_screen_details" msgid="7984745102006782603">"「健康資料同步」正在與 Android 系統整合,方便你直接從設定頁面存取。"</string>
     <string name="update_button" msgid="4544529019832009496">"更新"</string>
-    <string name="migration_update_needed_notification_content" msgid="478899618719297517">"開始執行這項更新作業,讓 Health Connect 能夠繼續與系統設定整合"</string>
+    <string name="migration_update_needed_notification_content" msgid="478899618719297517">"開始執行這項更新作業,讓「健康資料同步」能夠繼續與系統設定整合"</string>
     <string name="migration_update_needed_notification_action" msgid="1219223694165492000">"立即更新"</string>
     <string name="migration_module_update_needed_notification_title" msgid="5428523284357105379">"需要完成系統更新"</string>
     <string name="migration_module_update_needed_action" msgid="7211167950758064289">"繼續操作前,請更新你的手機系統。"</string>
     <string name="migration_module_update_needed_restart" msgid="1246884613546321798">"如果你已更新手機系統,請嘗試重新啟動手機,繼續進行整合"</string>
-    <string name="migration_app_update_needed_notification_title" msgid="8971076370900025444">"Health Connect 需要更新"</string>
-    <string name="migration_app_update_needed_action" msgid="3289432528592774601">"繼續操作前,請將 Health Connect 應用程式更新到最新版本。"</string>
+    <string name="migration_app_update_needed_notification_title" msgid="8971076370900025444">"「健康資料同步」需要更新"</string>
+    <string name="migration_app_update_needed_action" msgid="3289432528592774601">"繼續操作前,請將「健康資料同步」應用程式更新到最新版本。"</string>
     <string name="migration_more_space_needed_screen_title" msgid="1535473230886051579">"需要更多空間"</string>
-    <string name="migration_more_space_needed_screen_details" msgid="621140247825603412">"Health Connect 需要 <xliff:g id="SPACE_NEEDED">%1$s</xliff:g> 的手機儲存空間才能繼續整合。\n\n請在手機上清出一些空間,然後再試一次。"</string>
+    <string name="migration_more_space_needed_screen_details" msgid="621140247825603412">"「健康資料同步」需要 <xliff:g id="SPACE_NEEDED">%1$s</xliff:g> 的手機儲存空間才能繼續整合。\n\n請在手機上清出一些空間,然後再試一次。"</string>
     <string name="try_again_button" msgid="8745496819992160789">"再試一次"</string>
     <string name="free_up_space_button" msgid="4141013808635654695">"釋出空間"</string>
     <string name="migration_more_space_needed_notification_title" msgid="8238155395120107672">"需要更多空間"</string>
-    <string name="migration_more_space_needed_notification_content" msgid="4034728181940567836">"Health Connect 需要 <xliff:g id="SPACE_NEEDED">%1$s</xliff:g> 的手機儲存空間才能繼續整合。"</string>
+    <string name="migration_more_space_needed_notification_content" msgid="4034728181940567836">"「健康資料同步」需要 <xliff:g id="SPACE_NEEDED">%1$s</xliff:g> 的手機儲存空間才能繼續整合。"</string>
     <string name="migration_paused_screen_title" msgid="8041170155372429894">"已暫停整合"</string>
-    <string name="migration_paused_screen_details" msgid="5898311710030340187">"Health Connect 應用程式在與 Android 系統整合時關閉了。\n\n請按一下「繼續」重新開啟應用程式,繼續轉移資料和權限。"</string>
-    <string name="migration_paused_screen_details_timeout" msgid="353768000785837394">"為了保留你的 Health Connect 資料,請在 <xliff:g id="TIME_NEEDED">%1$s</xliff:g>內完成這項操作"</string>
+    <string name="migration_paused_screen_details" msgid="5898311710030340187">"「健康資料同步」應用程式在與 Android 系統整合時關閉了。\n\n請按一下「繼續」重新開啟應用程式,繼續轉移資料和權限。"</string>
+    <string name="migration_paused_screen_details_timeout" msgid="353768000785837394">"為了保留你的「健康資料同步」資料,請在 <xliff:g id="TIME_NEEDED">%1$s</xliff:g>內完成這項操作"</string>
     <string name="resume_button" msgid="2255148549862208047">"繼續"</string>
     <string name="migration_paused_notification_title" msgid="4368414714202113077">"整合已暫停"</string>
-    <string name="migration_paused_notification_content" msgid="1950511270109811771">"Health Connect 正在與 Android 系統整合。輕觸即可繼續"</string>
+    <string name="migration_paused_notification_content" msgid="1950511270109811771">"「健康資料同步」正在與 Android 系統整合。輕觸即可繼續"</string>
     <string name="resume_migration_banner_title" msgid="4443957114824045317">"繼續整合"</string>
-    <string name="resume_migration_banner_description" msgid="6236230413670826036">"輕觸一下即可繼續讓 Health Connect 與 Android 系統整合。為了保留你的資料,請在 <xliff:g id="TIME_NEEDED">%1$s</xliff:g>內完成這項操作"</string>
-    <string name="resume_migration_banner_description_fallback" msgid="6060444898839211883">"輕觸一下即可繼續讓 Health Connect 與 Android 系統整合。"</string>
+    <string name="resume_migration_banner_description" msgid="6236230413670826036">"輕觸一下即可繼續讓「健康資料同步」與 Android 系統整合。為了保留你的資料,請在 <xliff:g id="TIME_NEEDED">%1$s</xliff:g>內完成這項操作"</string>
+    <string name="resume_migration_banner_description_fallback" msgid="6060444898839211883">"輕觸一下即可繼續讓「健康資料同步」與 Android 系統整合。"</string>
     <string name="resume_migration_banner_button" msgid="2112318760107756469">"繼續"</string>
-    <string name="resume_migration_notification_title" msgid="8859575633668908327">"繼續整合 Health Connect"</string>
+    <string name="resume_migration_notification_title" msgid="8859575633668908327">"繼續整合「健康資料同步」"</string>
     <string name="resume_migration_notification_content" msgid="46172108837648715">"為了保留你的資料,請在 <xliff:g id="TIME_NEEDED">%1$s</xliff:g>內完成這項操作"</string>
     <string name="app_update_needed_banner_title" msgid="4724335956851853802">"應用程式需要更新"</string>
-    <string name="app_update_needed_banner_description_single" msgid="2229935331303234217">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」必須是最新版本,才能持續與 Health Connect 搭配運作"</string>
-    <string name="app_update_needed_banner_description_multiple" msgid="1523113182062764912">"部分應用程式必須是最新版本,才能持續與 Health Connect 搭配運作"</string>
+    <string name="app_update_needed_banner_description_single" msgid="2229935331303234217">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」必須是最新版本,才能持續與「健康資料同步」搭配運作"</string>
+    <string name="app_update_needed_banner_description_multiple" msgid="1523113182062764912">"部分應用程式必須是最新版本,才能持續與「健康資料同步」搭配運作"</string>
     <string name="app_update_needed_banner_button" msgid="8223115764065649627">"檢查更新"</string>
-    <string name="migration_pending_permissions_dialog_title" msgid="6019552841791757048">"Health Connect 整合作業"</string>
-    <string name="migration_pending_permissions_dialog_content" msgid="6350115816948005466">"Health Connect 已準備好與 Android 系統整合。如果你現在授予「<xliff:g id="APP_NAME">%1$s</xliff:g>」存取權限,在整合作業完成前,部分功能可能會無法運作。"</string>
-    <string name="migration_pending_permissions_dialog_content_apps" msgid="6417173899016940664">"Health Connect 已準備就緒,可以與 Android 系統整合。如果現在將權限授予應用程式,部分功能在整合完成前可能無法運作。"</string>
+    <string name="migration_pending_permissions_dialog_title" msgid="6019552841791757048">"「健康資料同步」整合作業"</string>
+    <string name="migration_pending_permissions_dialog_content" msgid="6350115816948005466">"「健康資料同步」已準備好與 Android 系統整合。如果你現在授予「<xliff:g id="APP_NAME">%1$s</xliff:g>」存取權限,在整合作業完成前,部分功能可能會無法運作。"</string>
+    <string name="migration_pending_permissions_dialog_content_apps" msgid="6417173899016940664">"「健康資料同步」已準備就緒,可以與 Android 系統整合。如果現在將權限授予應用程式,部分功能在整合完成前可能無法運作。"</string>
     <string name="migration_pending_permissions_dialog_button_continue" msgid="258571372365364506">"繼續"</string>
     <string name="migration_pending_permissions_dialog_button_start_integration" msgid="754910196871313049">"開始整合"</string>
-    <string name="migration_in_progress_permissions_dialog_title" msgid="2188354144857156984">"Health Connect 整合中"</string>
-    <string name="migration_in_progress_permissions_dialog_content" msgid="2249793103623253693">"Health Connect 正在與 Android 系統整合。\n\n你會在程序完成時接到通知,屆時即可搭配使用「<xliff:g id="APP_NAME">%1$s</xliff:g>」和 Health Connect。"</string>
-    <string name="migration_in_progress_permissions_dialog_content_apps" msgid="8653954808926889199">"Health Connect 正在與 Android 系統整合。\n\n你會在整合完成後收到通知,屆時就能使用 Health Connect。"</string>
+    <string name="migration_in_progress_permissions_dialog_title" msgid="2188354144857156984">"「健康資料同步」整合中"</string>
+    <string name="migration_in_progress_permissions_dialog_content" msgid="2249793103623253693">"「健康資料同步」正在與 Android 系統整合。\n\n你會在程序完成時接到通知,屆時即可搭配使用「<xliff:g id="APP_NAME">%1$s</xliff:g>」和「健康資料同步」。"</string>
+    <string name="migration_in_progress_permissions_dialog_content_apps" msgid="8653954808926889199">"「健康資料同步」正在與 Android 系統整合。\n\n你會在整合完成後收到通知,屆時就能使用「健康資料同步」。"</string>
     <string name="migration_in_progress_permissions_dialog_button_got_it" msgid="3437208109334974656">"我知道了"</string>
-    <string name="migration_not_complete_dialog_title" msgid="3725576338159027149">"Health Connect 未完成整合"</string>
+    <string name="migration_not_complete_dialog_title" msgid="3725576338159027149">"「健康資料同步」未完成整合"</string>
     <string name="migration_not_complete_dialog_content" msgid="4992771587233088606">"你會在能夠再次整合時收到通知。"</string>
     <string name="migration_not_complete_dialog_button" msgid="3271842109680807482">"我知道了"</string>
-    <string name="migration_not_complete_notification_title" msgid="7392885522310227293">"Health Connect 未完成整合"</string>
+    <string name="migration_not_complete_notification_title" msgid="7392885522310227293">"「健康資料同步」未完成整合"</string>
     <string name="migration_not_complete_notification_action" msgid="757041885992445657">"閱讀完整內容"</string>
-    <string name="migration_complete_notification_title" msgid="4988631739109332404">"Health Connect 整合完成"</string>
+    <string name="migration_complete_notification_title" msgid="4988631739109332404">"「健康資料同步」整合完成"</string>
     <string name="migration_complete_notification_action" msgid="5350322865206331186">"開啟"</string>
     <string name="migration_whats_new_dialog_title" msgid="2349465358457105228">"新功能"</string>
-    <string name="migration_whats_new_dialog_content" msgid="1271560399054864488">"你現在可以直接從設定頁面存取 Health Connect,也可以隨時解除安裝 Health Connect 應用程式,釋出儲存空間。"</string>
+    <string name="migration_whats_new_dialog_content" msgid="1271560399054864488">"你現在可以直接從設定頁面存取「健康資料同步」,也可以隨時解除安裝「健康資料同步」應用程式,釋出儲存空間。"</string>
     <string name="migration_whats_new_dialog_button" msgid="642575552457587805">"我知道了"</string>
     <string name="data_totals_header" msgid="8316977153276216025">"資料總數"</string>
     <string name="app_sources_header" msgid="6343062519512947665">"應用程式來源"</string>
@@ -792,7 +793,7 @@
     <string name="edit_data_sources" msgid="79641360876849547">"編輯應用程式來源清單"</string>
     <string name="default_app_summary" msgid="6183876151011837062">"裝置預設設定"</string>
     <string name="app_data_title" msgid="6499967982291000837">"應用程式資料"</string>
-    <string name="no_data_footer" msgid="4777297654713673100">"如果資料是來自有權存取 Health Connect 的應用程式,就會顯示在這裡"</string>
+    <string name="no_data_footer" msgid="4777297654713673100">"如果資料是來自有權存取「健康資料同步」的應用程式,就會顯示在這裡"</string>
     <string name="date_picker_day" msgid="3076687507968958991">"一天"</string>
     <string name="date_picker_week" msgid="1038805538316142229">"一週"</string>
     <string name="date_picker_month" msgid="3560692391260778560">"一個月"</string>
diff --git a/apk/res/values-zu/strings.xml b/apk/res/values-zu/strings.xml
index 6b00919..2b6e8c1 100644
--- a/apk/res/values-zu/strings.xml
+++ b/apk/res/values-zu/strings.xml
@@ -45,6 +45,7 @@
     <string name="write_data_access_label" msgid="7955988316773000250">"Bhala: %s"</string>
     <string name="data_type_separator" msgid="1299848322898210658">", "</string>
     <string name="manage_permissions" msgid="8394221950712608160">"Lawula izimvume"</string>
+    <string name="recent_access_time_content_descritption" msgid="1709675393952226273">"Isikhathi: %s"</string>
     <string name="activity_category_uppercase" msgid="136628843341377088">"Umsebenzi"</string>
     <string name="activity_category_lowercase" msgid="3007220578865400601">"umsebenzi"</string>
     <string name="body_measurements_category_uppercase" msgid="422923782603313038">"Ukulinganisela umzimba"</string>
diff --git a/apk/res/values/strings.xml b/apk/res/values/strings.xml
index cb1893d..609e6fa 100644
--- a/apk/res/values/strings.xml
+++ b/apk/res/values/strings.xml
@@ -51,6 +51,7 @@
     <string name="write_data_access_label" description="Label used to indicate that app performed write (insertion, deletion or update) access to specified data types (as opposed to reading the data), as in 'Write: Steps, Heart rate'. [CHAR_LIMIT=NONE]">Write: %s</string>
     <string name="data_type_separator" description="Separator (comma and whitespace in English) for a potentially long list of data type names listed in data access entry, as in 'Write: Steps, Distance, Calories'. [CHAR_LIMIT=NONE]">,\u0020</string>
     <string name="manage_permissions" description="Text for floating action button that takes users from Recent Access apps to the App permissions screen [CHAR LIMIT=40]">Manage permissions</string>
+    <string name="recent_access_time_content_descritption" description="Content description for time section of recent access preference providing better context for accessibility talk back. [CHAR_LIMIT=NONE]">Time: %s</string>
     <!--endregion-->
 
     <!--region Data categories  -->
@@ -949,6 +950,9 @@
     <string name="action_drag_label_move_down" description="Label for an accessibility action that moves an app down in the ordered app priority list [CHAR LIMIT=50]">Move down</string>
     <string name="action_drag_label_move_top" description="Label for an accessibility action that moves an app to the top of the ordered app priority list [CHAR LIMIT=50]">Move to top</string>
     <string name="action_drag_label_move_bottom" description="Label for an accessibility action that moves an app to the bottom of the ordered app priority list [CHAR LIMIT=50]">Move to bottom</string>
+    <string name="reorder_button_content_description" description="Content description of a button for accessibility that is used to reorder selected app in the app priority list [CHAR LIMIT=50]">Button to reorder <xliff:g id="selected_app" example="Test app">%s</xliff:g> in the priority list, double tap and drag to reorder</string>
+    <string name="remove_button_content_description" description="Content description of a button for accessibility that is used to remove selected app from the app priority list [CHAR LIMIT=50]">Button to remove <xliff:g id="selected_app" example="Test app">%s</xliff:g> from the priority list</string>
+    <string name="reorder_button_action_description" description="Action description of a button for accessibility that is used to reorder selected app from the app priority list [CHAR LIMIT=50]">Double tap and drag to reorder</string>
     <!--  endregion-->
 
     <!--  region Search Indexable-->
diff --git a/apk/res/values/themes.xml b/apk/res/values/themes.xml
index 98ba2fd..b8c9b80 100644
--- a/apk/res/values/themes.xml
+++ b/apk/res/values/themes.xml
@@ -136,6 +136,10 @@
         <item name="listItemMinHeight">@dimen/list_item_min_height</item>
         <item name="recentAccessTimelineDividerHeight">@dimen/timeline_divider_height</item>
         <!-- END REGION -->
+
+        <!-- Popup Menu -->
+        <item name="android:itemBackground">@color/settingslib_dialog_background</item>
+        <!-- END REGION -->
     </style>
 
     <style name="Theme.HealthConnect" parent="Base.Theme.HealthConnect" />
diff --git a/apk/res/xml/data_sources_and_priority_screen.xml b/apk/res/xml/data_sources_and_priority_screen.xml
index ee70416..f3892e2 100644
--- a/apk/res/xml/data_sources_and_priority_screen.xml
+++ b/apk/res/xml/data_sources_and_priority_screen.xml
@@ -16,14 +16,18 @@
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
     <PreferenceCategory
+        android:key="data_type_spinner_group"
+        android:order="1"
+        app:isPreferenceVisible="false"/>
+    <PreferenceCategory
         android:key="data_totals_group"
         android:title="@string/data_totals_header"
-        android:order="1"
+        android:order="2"
         app:isPreferenceVisible="false"/>
     <PreferenceCategory
         android:key="app_sources_group"
         android:title="@string/app_sources_header"
-        android:order="2"
+        android:order="3"
         app:isPreferenceVisible="false"/>
     <com.android.settingslib.widget.FooterPreference
         android:key="data_sources_footer"
diff --git a/apk/res/xml/health_permission_types_screen.xml b/apk/res/xml/health_permission_types_screen.xml
index 57d848b..3dc8cfd 100644
--- a/apk/res/xml/health_permission_types_screen.xml
+++ b/apk/res/xml/health_permission_types_screen.xml
@@ -14,12 +14,14 @@
   limitations under the License.
 -->
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:key="permission_types_screen">
     <com.android.settingslib.widget.AppHeaderPreference
         android:key="permission_types_header"
         android:order="0" />
     <PreferenceCategory
         android:key="app_filters_preference"
+        app:isPreferenceVisible="false"
         android:order="1" />
     <PreferenceCategory
         android:key="permission_types"
diff --git a/apk/src/com/android/healthconnect/controller/autodelete/AutoDeleteConfirmationDialogFragment.kt b/apk/src/com/android/healthconnect/controller/autodelete/AutoDeleteConfirmationDialogFragment.kt
index 4684225..bd04e03 100644
--- a/apk/src/com/android/healthconnect/controller/autodelete/AutoDeleteConfirmationDialogFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/autodelete/AutoDeleteConfirmationDialogFragment.kt
@@ -60,7 +60,7 @@
                             AUTO_DELETE_SAVED_EVENT,
                             bundleOf(AUTO_DELETE_SAVED_EVENT to viewModel.newAutoDeleteRange.value))
                     }
-                .setNegativeButton(
+                .setNeutralButton(
                     android.R.string.cancel, AutoDeleteElement.AUTO_DELETE_DIALOG_CANCEL_BUTTON) {
                         _,
                         _ ->
diff --git a/apk/src/com/android/healthconnect/controller/data/entries/AllEntriesFragment.kt b/apk/src/com/android/healthconnect/controller/data/entries/AllEntriesFragment.kt
index d321274..a63a839 100644
--- a/apk/src/com/android/healthconnect/controller/data/entries/AllEntriesFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/data/entries/AllEntriesFragment.kt
@@ -197,6 +197,7 @@
                 is With -> {
                     entriesRecyclerView.isVisible = true
                     adapter.updateData(state.entries)
+                    entriesRecyclerView.scrollToPosition(0)
                     errorView.isVisible = false
                     noDataView.isVisible = false
                     loadingView.isVisible = false
diff --git a/apk/src/com/android/healthconnect/controller/data/entries/api/LoadEntriesHelper.kt b/apk/src/com/android/healthconnect/controller/data/entries/api/LoadEntriesHelper.kt
index 241f350..b4d25c0 100644
--- a/apk/src/com/android/healthconnect/controller/data/entries/api/LoadEntriesHelper.kt
+++ b/apk/src/com/android/healthconnect/controller/data/entries/api/LoadEntriesHelper.kt
@@ -71,9 +71,13 @@
     suspend fun readDataType(
         data: Class<out Record>,
         timeFilterRange: TimeInstantRangeFilter,
-        packageName: String?
+        packageName: String?,
+        ascending: Boolean = true,
+        pageSize: Int = 1000
     ): List<Record> {
-        val filter = buildReadRecordsRequestUsingFilters(data, timeFilterRange, packageName)
+        val filter =
+            buildReadRecordsRequestUsingFilters(
+                data, timeFilterRange, packageName, ascending, pageSize)
         val records =
             suspendCancellableCoroutine<ReadRecordsResponse<*>> { continuation ->
                     healthConnectManager.readRecords(
@@ -95,6 +99,20 @@
             .flatten()
     }
 
+    /** Returns a list containing the most recent record from the specified input. */
+    suspend fun readLastRecord(input: LoadDataEntriesInput): List<Record> {
+        val timeFilterRange =
+            getTimeFilter(input.displayedStartTime, input.period, endTimeExclusive = true)
+        val dataTypes = HealthPermissionToDatatypeMapper.getDataTypes(input.permissionType)
+
+        return dataTypes
+            .map { dataType ->
+                readDataType(
+                    dataType, timeFilterRange, input.packageName, ascending = false, pageSize = 1)
+            }
+            .flatten()
+    }
+
     /**
      * If more than one day's data is displayed, inserts a section header for each day: 'Today',
      * 'Yesterday', then date format.
@@ -210,10 +228,15 @@
     fun buildReadRecordsRequestUsingFilters(
         data: Class<out Record>,
         timeFilterRange: TimeInstantRangeFilter,
-        packageName: String?
+        packageName: String?,
+        ascending: Boolean = true,
+        pageSize: Int = 1000
     ): ReadRecordsRequestUsingFilters<out Record> {
         val filter =
-            ReadRecordsRequestUsingFilters.Builder(data).setTimeRangeFilter(timeFilterRange)
+            ReadRecordsRequestUsingFilters.Builder(data)
+                .setAscending(ascending)
+                .setPageSize(pageSize)
+                .setTimeRangeFilter(timeFilterRange)
         if (packageName != null) {
             filter.addDataOrigins(DataOrigin.Builder().setPackageName(packageName).build()).build()
         }
diff --git a/apk/src/com/android/healthconnect/controller/dataentries/DataEntriesFragment.kt b/apk/src/com/android/healthconnect/controller/dataentries/DataEntriesFragment.kt
index 8575b51..375d790 100644
--- a/apk/src/com/android/healthconnect/controller/dataentries/DataEntriesFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/dataentries/DataEntriesFragment.kt
@@ -237,6 +237,7 @@
                 is WithData -> {
                     entriesRecyclerView.isVisible = true
                     adapter.updateData(state.entries)
+                    entriesRecyclerView.scrollToPosition(0)
                     errorView.isVisible = false
                     noDataView.isVisible = false
                     loadingView.isVisible = false
diff --git a/apk/src/com/android/healthconnect/controller/datasources/DataSourcesFragment.kt b/apk/src/com/android/healthconnect/controller/datasources/DataSourcesFragment.kt
index 31ce77a..4c6f2b5 100644
--- a/apk/src/com/android/healthconnect/controller/datasources/DataSourcesFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/datasources/DataSourcesFragment.kt
@@ -57,6 +57,7 @@
     Hilt_DataSourcesFragment(), AppSourcesAdapter.OnAppRemovedFromPriorityListListener {
 
     companion object {
+        private const val DATA_TYPE_SPINNER_PREFERENCE_GROUP = "data_type_spinner_group"
         private const val DATA_TOTALS_PREFERENCE_GROUP = "data_totals_group"
         private const val DATA_TOTALS_PREFERENCE_KEY = "data_totals_preference"
         private const val APP_SOURCES_PREFERENCE_GROUP = "app_sources_group"
@@ -65,6 +66,7 @@
         private const val NON_EMPTY_FOOTER_PREFERENCE_KEY = "data_sources_footer"
         private const val EMPTY_STATE_HEADER_PREFERENCE_KEY = "empty_state_header"
         private const val EMPTY_STATE_FOOTER_PREFERENCE_KEY = "empty_state_footer"
+        private const val IS_EDIT_MODE = "is_edit_mode"
 
         private val dataSourcesCategories =
             arrayListOf(HealthDataCategory.ACTIVITY, HealthDataCategory.SLEEP)
@@ -76,6 +78,7 @@
 
     @Inject lateinit var logger: HealthConnectLogger
     @Inject lateinit var appUtils: AppUtils
+    private var isEditMode = false
 
     private val dataSourcesViewModel: DataSourcesViewModel by activityViewModels()
     private lateinit var spinnerPreference: SettingsSpinnerPreference
@@ -83,6 +86,10 @@
     private var currentCategorySelection: @HealthDataCategoryInt Int = HealthDataCategory.ACTIVITY
     @Inject lateinit var timeSource: TimeSource
 
+    private val dataTypeSpinnerPreferenceGroup: PreferenceGroup? by lazy {
+        preferenceScreen.findPreference(DATA_TYPE_SPINNER_PREFERENCE_GROUP)
+    }
+
     private val dataTotalsPreferenceGroup: PreferenceGroup? by lazy {
         preferenceScreen.findPreference(DATA_TOTALS_PREFERENCE_GROUP)
     }
@@ -133,8 +140,19 @@
         setupSpinnerPreference()
     }
 
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        outState.putBoolean(IS_EDIT_MODE, isEditMode)
+    }
+
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+
+        savedInstanceState?.let { bundle ->
+            val savedIsEditMode = bundle.getBoolean(IS_EDIT_MODE, false)
+            isEditMode = savedIsEditMode
+        }
+
         setLoading(true)
         val currentStringSelection = spinnerPreference.selectedItem
         currentCategorySelection =
@@ -163,7 +181,7 @@
                 if (priorityList.isEmpty() && potentialAppSources.isEmpty()) {
                     addEmptyState()
                 } else {
-                    updateMenu(priorityList.size > 1 && !dataSourcesViewModel.isEditMode)
+                    updateMenu(priorityList.size > 1 && !isEditMode)
                     updateAppSourcesSection(priorityList, potentialAppSources)
                     updateDataTotalsSection(cardInfos)
                 }
@@ -200,7 +218,7 @@
     }
 
     private fun editPriorityList() {
-        dataSourcesViewModel.isEditMode = true
+        isEditMode = true
         updateMenu(shouldShowEditButton = false)
         appSourcesPreferenceGroup?.removePreferenceRecursively(ADD_AN_APP_PREFERENCE_KEY)
         val appSourcesPreference =
@@ -214,7 +232,7 @@
             ?.toggleEditMode(false)
         updateMenu(dataSourcesViewModel.getEditedPriorityList().size > 1)
         updateAddApp(dataSourcesViewModel.getEditedPotentialAppSources().isNotEmpty())
-        dataSourcesViewModel.isEditMode = false
+        isEditMode = false
     }
 
     /** Updates the priority list preference. */
@@ -236,10 +254,10 @@
                     this)
                 .also {
                     it.key = APP_SOURCES_PREFERENCE_KEY
-                    it.setEditMode(dataSourcesViewModel.isEditMode)
+                    it.setEditMode(isEditMode)
                 })
 
-        updateAddApp(potentialAppSources.isNotEmpty() && !dataSourcesViewModel.isEditMode)
+        updateAddApp(potentialAppSources.isNotEmpty() && !isEditMode)
         nonEmptyFooterPreference?.isVisible = true
     }
 
@@ -390,7 +408,8 @@
         spinnerPreference.setSelection(
             dataSourcesCategories.indexOf(dataSourcesViewModel.getCurrentSelection()))
 
-        preferenceScreen.addPreference(spinnerPreference)
+        dataTypeSpinnerPreferenceGroup?.isVisible = true
+        dataTypeSpinnerPreferenceGroup?.addPreference(spinnerPreference)
         logger.logImpression(DataSourcesElement.DATA_TYPE_SPINNER)
     }
 }
diff --git a/apk/src/com/android/healthconnect/controller/datasources/DataSourcesViewModel.kt b/apk/src/com/android/healthconnect/controller/datasources/DataSourcesViewModel.kt
index 0d28ea5..32f1984 100644
--- a/apk/src/com/android/healthconnect/controller/datasources/DataSourcesViewModel.kt
+++ b/apk/src/com/android/healthconnect/controller/datasources/DataSourcesViewModel.kt
@@ -75,8 +75,6 @@
     val dataSourcesInfo: LiveData<DataSourcesInfo>
         get() = _dataSourcesInfo
 
-    var isEditMode = false
-
     init {
         _dataSourcesAndAggregationsInfo.addSource(_currentPriorityList) { priorityListState ->
             if (!priorityListState.shouldObserve) {
diff --git a/apk/src/com/android/healthconnect/controller/datasources/api/LoadLastDateWithPriorityDataUseCase.kt b/apk/src/com/android/healthconnect/controller/datasources/api/LoadLastDateWithPriorityDataUseCase.kt
index 64232cb..1db7039 100644
--- a/apk/src/com/android/healthconnect/controller/datasources/api/LoadLastDateWithPriorityDataUseCase.kt
+++ b/apk/src/com/android/healthconnect/controller/datasources/api/LoadLastDateWithPriorityDataUseCase.kt
@@ -108,9 +108,16 @@
                 date.isAfter(today.minusMonths(1)) && !date.isAfter(today)
             }
 
-        if (recentDates.isEmpty()) return null
-
-        val minDate = recentDates.min()
+        // Activity dates are not kept during B&R, so it's possible to have data
+        // even without activity dates.
+        val minDate: LocalDate =
+            if (recentDates.isEmpty()) {
+                // Either there are no dates, or this is a fresh device out of D2D
+                // Check if there is any data in the past month anyway
+                today.minusMonths(1)
+            } else {
+                recentDates.min()
+            }
 
         // Query the data entries from this last month in one single API call
         val input =
@@ -121,7 +128,7 @@
                 period = DateNavigationPeriod.PERIOD_MONTH,
                 showDataOrigin = false)
 
-        val entryRecords = loadEntriesHelper.readRecords(input)
+        val entryRecords = loadEntriesHelper.readLastRecord(input)
 
         if (entryRecords.isNotEmpty()) {
             // The records are returned in descending order by startTime
diff --git a/apk/src/com/android/healthconnect/controller/datasources/appsources/AppSourcesAdapter.kt b/apk/src/com/android/healthconnect/controller/datasources/appsources/AppSourcesAdapter.kt
index 4b9b6bb..d5154de 100644
--- a/apk/src/com/android/healthconnect/controller/datasources/appsources/AppSourcesAdapter.kt
+++ b/apk/src/com/android/healthconnect/controller/datasources/appsources/AppSourcesAdapter.kt
@@ -19,6 +19,7 @@
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.ImageView
 import android.widget.TextView
 import androidx.recyclerview.widget.ItemTouchHelper
@@ -155,6 +156,21 @@
             val positionString: String = NumberFormat.getIntegerInstance().format(appPosition + 1)
             appPositionView.text = positionString
             appNameView.text = appMetadata.appName
+            actionIconBackground.contentDescription =
+                context.getString(R.string.reorder_button_content_description, appNameView.text)
+            actionIconBackground.accessibilityDelegate =
+                object : View.AccessibilityDelegate() {
+                    override fun onInitializeAccessibilityNodeInfo(
+                        host: View,
+                        info: AccessibilityNodeInfo
+                    ) {
+                        super.onInitializeAccessibilityNodeInfo(host, info)
+                        info.addAction(
+                            AccessibilityNodeInfo.AccessibilityAction(
+                                DataSourcesElement.REORDER_APP_SOURCE_BUTTON.impressionId,
+                                context.getString(R.string.reorder_button_action_description)))
+                    }
+                }
 
             if (appUtils.isDefaultApp(context, appMetadata.packageName)) {
                 appSourceSummary.visibility = View.VISIBLE
@@ -177,6 +193,8 @@
             actionView.visibility = View.VISIBLE
             actionIconBackground.background =
                 AttributeResolver.getDrawable(itemView.context, R.attr.closeIcon)
+            actionIconBackground.contentDescription =
+                context.getString(R.string.remove_button_content_description, appNameView.text)
             actionView.setOnTouchListener(null)
             actionView.setOnClickListener {
                 logger.logInteraction(DataSourcesElement.REMOVE_APP_SOURCE_BUTTON)
diff --git a/apk/src/com/android/healthconnect/controller/deletion/DeletionAppDataConfirmationDialogFragment.kt b/apk/src/com/android/healthconnect/controller/deletion/DeletionAppDataConfirmationDialogFragment.kt
index 4c3fc8e..b50fa87 100644
--- a/apk/src/com/android/healthconnect/controller/deletion/DeletionAppDataConfirmationDialogFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/deletion/DeletionAppDataConfirmationDialogFragment.kt
@@ -19,6 +19,7 @@
 import android.os.Bundle
 import android.view.View
 import android.widget.CheckBox
+import android.widget.ImageView
 import android.widget.TextView
 import androidx.fragment.app.DialogFragment
 import androidx.fragment.app.activityViewModels
@@ -26,6 +27,7 @@
 import com.android.healthconnect.controller.R
 import com.android.healthconnect.controller.deletion.DeletionConstants.CONFIRMATION_EVENT
 import com.android.healthconnect.controller.shared.dialog.AlertDialogBuilder
+import com.android.healthconnect.controller.utils.AttributeResolver
 import com.android.healthconnect.controller.utils.logging.DeletionDialogConfirmationElement
 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
 import dagger.hilt.android.AndroidEntryPoint
@@ -42,10 +44,18 @@
         var isInactiveApp = viewModel.isInactiveApp
 
         val view: View = layoutInflater.inflate(R.layout.dialog_message_with_checkbox, null)
+        val iconView = view.findViewById(R.id.dialog_icon) as ImageView
+        val title = view.findViewById(R.id.dialog_title) as TextView
         val message = view.findViewById(R.id.dialog_message) as TextView
         val checkBox = view.findViewById(R.id.dialog_checkbox) as CheckBox
 
         val appName = viewModel.deletionParameters.value?.getAppName()
+        val iconDrawable = AttributeResolver.getNullableDrawable(view.context, R.attr.deleteIcon)
+        iconDrawable?.let {
+            iconView.setImageDrawable(it)
+            iconView.visibility = View.VISIBLE
+        }
+        title.text = getString(R.string.confirming_question_app_data_all, appName)
         message.text = getString(R.string.confirming_question_message)
         if (isInactiveApp) {
             checkBox.visibility = View.GONE
@@ -64,9 +74,7 @@
             AlertDialogBuilder(this)
                 .setLogName(
                     DeletionDialogConfirmationElement.DELETION_DIALOG_CONFIRMATION_CONTAINER)
-                .setCustomTitle(getString(R.string.confirming_question_app_data_all, appName))
                 .setView(view)
-                .setCustomIcon(R.attr.deleteIcon)
                 .setPositiveButton(
                     R.string.confirming_question_delete_button,
                     DeletionDialogConfirmationElement.DELETION_DIALOG_CONFIRMATION_DELETE_BUTTON) {
@@ -76,7 +84,7 @@
                         viewModel.setRemovePermissions(checkBox.isChecked)
                         setFragmentResult(CONFIRMATION_EVENT, Bundle())
                     }
-                .setNegativeButton(
+                .setNeutralButton(
                     android.R.string.cancel,
                     DeletionDialogConfirmationElement.DELETION_DIALOG_CONFIRMATION_CANCEL_BUTTON) {
                         _,
diff --git a/apk/src/com/android/healthconnect/controller/deletion/DeletionConfirmationDialogFragment.kt b/apk/src/com/android/healthconnect/controller/deletion/DeletionConfirmationDialogFragment.kt
index 537fc23..36fb974 100644
--- a/apk/src/com/android/healthconnect/controller/deletion/DeletionConfirmationDialogFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/deletion/DeletionConfirmationDialogFragment.kt
@@ -62,7 +62,7 @@
                     }
 
         if (viewModel.showTimeRangeDialogFragment) {
-            alertDialogBuilder.setNegativeButton(
+            alertDialogBuilder.setNeutralButton(
                 R.string.confirming_question_go_back_button,
                 DeletionDialogConfirmationElement.DELETION_DIALOG_CONFIRMATION_GO_BACK_BUTTON) {
                     _,
@@ -70,7 +70,7 @@
                     setFragmentResult(GO_BACK_EVENT, Bundle())
                 }
         } else {
-            alertDialogBuilder.setNegativeButton(
+            alertDialogBuilder.setNeutralButton(
                 android.R.string.cancel,
                 DeletionDialogConfirmationElement.DELETION_DIALOG_CONFIRMATION_CANCEL_BUTTON) { _, _
                     ->
diff --git a/apk/src/com/android/healthconnect/controller/deletion/FailedDialogFragment.kt b/apk/src/com/android/healthconnect/controller/deletion/FailedDialogFragment.kt
index 4ddbccb..63f9bc0 100644
--- a/apk/src/com/android/healthconnect/controller/deletion/FailedDialogFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/deletion/FailedDialogFragment.kt
@@ -41,7 +41,7 @@
                 FailedDialogElement.DELETION_DIALOG_ERROR_TRY_AGAIN_BUTTON) { _, _ ->
                     setFragmentResult(TRY_AGAIN_EVENT, Bundle())
                 }
-            .setNegativeButton(
+            .setNeutralButton(
                 R.string.delete_dialog_failure_close_button,
                 FailedDialogElement.DELETION_DIALOG_ERROR_CLOSE_BUTTON)
             .create()
diff --git a/apk/src/com/android/healthconnect/controller/deletion/TimeRangeDialogFragment.kt b/apk/src/com/android/healthconnect/controller/deletion/TimeRangeDialogFragment.kt
index 1dff4cd..344b19a 100644
--- a/apk/src/com/android/healthconnect/controller/deletion/TimeRangeDialogFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/deletion/TimeRangeDialogFragment.kt
@@ -18,6 +18,7 @@
 import android.app.Dialog
 import android.os.Bundle
 import android.view.View
+import android.widget.ImageView
 import android.widget.RadioGroup
 import android.widget.TextView
 import androidx.core.view.children
@@ -27,6 +28,7 @@
 import com.android.healthconnect.controller.R
 import com.android.healthconnect.controller.deletion.DeletionConstants.TIME_RANGE_SELECTION_EVENT
 import com.android.healthconnect.controller.shared.dialog.AlertDialogBuilder
+import com.android.healthconnect.controller.utils.AttributeResolver
 import com.android.healthconnect.controller.utils.logging.DeletionDialogTimeRangeElement
 import com.android.healthconnect.controller.utils.logging.ErrorPageElement
 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
@@ -46,14 +48,21 @@
         val view: View = layoutInflater.inflate(R.layout.dialog_message_time_range_picker, null)
         val radioGroup: RadioGroup = view.findViewById(R.id.radio_group_time_range)
         val messageView: TextView = view.findViewById(R.id.time_range_message)
+        val iconView = view.findViewById(R.id.dialog_icon) as ImageView
+        val title = view.findViewById(R.id.dialog_title) as TextView
         messageView.text = buildMessage()
+        val iconDrawable =
+            AttributeResolver.getNullableDrawable(view.context, R.attr.deletionSettingsIcon)
+        iconDrawable?.let {
+            iconView.setImageDrawable(it)
+            iconView.visibility = View.VISIBLE
+        }
+        title.text = getString(R.string.time_range_title)
 
         return AlertDialogBuilder(this)
             .setLogName(DeletionDialogTimeRangeElement.DELETION_DIALOG_TIME_RANGE_CONTAINER)
-            .setCustomTitle(R.string.time_range_title)
-            .setCustomIcon(R.attr.deletionSettingsIcon)
             .setView(view)
-            .setNegativeButton(
+            .setNeutralButton(
                 android.R.string.cancel,
                 DeletionDialogTimeRangeElement.DELETION_DIALOG_TIME_RANGE_CANCEL_BUTTON) { _, _ ->
                 }
diff --git a/apk/src/com/android/healthconnect/controller/migration/MigrationActivity.kt b/apk/src/com/android/healthconnect/controller/migration/MigrationActivity.kt
index 0cd6d05..49564c4 100644
--- a/apk/src/com/android/healthconnect/controller/migration/MigrationActivity.kt
+++ b/apk/src/com/android/healthconnect/controller/migration/MigrationActivity.kt
@@ -71,18 +71,16 @@
                     return true
                 }
             } else if (migrationState == MigrationState.ALLOWED_PAUSED ||
-                    migrationState == MigrationState.ALLOWED_NOT_STARTED) {
+                migrationState == MigrationState.ALLOWED_NOT_STARTED) {
                 val allowedPausedSeen =
-                        sharedPreference.getBoolean(INTEGRATION_PAUSED_SEEN_KEY, false)
+                    sharedPreference.getBoolean(INTEGRATION_PAUSED_SEEN_KEY, false)
 
                 if (!allowedPausedSeen) {
                     activity.startActivity(createMigrationActivityIntent(activity))
                     activity.finish()
                     return true
                 }
-            }
-
-            else if (migrationState == MigrationState.IN_PROGRESS) {
+            } else if (migrationState == MigrationState.IN_PROGRESS) {
                 activity.startActivity(createMigrationActivityIntent(activity))
                 activity.finish()
                 return true
@@ -102,7 +100,7 @@
                 .setTitle(R.string.migration_pending_permissions_dialog_title)
                 .setMessage(message)
                 .setCancelable(false)
-                .setNegativeButton(
+                .setNeutralButton(
                     R.string.migration_pending_permissions_dialog_button_start_integration,
                     MigrationElement.MIGRATION_PENDING_DIALOG_CANCEL_BUTTON,
                     negativeButtonAction)
@@ -132,30 +130,35 @@
                 .show()
         }
 
-        fun maybeShowWhatsNewDialog(context: Context,
-                                    negativeButtonAction: DialogInterface.OnClickListener? = null) {
+        fun maybeShowWhatsNewDialog(
+            context: Context,
+            negativeButtonAction: DialogInterface.OnClickListener? = null
+        ) {
             val sharedPreference =
-                    context.getSharedPreferences("USER_ACTIVITY_TRACKER", Context.MODE_PRIVATE)
+                context.getSharedPreferences("USER_ACTIVITY_TRACKER", Context.MODE_PRIVATE)
             val dialogSeen =
-                    sharedPreference.getBoolean(context.getString(R.string.whats_new_dialog_seen), false)
+                sharedPreference.getBoolean(
+                    context.getString(R.string.whats_new_dialog_seen), false)
 
             if (!dialogSeen) {
                 AlertDialogBuilder(context)
-                        .setLogName(MigrationElement.MIGRATION_DONE_DIALOG_CONTAINER)
-                        .setTitle(R.string.migration_whats_new_dialog_title)
-                        .setMessage(R.string.migration_whats_new_dialog_content)
-                        .setCancelable(false)
-                        .setNegativeButton(
-                                R.string.migration_whats_new_dialog_button, MigrationElement.MIGRATION_DONE_DIALOG_BUTTON) {
-                            unusedDialogInterface, unusedInt ->
+                    .setLogName(MigrationElement.MIGRATION_DONE_DIALOG_CONTAINER)
+                    .setTitle(R.string.migration_whats_new_dialog_title)
+                    .setMessage(R.string.migration_whats_new_dialog_content)
+                    .setCancelable(false)
+                    .setNegativeButton(
+                        R.string.migration_whats_new_dialog_button,
+                        MigrationElement.MIGRATION_DONE_DIALOG_BUTTON) {
+                            unusedDialogInterface,
+                            unusedInt ->
                             sharedPreference.edit().apply {
                                 putBoolean(context.getString(R.string.whats_new_dialog_seen), true)
                                 apply()
                             }
                             negativeButtonAction?.onClick(unusedDialogInterface, unusedInt)
                         }
-                        .create()
-                        .show()
+                    .create()
+                    .show()
             }
         }
 
diff --git a/apk/src/com/android/healthconnect/controller/permissions/connectedapps/ConnectedAppsFragment.kt b/apk/src/com/android/healthconnect/controller/permissions/connectedapps/ConnectedAppsFragment.kt
index 5e1ea57..eb88c6d 100644
--- a/apk/src/com/android/healthconnect/controller/permissions/connectedapps/ConnectedAppsFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/permissions/connectedapps/ConnectedAppsFragment.kt
@@ -124,7 +124,7 @@
             .setIcon(R.attr.disconnectAllIcon)
             .setTitle(R.string.permissions_disconnect_all_dialog_title)
             .setMessage(R.string.permissions_disconnect_all_dialog_message)
-            .setNegativeButton(
+            .setNeutralButton(
                 android.R.string.cancel,
                 DisconnectAllAppsDialogElement.DISCONNECT_ALL_APPS_DIALOG_CANCEL_BUTTON) { _, _ ->
                     viewModel.setAlertDialogStatus(false)
diff --git a/apk/src/com/android/healthconnect/controller/permissions/shared/DisconnectDialogFragment.kt b/apk/src/com/android/healthconnect/controller/permissions/shared/DisconnectDialogFragment.kt
index aac324b..727b728 100644
--- a/apk/src/com/android/healthconnect/controller/permissions/shared/DisconnectDialogFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/permissions/shared/DisconnectDialogFragment.kt
@@ -15,7 +15,9 @@
 
 import android.app.Dialog
 import android.os.Bundle
+import android.view.View
 import android.widget.CheckBox
+import android.widget.ImageView
 import android.widget.TextView
 import androidx.core.os.bundleOf
 import androidx.core.view.isVisible
@@ -23,6 +25,7 @@
 import androidx.fragment.app.setFragmentResult
 import com.android.healthconnect.controller.R
 import com.android.healthconnect.controller.shared.dialog.AlertDialogBuilder
+import com.android.healthconnect.controller.utils.AttributeResolver
 import com.android.healthconnect.controller.utils.logging.DisconnectAppDialogElement
 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
 import dagger.hilt.android.AndroidEntryPoint
@@ -61,6 +64,16 @@
         body.findViewById<TextView>(R.id.dialog_message).apply {
             text = getString(R.string.permissions_disconnect_dialog_message, appName)
         }
+        body.findViewById<TextView>(R.id.dialog_title).apply {
+            text = getString(R.string.permissions_disconnect_dialog_title)
+        }
+        val iconView = body.findViewById(R.id.dialog_icon) as ImageView
+        val iconDrawable =
+            AttributeResolver.getNullableDrawable(body.context, R.attr.disconnectIcon)
+        iconDrawable?.let {
+            iconView.setImageDrawable(it)
+            iconView.visibility = View.VISIBLE
+        }
         val checkBox =
             body.findViewById<CheckBox>(R.id.dialog_checkbox).apply {
                 text = getString(R.string.permissions_disconnect_dialog_checkbox, appName)
@@ -73,10 +86,8 @@
         val dialog =
             AlertDialogBuilder(this)
                 .setLogName(DisconnectAppDialogElement.DISCONNECT_APP_DIALOG_CONTAINER)
-                .setCustomIcon(R.attr.disconnectIcon)
-                .setCustomTitle(R.string.permissions_disconnect_dialog_title)
                 .setView(body)
-                .setNegativeButton(
+                .setNeutralButton(
                     android.R.string.cancel,
                     DisconnectAppDialogElement.DISCONNECT_APP_DIALOG_CANCEL_BUTTON) { _, _ ->
                         setFragmentResult(DISCONNECT_CANCELED_EVENT, bundleOf())
diff --git a/apk/src/com/android/healthconnect/controller/permissiontypes/HealthPermissionTypesFragment.kt b/apk/src/com/android/healthconnect/controller/permissiontypes/HealthPermissionTypesFragment.kt
index c20c551..81f0326 100644
--- a/apk/src/com/android/healthconnect/controller/permissiontypes/HealthPermissionTypesFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/permissiontypes/HealthPermissionTypesFragment.kt
@@ -161,6 +161,9 @@
                 is HealthPermissionTypesViewModel.AppsWithDataFragmentState.WithData -> {
                     if (state.appsWithData.size > 1) {
                         addAppFilters(state.appsWithData)
+                        mAppFiltersPreference?.isVisible = true
+                    } else {
+                        mAppFiltersPreference?.isVisible = false
                     }
                 }
             }
diff --git a/apk/src/com/android/healthconnect/controller/permissiontypes/prioritylist/PriorityListDialogFragment.kt b/apk/src/com/android/healthconnect/controller/permissiontypes/prioritylist/PriorityListDialogFragment.kt
index 72bf1d9..5c758f1 100644
--- a/apk/src/com/android/healthconnect/controller/permissiontypes/prioritylist/PriorityListDialogFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/permissiontypes/prioritylist/PriorityListDialogFragment.kt
@@ -60,7 +60,7 @@
         return AlertDialogBuilder(this)
             .setLogName(PermissionTypesElement.SET_APP_PRIORITY_DIALOG_CONTAINER)
             .setView(parentView)
-            .setNegativeButton(
+            .setNeutralButton(
                 android.R.string.cancel,
                 PermissionTypesElement.SET_APP_PRIORITY_DIALOG_CANCEL_BUTTON)
             .setPositiveButton(
diff --git a/apk/src/com/android/healthconnect/controller/recentaccess/RecentAccessFragment.kt b/apk/src/com/android/healthconnect/controller/recentaccess/RecentAccessFragment.kt
index 5520c62..6d0ad5a 100644
--- a/apk/src/com/android/healthconnect/controller/recentaccess/RecentAccessFragment.kt
+++ b/apk/src/com/android/healthconnect/controller/recentaccess/RecentAccessFragment.kt
@@ -169,13 +169,18 @@
                         if (!recentApp.isInactive) {
                             // Do not set click listeners for inactive apps
                             it.setOnPreferenceClickListener {
-                                findNavController()
-                                    .navigate(
-                                        R.id.action_recentAccessFragment_to_connectedAppFragment,
-                                        bundleOf(
-                                            Intent.EXTRA_PACKAGE_NAME to
-                                                recentApp.metadata.packageName,
-                                            Constants.EXTRA_APP_NAME to recentApp.metadata.appName))
+                                if (findNavController().currentDestination?.id ==
+                                    R.id.recentAccessFragment) {
+                                    findNavController()
+                                        .navigate(
+                                            R.id
+                                                .action_recentAccessFragment_to_connectedAppFragment,
+                                            bundleOf(
+                                                Intent.EXTRA_PACKAGE_NAME to
+                                                    recentApp.metadata.packageName,
+                                                Constants.EXTRA_APP_NAME to
+                                                    recentApp.metadata.appName))
+                                }
                                 true
                             }
                         }
diff --git a/apk/src/com/android/healthconnect/controller/recentaccess/RecentAccessPreference.kt b/apk/src/com/android/healthconnect/controller/recentaccess/RecentAccessPreference.kt
index b12ad21..a25e92e 100644
--- a/apk/src/com/android/healthconnect/controller/recentaccess/RecentAccessPreference.kt
+++ b/apk/src/com/android/healthconnect/controller/recentaccess/RecentAccessPreference.kt
@@ -95,6 +95,8 @@
 
         accessTime = holder.findViewById(R.id.time) as TextView
         accessTime.text = formattedTime
+        accessTime.contentDescription =
+            context.getString(R.string.recent_access_time_content_descritption, formattedTime)
     }
 
     override fun isSameItem(preference: Preference): Boolean {
diff --git a/apk/src/com/android/healthconnect/controller/shared/dialog/AlertDialogBuilder.kt b/apk/src/com/android/healthconnect/controller/shared/dialog/AlertDialogBuilder.kt
index 60fa617..f7a299b 100644
--- a/apk/src/com/android/healthconnect/controller/shared/dialog/AlertDialogBuilder.kt
+++ b/apk/src/com/android/healthconnect/controller/shared/dialog/AlertDialogBuilder.kt
@@ -199,6 +199,30 @@
                 onClickListener?.onClick(dialog, which)
             }
 
+        alertDialogBuilder.setNegativeButton(textId, loggingClickListener)
+        return this
+    }
+
+    /**
+     * To ensure a clear and accessible layout for all users, this button replaces a traditional
+     * negative button with a neutral button and used as a negative button when a positive button is
+     * also present. This prevents button borders from overlapping, when display and font sizes are
+     * set to their largest in accessibility settings.
+     */
+    fun setNeutralButton(
+        @StringRes textId: Int,
+        buttonId: ElementName,
+        onClickListener: DialogInterface.OnClickListener? = null
+    ): AlertDialogBuilder {
+        hasNegativeButton = true
+        negativeButtonKey = buttonId
+
+        val loggingClickListener =
+            DialogInterface.OnClickListener { dialog, which ->
+                logger.logInteraction(negativeButtonKey)
+                onClickListener?.onClick(dialog, which)
+            }
+
         alertDialogBuilder.setNeutralButton(textId, loggingClickListener)
         return this
     }
diff --git a/apk/src/com/android/healthconnect/controller/shared/preference/AggregationDataCard.kt b/apk/src/com/android/healthconnect/controller/shared/preference/AggregationDataCard.kt
index 26cb510..3781069 100644
--- a/apk/src/com/android/healthconnect/controller/shared/preference/AggregationDataCard.kt
+++ b/apk/src/com/android/healthconnect/controller/shared/preference/AggregationDataCard.kt
@@ -54,6 +54,7 @@
 
         cardIcon.background = fromHealthPermissionType(cardInfo.healthPermissionType).icon(context)
         cardTitle.text = cardInfo.aggregation.aggregation
+        cardTitle.contentDescription = cardInfo.aggregation.aggregationA11y
 
         val totalStartDate = cardInfo.startDate
         val totalEndDate = cardInfo.endDate
@@ -99,13 +100,29 @@
                 ConstraintSet.BOTTOM,
                 ConstraintSet.PARENT_ID,
                 ConstraintSet.BOTTOM)
+            constraintSet.connect(
+                R.id.card_title_number, ConstraintSet.END, R.id.card_date, ConstraintSet.START)
 
             constraintSet.connect(
+                R.id.card_date, ConstraintSet.START, R.id.card_title_number, ConstraintSet.END)
+            constraintSet.connect(
                 R.id.card_date, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END)
             constraintSet.connect(
                 R.id.card_date, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
             constraintSet.connect(
                 R.id.card_date, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
+
+            constraintSet.createHorizontalChain(
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.LEFT,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.RIGHT,
+                intArrayOf(R.id.card_title_number, R.id.card_date),
+                null,
+                ConstraintSet.CHAIN_SPREAD_INSIDE)
+
+            constraintSet.constrainedWidth(R.id.card_title_number, true)
+            constraintSet.constrainedWidth(R.id.card_date, true)
         }
 
         constraintSet.applyTo(titleAndDateContainer)
diff --git a/apk/tests/Android.bp b/apk/tests/Android.bp
index ba4d2f7..d42a49d 100644
--- a/apk/tests/Android.bp
+++ b/apk/tests/Android.bp
@@ -82,6 +82,7 @@
         "androidx.test.ext.junit",
         "androidx.test.ext.truth",
         "androidx.test.rules",
+        "compatibility-device-util-axt",
         "mockito-kotlin2"
     ],
     resource_dirs: ["main_res"],
diff --git a/apk/tests/src/com/android/healthconnect/controller/tests/data/entries/api/LoadEntriesHelperUseCaseTest.kt b/apk/tests/src/com/android/healthconnect/controller/tests/data/entries/api/LoadEntriesHelperUseCaseTest.kt
index 8ced864..f40339e 100644
--- a/apk/tests/src/com/android/healthconnect/controller/tests/data/entries/api/LoadEntriesHelperUseCaseTest.kt
+++ b/apk/tests/src/com/android/healthconnect/controller/tests/data/entries/api/LoadEntriesHelperUseCaseTest.kt
@@ -18,7 +18,11 @@
 import android.health.connect.ReadRecordsRequestUsingFilters
 import android.health.connect.ReadRecordsResponse
 import android.health.connect.TimeInstantRangeFilter
+import android.health.connect.datatypes.DistanceRecord
 import android.health.connect.datatypes.SleepSessionRecord
+import android.health.connect.datatypes.StepsRecord
+import android.health.connect.datatypes.TotalCaloriesBurnedRecord
+import android.health.connect.datatypes.units.Length
 import android.os.OutcomeReceiver
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.healthconnect.controller.data.entries.api.LoadDataEntriesInput
@@ -26,6 +30,7 @@
 import com.android.healthconnect.controller.data.entries.datenavigation.DateNavigationPeriod
 import com.android.healthconnect.controller.dataentries.formatters.shared.HealthDataEntryFormatter
 import com.android.healthconnect.controller.permissions.data.HealthPermissionType
+import com.android.healthconnect.controller.tests.utils.getDistanceRecord
 import com.android.healthconnect.controller.tests.utils.getMetaData
 import com.android.healthconnect.controller.tests.utils.setLocale
 import com.android.healthconnect.controller.tests.utils.verifySleepSessionListsEqual
@@ -65,6 +70,8 @@
 
     @Captor
     lateinit var requestCaptor: ArgumentCaptor<ReadRecordsRequestUsingFilters<SleepSessionRecord>>
+    @Captor
+    lateinit var stepsRequestCaptor: ArgumentCaptor<ReadRecordsRequestUsingFilters<StepsRecord>>
 
     @Before
     fun setup() {
@@ -74,14 +81,13 @@
         hiltRule.inject()
         loadEntriesHelper =
             LoadEntriesHelper(context, healthDataEntryFormatter, healthConnectManager)
+        TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC")))
     }
 
     // TODO (b/309288325) add tests for other permission types
 
     @Test
     fun loadSleepData_withinDay_returnsListOfRecords_sortedByDescendingStartTime() = runTest {
-        TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC")))
-
         val startTime = Instant.parse("2023-06-12T22:30:00Z").atStartOfDay()
         val input =
             LoadDataEntriesInput(
@@ -135,8 +141,6 @@
     @Test
     fun loadSleepDataUseCase_withinWeek_returnsListOfRecords_sortedByDescendingStartTime() =
         runTest {
-            TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC")))
-
             val startTime = Instant.parse("2023-06-12T22:30:00Z").atStartOfDay()
             val input =
                 LoadDataEntriesInput(
@@ -200,8 +204,6 @@
     @Test
     fun loadSleepDataUseCase_withinMonth_returnsListOfRecords_sortedByDescendingStartTime() =
         runTest {
-            TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC")))
-
             val startTime = Instant.parse("2023-06-12T22:30:00Z").atStartOfDay()
             val input =
                 LoadDataEntriesInput(
@@ -267,6 +269,106 @@
             verifySleepSessionListsEqual(actual, expected)
         }
 
+    @Test
+    fun readLastRecord_forDistance_returnsListOfOneRecord() = runTest {
+        val startTime = Instant.parse("2023-06-12T22:30:00Z")
+        val input =
+            LoadDataEntriesInput(
+                displayedStartTime = startTime.atStartOfDay(),
+                packageName = null,
+                period = DateNavigationPeriod.PERIOD_MONTH,
+                showDataOrigin = true,
+                permissionType = HealthPermissionType.DISTANCE)
+
+        val timeRangeFilter =
+            loadEntriesHelper.getTimeFilter(
+                startTime.atStartOfDay(), DateNavigationPeriod.PERIOD_MONTH, true)
+
+        Mockito.doAnswer(prepareDistanceAnswer())
+            .`when`(healthConnectManager)
+            .readRecords(
+                ArgumentMatchers.any(ReadRecordsRequestUsingFilters::class.java),
+                ArgumentMatchers.any(),
+                ArgumentMatchers.any())
+
+        val actual = loadEntriesHelper.readLastRecord(input)
+        val expected = listOf(getDistanceRecord(Length.fromMeters(1000.0), time = startTime))
+        Mockito.verify(healthConnectManager, Mockito.times(1))
+            .readRecords(
+                stepsRequestCaptor.capture(), ArgumentMatchers.any(), ArgumentMatchers.any())
+        assertThat((stepsRequestCaptor.value.timeRangeFilter as TimeInstantRangeFilter).startTime)
+            .isEqualTo(timeRangeFilter.startTime)
+        assertThat((stepsRequestCaptor.value.timeRangeFilter as TimeInstantRangeFilter).endTime)
+            .isEqualTo(timeRangeFilter.endTime)
+        assertThat((stepsRequestCaptor.value.timeRangeFilter as TimeInstantRangeFilter).isBounded)
+            .isEqualTo(timeRangeFilter.isBounded)
+        assertThat(actual.size).isEqualTo(expected.size)
+        assertThat((actual[0] as DistanceRecord).distance).isEqualTo(expected[0].distance)
+        assertThat((actual[0] as DistanceRecord).startTime).isEqualTo(startTime)
+        assertThat((actual[0] as DistanceRecord).endTime).isEqualTo(expected[0].endTime)
+        assertThat(stepsRequestCaptor.value.isAscending).isFalse()
+        assertThat(stepsRequestCaptor.value.pageSize).isEqualTo(1)
+    }
+
+    @Test
+    fun readLastRecord_forTotalCaloriesBurned_whenNoData_returnsEmptyList() = runTest {
+        val startTime = Instant.parse("2023-06-12T22:30:00Z").atStartOfDay()
+        val input =
+            LoadDataEntriesInput(
+                displayedStartTime = startTime,
+                packageName = null,
+                period = DateNavigationPeriod.PERIOD_MONTH,
+                showDataOrigin = true,
+                permissionType = HealthPermissionType.TOTAL_CALORIES_BURNED)
+
+        val timeRangeFilter =
+            loadEntriesHelper.getTimeFilter(startTime, DateNavigationPeriod.PERIOD_MONTH, true)
+
+        Mockito.doAnswer(prepareEmptyCaloriesAnswer())
+            .`when`(healthConnectManager)
+            .readRecords(
+                ArgumentMatchers.any(ReadRecordsRequestUsingFilters::class.java),
+                ArgumentMatchers.any(),
+                ArgumentMatchers.any())
+
+        val actual = loadEntriesHelper.readLastRecord(input)
+
+        Mockito.verify(healthConnectManager, Mockito.times(1))
+            .readRecords(
+                stepsRequestCaptor.capture(), ArgumentMatchers.any(), ArgumentMatchers.any())
+        assertThat((stepsRequestCaptor.value.timeRangeFilter as TimeInstantRangeFilter).startTime)
+            .isEqualTo(timeRangeFilter.startTime)
+        assertThat((stepsRequestCaptor.value.timeRangeFilter as TimeInstantRangeFilter).endTime)
+            .isEqualTo(timeRangeFilter.endTime)
+        assertThat((stepsRequestCaptor.value.timeRangeFilter as TimeInstantRangeFilter).isBounded)
+            .isEqualTo(timeRangeFilter.isBounded)
+        assertThat(actual.size).isEqualTo(0)
+        assertThat(stepsRequestCaptor.value.isAscending).isFalse()
+        assertThat(stepsRequestCaptor.value.pageSize).isEqualTo(1)
+    }
+
+    private fun prepareDistanceAnswer(): (InvocationOnMock) -> ReadRecordsResponse<DistanceRecord> {
+        val answer = { args: InvocationOnMock ->
+            val receiver =
+                args.arguments[2] as OutcomeReceiver<ReadRecordsResponse<DistanceRecord>, *>
+            receiver.onResult(getMonthDistanceRecords())
+            getMonthDistanceRecords()
+        }
+        return answer
+    }
+
+    private fun prepareEmptyCaloriesAnswer():
+        (InvocationOnMock) -> ReadRecordsResponse<TotalCaloriesBurnedRecord> {
+        val answer = { args: InvocationOnMock ->
+            val receiver =
+                args.arguments[2]
+                    as OutcomeReceiver<ReadRecordsResponse<TotalCaloriesBurnedRecord>, *>
+            receiver.onResult(getEmptyCaloriesRecords())
+            getEmptyCaloriesRecords()
+        }
+        return answer
+    }
+
     private fun prepareDaySleepAnswer():
         (InvocationOnMock) -> ReadRecordsResponse<SleepSessionRecord> {
         val answer = { args: InvocationOnMock ->
@@ -387,4 +489,16 @@
                     .build()),
             -1)
     }
+
+    private fun getMonthDistanceRecords(): ReadRecordsResponse<DistanceRecord> {
+        return ReadRecordsResponse<DistanceRecord>(
+            listOf(
+                getDistanceRecord(
+                    Length.fromMeters(1000.0), Instant.parse("2023-06-12T22:30:00Z"))),
+            -1)
+    }
+
+    private fun getEmptyCaloriesRecords(): ReadRecordsResponse<TotalCaloriesBurnedRecord> {
+        return ReadRecordsResponse<TotalCaloriesBurnedRecord>(listOf(), -1)
+    }
 }
diff --git a/apk/tests/src/com/android/healthconnect/controller/tests/datasources/api/LoadLastDateWithPriorityDataUseCaseTest.kt b/apk/tests/src/com/android/healthconnect/controller/tests/datasources/api/LoadLastDateWithPriorityDataUseCaseTest.kt
index 5395158..b2a8dd3 100644
--- a/apk/tests/src/com/android/healthconnect/controller/tests/datasources/api/LoadLastDateWithPriorityDataUseCaseTest.kt
+++ b/apk/tests/src/com/android/healthconnect/controller/tests/datasources/api/LoadLastDateWithPriorityDataUseCaseTest.kt
@@ -105,15 +105,40 @@
     }
 
     @Test
-    fun onePriorityApp_noActivityDates_returnsNull() = runTest {
+    fun onePriorityApp_noActivityDates_noData_returnsNull() = runTest {
+        val now = Instant.parse("2023-10-14T12:00:00Z")
+        timeSource.setNow(now)
         loadPriorityListUseCase.updatePriorityList(listOf(TEST_APP))
 
         mockQueryActivityDatesAnswer(listOf())
+        mockReadRecordsResult(
+            packageName = TEST_APP_PACKAGE_NAME,
+            healthPermissionType = HealthPermissionType.STEPS,
+            queryDate = timeSource.currentLocalDateTime().toLocalDate().minusMonths(1),
+            numRecords = 0)
 
         val result = loadLastDateWithPriorityDataUseCase.invoke(HealthPermissionType.STEPS)
         assertThat(result is UseCaseResults.Success).isTrue()
         assertThat((result as UseCaseResults.Success).data).isNull()
-        Mockito.verify(healthConnectManager, times(0)).readRecords<StepsRecord>(any(), any(), any())
+    }
+
+    @Test
+    fun onePriorityApp_noActivityDates_dataPresent_returnsMostRecentDateWithPriority() = runTest {
+        val now = Instant.parse("2023-10-14T12:00:00Z")
+        timeSource.setNow(now)
+        loadPriorityListUseCase.updatePriorityList(listOf(TEST_APP))
+        val queryDate = timeSource.currentLocalDateTime().toLocalDate().minusMonths(1)
+
+        mockQueryActivityDatesAnswer(listOf())
+        mockReadRecordsResult(
+            packageName = TEST_APP_PACKAGE_NAME,
+            healthPermissionType = HealthPermissionType.STEPS,
+            queryDate = queryDate,
+            numRecords = 1)
+
+        val result = loadLastDateWithPriorityDataUseCase.invoke(HealthPermissionType.STEPS)
+        assertThat(result is UseCaseResults.Success).isTrue()
+        assertThat((result as UseCaseResults.Success).data).isEqualTo(queryDate)
     }
 
     @Test
@@ -148,6 +173,11 @@
             healthPermissionType = HealthPermissionType.STEPS,
             queryDate = dateWithData,
             numRecords = 2)
+        mockReadRecordsResult(
+            packageName = TEST_APP_PACKAGE_NAME,
+            healthPermissionType = HealthPermissionType.STEPS,
+            queryDate = timeSource.currentLocalDateTime().toLocalDate().minusMonths(1),
+            numRecords = 0)
 
         mockQueryActivityDatesAnswer(listOf(dateWithData))
 
diff --git a/apk/tests/src/com/android/healthconnect/controller/tests/utils/LocalDateTimeFormatterTest.kt b/apk/tests/src/com/android/healthconnect/controller/tests/utils/LocalDateTimeFormatterTest.kt
index bcb66f3..f28af15 100644
--- a/apk/tests/src/com/android/healthconnect/controller/tests/utils/LocalDateTimeFormatterTest.kt
+++ b/apk/tests/src/com/android/healthconnect/controller/tests/utils/LocalDateTimeFormatterTest.kt
@@ -1,15 +1,22 @@
 package com.android.healthconnect.controller.tests.utils
 
 import android.content.Context
-import androidx.test.platform.app.InstrumentationRegistry.*
+import android.content.res.Configuration
+import android.os.LocaleList
+import android.provider.Settings.System
+import com.android.compatibility.common.util.UserSettings
+import com.android.compatibility.common.util.UserSettings.Namespace
 import com.android.healthconnect.controller.utils.LocalDateTimeFormatter
 import com.google.common.truth.Truth.assertThat
+import dagger.hilt.android.qualifiers.ApplicationContext
 import dagger.hilt.android.testing.HiltAndroidRule
 import dagger.hilt.android.testing.HiltAndroidTest
 import java.time.Instant
 import java.time.temporal.ChronoUnit
 import java.util.Locale
 import java.util.TimeZone
+import javax.inject.Inject
+import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -19,10 +26,10 @@
 
     @get:Rule val hiltRule = HiltAndroidRule(this)
 
-    private lateinit var formatter: LocalDateTimeFormatter
+    @Inject @ApplicationContext lateinit var context: Context
+    private lateinit var systemSettings: UserSettings
 
-    private lateinit var context: Context
-
+    private var previousTimeFormat: String? = null
     private var previousDefaultTimeZone: TimeZone? = null
     private var previousLocale: Locale? = null
 
@@ -32,134 +39,147 @@
     fun setup() {
         hiltRule.inject()
 
-        context = getInstrumentation().context
-        previousDefaultTimeZone = TimeZone.getDefault()
-        previousLocale = context.resources.configuration.locale
+        systemSettings = UserSettings(context, Namespace.SYSTEM)
+        previousTimeFormat = systemSettings.get(System.TIME_12_24)
+        if (previousTimeFormat != null) {
+            // Clear setting so locale-defined time format is used.
+            systemSettings.syncSet(System.TIME_12_24, null)
+        }
 
-        // set default local
-        context.setLocale(Locale.UK)
+        previousDefaultTimeZone = TimeZone.getDefault()
+        previousLocale = Locale.getDefault()
 
         // set time zone
         TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
-        formatter = LocalDateTimeFormatter(context)
     }
 
+    @After
     fun tearDown() {
+        if (previousTimeFormat != null) {
+            systemSettings.syncSet(System.TIME_12_24, previousTimeFormat)
+        }
         TimeZone.setDefault(previousDefaultTimeZone)
-        previousLocale?.let { locale -> context.setLocale(locale) }
+        previousLocale?.let { locale -> Locale.setDefault(locale) }
     }
 
     @Test
     fun formatTime_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         assertThat(formatter.formatTime(time)).isEqualTo("14:06")
     }
 
     @Test
     fun formatTime_usLocale() {
-        context.setLocale(Locale.US)
+        val formatter = setLocaleAndCreateFormatter(Locale.US)
         assertThat(formatter.formatTime(time)).isEqualTo("2:06 PM")
     }
 
     @Test
     fun formatLongDate_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         assertThat(formatter.formatLongDate(time)).isEqualTo("20 October 2022")
     }
 
     @Test
     fun formatLongDate_usLocale() {
-        context.setLocale(Locale.US)
+        val formatter = setLocaleAndCreateFormatter(Locale.US)
         assertThat(formatter.formatLongDate(time)).isEqualTo("October 20, 2022")
     }
 
     @Test
     fun formatShortDate_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         assertThat(formatter.formatShortDate(time)).isEqualTo("20 October")
     }
 
     @Test
     fun formatShortDate_usLocale() {
-        context.setLocale(Locale.US)
+        val formatter = setLocaleAndCreateFormatter(Locale.US)
         assertThat(formatter.formatShortDate(time)).isEqualTo("October 20")
     }
 
     @Test
     fun formatTimeRange_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         val end = time.plus(1, ChronoUnit.HOURS)
         assertThat(formatter.formatTimeRange(time, end)).isEqualTo("14:06 - 15:06")
     }
 
     @Test
     fun formatTimeRange_usLocale() {
-        context.setLocale(Locale.US)
+        val formatter = setLocaleAndCreateFormatter(Locale.US)
         val end = time.plus(1, ChronoUnit.HOURS)
         assertThat(formatter.formatTimeRange(time, end)).isEqualTo("2:06 PM - 3:06 PM")
     }
 
     @Test
     fun formatTimeRangeA11y_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         val end = time.plus(1, ChronoUnit.HOURS)
         assertThat(formatter.formatTimeRangeA11y(time, end)).isEqualTo("from 14:06 to 15:06")
     }
 
     @Test
     fun formatTimeRangeA11y_usLocale() {
-        context.setLocale(Locale.US)
+        val formatter = setLocaleAndCreateFormatter(Locale.US)
         val end = time.plus(1, ChronoUnit.HOURS)
         assertThat(formatter.formatTimeRangeA11y(time, end)).isEqualTo("from 2:06 PM to 3:06 PM")
     }
 
     @Test
     fun formatDateRangeWithYear_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         val end = time.plus(10, ChronoUnit.DAYS)
         assertThat(formatter.formatDateRangeWithYear(time, end)).isEqualTo("20–30 Oct 2022")
     }
 
     @Test
     fun formatDateRangeWithoutYear_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         val end = time.plus(10, ChronoUnit.DAYS)
         assertThat(formatter.formatDateRangeWithoutYear(time, end)).isEqualTo("20–30 Oct")
     }
 
     @Test
     fun formatMonthWithYear_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         assertThat(formatter.formatMonthWithYear(time)).isEqualTo("October 2022")
     }
 
     @Test
     fun formatMonthWithoutYear_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         assertThat(formatter.formatMonthWithoutYear(time)).isEqualTo("October")
     }
 
     @Test
     fun formatWeekdayDateWithYear_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         assertThat(formatter.formatWeekdayDateWithYear(time)).isEqualTo("Thu, 20 Oct 2022")
     }
 
     @Test
     fun formatWeekdayDateWithYear_usLocale() {
-        context.setLocale(Locale.US)
+        val formatter = setLocaleAndCreateFormatter(Locale.US)
         assertThat(formatter.formatWeekdayDateWithYear(time)).isEqualTo("Thu, Oct 20, 2022")
     }
 
     @Test
     fun formatWeekdayDateWithoutYear_ukLocale() {
-        context.setLocale(Locale.UK)
+        val formatter = setLocaleAndCreateFormatter(Locale.UK)
         assertThat(formatter.formatWeekdayDateWithoutYear(time)).isEqualTo("Thu, 20 Oct")
     }
 
     @Test
     fun formatWeekdayDateWithoutYear_usLocale() {
-        context.setLocale(Locale.US)
+        val formatter = setLocaleAndCreateFormatter(Locale.US)
         assertThat(formatter.formatWeekdayDateWithoutYear(time)).isEqualTo("Thu, Oct 20")
     }
+
+    private fun setLocaleAndCreateFormatter(locale: Locale): LocalDateTimeFormatter {
+        Locale.setDefault(locale)
+        val configuration = Configuration()
+        configuration.setLocales(LocaleList(locale))
+        return LocalDateTimeFormatter(context.createConfigurationContext(configuration))
+    }
 }
diff --git a/backuprestore/Android.bp b/backuprestore/Android.bp
index f45d7fa..1c83e7f 100644
--- a/backuprestore/Android.bp
+++ b/backuprestore/Android.bp
@@ -39,6 +39,16 @@
         "framework-healthfitness.impl",
     ],
     apex_available: ["com.android.healthfitness"],
+    errorprone: {
+        extra_check_modules: [
+           "//external/nullaway:nullaway_plugin"
+        ],
+        javacflags: [
+            "-Xep:NullAway:ERROR",
+            "-XepOpt:NullAway:AnnotatedPackages=com.android.health.connect.backuprestore",
+            "-XepOpt:NullAway:KnownInitializers=android.app.backup.BackupAgent.onCreate",
+        ],
+    },
 }
 
 android_app {
diff --git a/backuprestore/AndroidManifest.xml b/backuprestore/AndroidManifest.xml
index 749d412..ba5f75c 100644
--- a/backuprestore/AndroidManifest.xml
+++ b/backuprestore/AndroidManifest.xml
@@ -21,6 +21,7 @@
         android:killAfterRestore="false"
         android:allowBackup="true"
         android:fullBackupOnly="true"
-        android:backupAgent=".HealthConnectBackupAgent" >
+        android:backupAgent=".HealthConnectBackupAgent"
+        android:restoreAnyVersion="true" >
     </application>
 </manifest>
diff --git a/framework/Android.bp b/framework/Android.bp
index 131f9c3..e57e245 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -70,5 +70,15 @@
     impl_library_visibility: [
         "//packages/modules/HealthFitness:__subpackages__"
     ],
-    static_libs: ["healthfitness-statsd"]
+    static_libs: ["healthfitness-statsd"],
+    errorprone: {
+        extra_check_modules: [
+           "//external/nullaway:nullaway_plugin"
+        ],
+        javacflags: [
+            "-XepExcludedPaths:.*/out/soong/.*",
+            "-Xep:NullAway:ERROR",
+            "-XepOpt:NullAway:AnnotatedPackages=android.health.connect",
+        ],
+    },
 }
diff --git a/framework/java/android/health/connect/AggregateRecordsResponse.java b/framework/java/android/health/connect/AggregateRecordsResponse.java
index d457949..885d6f9 100644
--- a/framework/java/android/health/connect/AggregateRecordsResponse.java
+++ b/framework/java/android/health/connect/AggregateRecordsResponse.java
@@ -53,6 +53,7 @@
     }
 
     /** @hide */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public static <U> ZoneOffset getZoneOffsetInternal(
             @NonNull AggregationType<U> aggregationType,
             Map<AggregationType<U>, AggregateResult<U>> mAggregateResults) {
@@ -83,6 +84,7 @@
     }
 
     /** @hide */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public static <U> U getInternal(
             @NonNull AggregationType<U> aggregationType,
             Map<AggregationType<U>, AggregateResult<U>> mAggregateResults) {
diff --git a/framework/java/android/health/connect/AggregateResult.java b/framework/java/android/health/connect/AggregateResult.java
index f49a6b6..cf9ac1b 100644
--- a/framework/java/android/health/connect/AggregateResult.java
+++ b/framework/java/android/health/connect/AggregateResult.java
@@ -36,6 +36,7 @@
     private ZoneOffset mZoneOffset;
     private Set<DataOrigin> mDataOrigins;
 
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public AggregateResult(T result) {
         mResult = result;
     }
diff --git a/framework/java/android/health/connect/DeleteUsingFiltersRequest.java b/framework/java/android/health/connect/DeleteUsingFiltersRequest.java
index d80dd62..9a311a3 100644
--- a/framework/java/android/health/connect/DeleteUsingFiltersRequest.java
+++ b/framework/java/android/health/connect/DeleteUsingFiltersRequest.java
@@ -41,6 +41,7 @@
     /**
      * @see Builder
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private DeleteUsingFiltersRequest(
             @Nullable TimeRangeFilter timeRangeFilter,
             @NonNull Set<Class<? extends Record>> recordTypes,
@@ -79,6 +80,8 @@
     public static final class Builder {
         private final Set<DataOrigin> mDataOrigins = new ArraySet<>();
         private final Set<Class<? extends Record>> mRecordTypes = new ArraySet<>();
+
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         private TimeRangeFilter mTimeRangeFilter;
 
         /**
@@ -114,6 +117,7 @@
          * @param timeRangeFilter Time range b/w which the delete operation is to be performed
          * @return Same {@link Builder} with the timeRangeFilter field set
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setTimeRangeFilter(@Nullable TimeRangeFilter timeRangeFilter) {
             mTimeRangeFilter = timeRangeFilter;
diff --git a/framework/java/android/health/connect/HealthConnectManager.java b/framework/java/android/health/connect/HealthConnectManager.java
index fcd4e3b..da236d2 100644
--- a/framework/java/android/health/connect/HealthConnectManager.java
+++ b/framework/java/android/health/connect/HealthConnectManager.java
@@ -317,7 +317,10 @@
 
     private static final String TAG = "HealthConnectManager";
     private static final String HEALTH_PERMISSION_PREFIX = "android.permission.health.";
+
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile Set<String> sHealthPermissions;
+
     private final Context mContext;
     private final IHealthConnectService mService;
     private final InternalExternalRecordConverter mInternalExternalRecordConverter;
@@ -356,6 +359,7 @@
      *
      * @hide
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @RequiresPermission(MANAGE_HEALTH_PERMISSIONS)
     @UserHandleAware
     public void revokeHealthPermission(
@@ -375,6 +379,7 @@
      *
      * @hide
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @RequiresPermission(MANAGE_HEALTH_PERMISSIONS)
     @UserHandleAware
     public void revokeAllHealthPermissions(@NonNull String packageName, @Nullable String reason) {
diff --git a/framework/java/android/health/connect/HealthPermissions.java b/framework/java/android/health/connect/HealthPermissions.java
index 81195ab..1b1f42e 100644
--- a/framework/java/android/health/connect/HealthPermissions.java
+++ b/framework/java/android/health/connect/HealthPermissions.java
@@ -783,6 +783,7 @@
     }
 
     /** @hide */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public static String getHealthReadPermission(
             @HealthPermissionCategory.Type int permissionCategory) {
         if (sHealthCategoryToReadPermissionMap.isEmpty()) {
diff --git a/framework/java/android/health/connect/HealthServicesInitializer.java b/framework/java/android/health/connect/HealthServicesInitializer.java
index ba2a372..6525825 100644
--- a/framework/java/android/health/connect/HealthServicesInitializer.java
+++ b/framework/java/android/health/connect/HealthServicesInitializer.java
@@ -19,6 +19,7 @@
 import android.annotation.SystemApi;
 import android.app.SystemServiceRegistry;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.health.connect.aidl.IHealthConnectService;
 
 /**
@@ -42,9 +43,20 @@
                 Context.HEALTHCONNECT_SERVICE,
                 HealthConnectManager.class,
                 (context, serviceBinder) -> {
+                    if (!isHardwareSupported(context)) {
+                        return null;
+                    }
                     IHealthConnectService service =
                             IHealthConnectService.Stub.asInterface(serviceBinder);
                     return new HealthConnectManager(context, service);
                 });
     }
+
+    private static boolean isHardwareSupported(Context context) {
+        PackageManager pm = context.getPackageManager();
+        return (!pm.hasSystemFeature(PackageManager.FEATURE_EMBEDDED)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+    }
 }
diff --git a/framework/java/android/health/connect/LocalTimeRangeFilter.java b/framework/java/android/health/connect/LocalTimeRangeFilter.java
index 0a5df21..2f8871f 100644
--- a/framework/java/android/health/connect/LocalTimeRangeFilter.java
+++ b/framework/java/android/health/connect/LocalTimeRangeFilter.java
@@ -103,12 +103,16 @@
 
     /** Builder class for {@link LocalTimeRangeFilter} */
     public static final class Builder {
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         private LocalDateTime mLocalStartTime;
+
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         private LocalDateTime mLocalEndTime;
 
         /**
          * @param localStartTime represents local start time of this filter
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public LocalTimeRangeFilter.Builder setStartTime(@Nullable LocalDateTime localStartTime) {
             mLocalStartTime = localStartTime;
@@ -118,6 +122,7 @@
         /**
          * @param localEndTime represents local end time of this filter
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public LocalTimeRangeFilter.Builder setEndTime(@Nullable LocalDateTime localEndTime) {
             mLocalEndTime = localEndTime;
diff --git a/framework/java/android/health/connect/PageTokenWrapper.java b/framework/java/android/health/connect/PageTokenWrapper.java
new file mode 100644
index 0000000..744d46d
--- /dev/null
+++ b/framework/java/android/health/connect/PageTokenWrapper.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.health.connect;
+
+import static android.health.connect.Constants.DEFAULT_LONG;
+
+import static java.lang.Integer.min;
+
+import java.util.Objects;
+
+/**
+ * A wrapper object contains information encoded in the {@code long} page token.
+ *
+ * @hide
+ */
+public final class PageTokenWrapper {
+    /**
+     * This constant represents an empty token returned by the last read request, meaning no more
+     * pages are available.
+     *
+     * <p>We do not use this for read requests where page token is passed. The API design is
+     * asymmetry, when page token is passed in, it always contains {@code isAscending} information;
+     * the information is not available when it's returned.
+     */
+    public static final PageTokenWrapper EMPTY_PAGE_TOKEN = new PageTokenWrapper();
+
+    private static final long MAX_ALLOWED_TIME_MILLIS = (1L << 44) - 1;
+    private static final long MAX_ALLOWED_OFFSET = (1 << 18) - 1;
+    private static final int OFFSET_START_BIT = 45;
+    private static final int TIMESTAMP_START_BIT = 1;
+
+    private final boolean mIsAscending;
+    private final long mTimeMillis;
+    private final int mOffset;
+    private final boolean mIsTimestampSet;
+    private final boolean mIsEmpty;
+
+    /** isAscending stored in the page token. */
+    public boolean isAscending() {
+        return mIsAscending;
+    }
+
+    /** Timestamp stored in the page token. */
+    public long timeMillis() {
+        return mTimeMillis;
+    }
+
+    /** Offset stored in the page token. */
+    public int offset() {
+        return mOffset;
+    }
+
+    /** Whether or not the timestamp is set. */
+    public boolean isTimestampSet() {
+        return mIsTimestampSet;
+    }
+
+    /** Whether or not the page token contains meaningful values. */
+    public boolean isEmpty() {
+        return mIsEmpty;
+    }
+
+    /**
+     * Both {@code timeMillis} and {@code offset} have to be non-negative; {@code timeMillis} cannot
+     * exceed 2^44-1.
+     *
+     * <p>Note that due to space constraints, {@code offset} cannot exceed 2^18-1 (262143). If the
+     * {@code offset} parameter exceeds the maximum allowed value, it'll fallback to the max value.
+     *
+     * <p>More details see go/hc-page-token
+     */
+    public static PageTokenWrapper of(boolean isAscending, long timeMillis, int offset) {
+        checkArgument(timeMillis >= 0, "timestamp can not be negative");
+        checkArgument(timeMillis <= MAX_ALLOWED_TIME_MILLIS, "timestamp too large");
+        checkArgument(offset >= 0, "offset can not be negative");
+        int boundedOffset = min((int) MAX_ALLOWED_OFFSET, offset);
+        return new PageTokenWrapper(isAscending, timeMillis, boundedOffset);
+    }
+
+    /**
+     * Generate a page token that contains only {@code isAscending} information. Timestamp and
+     * offset are not set.
+     */
+    public static PageTokenWrapper ofAscending(boolean isAscending) {
+        return new PageTokenWrapper(isAscending);
+    }
+
+    /**
+     * Construct a {@link PageTokenWrapper} from {@code pageToken} and {@code defaultIsAscending}.
+     *
+     * <p>When {@code pageToken} is not set, in which case we can not get {@code isAscending} from
+     * the token, it falls back to {@code defaultIsAscending}.
+     *
+     * <p>{@code pageToken} must be a non-negative long number (except for using the sentinel value
+     * {@code DEFAULT_LONG}, whose current value is {@code -1}, which represents page token not set)
+     */
+    public static PageTokenWrapper from(long pageToken, boolean defaultIsAscending) {
+        if (pageToken == DEFAULT_LONG) {
+            return PageTokenWrapper.ofAscending(defaultIsAscending);
+        }
+        checkArgument(pageToken >= 0, "pageToken cannot be negative");
+        return PageTokenWrapper.of(
+                getIsAscending(pageToken), getTimestamp(pageToken), getOffset(pageToken));
+    }
+
+    /**
+     * Take the least significant bit in the given {@code pageToken} to retrieve isAscending
+     * information.
+     *
+     * <p>If the last bit of the token is 1, isAscending is false; otherwise isAscending is true.
+     */
+    private static boolean getIsAscending(long pageToken) {
+        return (pageToken & 1) == 0;
+    }
+
+    /** Shifts bits in the given {@code pageToken} to retrieve timestamp information. */
+    private static long getTimestamp(long pageToken) {
+        long mask = MAX_ALLOWED_TIME_MILLIS << TIMESTAMP_START_BIT;
+        return (pageToken & mask) >> TIMESTAMP_START_BIT;
+    }
+
+    /** Shifts bits in the given {@code pageToken} to retrieve offset information. */
+    private static int getOffset(long pageToken) {
+        return (int) (pageToken >> OFFSET_START_BIT);
+    }
+
+    private static void checkArgument(boolean expression, String errorMsg) {
+        if (!expression) {
+            throw new IllegalArgumentException(errorMsg);
+        }
+    }
+
+    /**
+     * Encodes a {@link PageTokenWrapper} to a long value.
+     *
+     * <p>Page token is structured as following from right (least significant bit) to left (most
+     * significant bit):
+     * <li>Least significant bit: 0 = isAscending true, 1 = isAscending false
+     * <li>Next 44 bits: timestamp, represents epoch time millis
+     * <li>Next 18 bits: offset, represents number of records processed in the previous page
+     * <li>Sign bit: not used for encoding, page token is a signed long
+     */
+    public long encode() {
+        return mIsTimestampSet
+                ? ((long) mOffset << OFFSET_START_BIT)
+                        | (mTimeMillis << TIMESTAMP_START_BIT)
+                        | (mIsAscending ? 0 : 1)
+                : DEFAULT_LONG;
+    }
+
+    @Override
+    public String toString() {
+        if (mIsEmpty) {
+            return "PageTokenWrapper{}";
+        }
+        StringBuilder builder = new StringBuilder("PageTokenWrapper{");
+        builder.append("isAscending = ").append(mIsAscending);
+        if (mIsTimestampSet) {
+            builder.append(", timeMillis = ").append(mTimeMillis);
+            builder.append(", offset = ").append(mOffset);
+        }
+        return builder.append("}").toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof PageTokenWrapper that)) return false;
+        return mIsAscending == that.mIsAscending
+                && mTimeMillis == that.mTimeMillis
+                && mOffset == that.mOffset
+                && mIsTimestampSet == that.mIsTimestampSet
+                && mIsEmpty == that.mIsEmpty;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIsAscending, mOffset, mTimeMillis, mIsTimestampSet, mIsEmpty);
+    }
+
+    private PageTokenWrapper(boolean isAscending, long timeMillis, int offset) {
+        this.mIsAscending = isAscending;
+        this.mTimeMillis = timeMillis;
+        this.mOffset = offset;
+        this.mIsTimestampSet = true;
+        this.mIsEmpty = false;
+    }
+
+    private PageTokenWrapper(boolean isAscending) {
+        this.mIsAscending = isAscending;
+        this.mTimeMillis = 0;
+        this.mOffset = 0;
+        this.mIsTimestampSet = false;
+        this.mIsEmpty = false;
+    }
+
+    private PageTokenWrapper() {
+        this.mIsAscending = true;
+        this.mTimeMillis = 0;
+        this.mOffset = 0;
+        this.mIsTimestampSet = false;
+        this.mIsEmpty = true;
+    }
+}
diff --git a/framework/java/android/health/connect/ReadRecordsRequestUsingFilters.java b/framework/java/android/health/connect/ReadRecordsRequestUsingFilters.java
index f3329d2..0fa107a 100644
--- a/framework/java/android/health/connect/ReadRecordsRequestUsingFilters.java
+++ b/framework/java/android/health/connect/ReadRecordsRequestUsingFilters.java
@@ -61,11 +61,7 @@
         mTimeRangeFilter = timeRangeFilter;
         mDataOrigins = dataOrigins;
         mPageSize = pageSize;
-        if (pageToken != DEFAULT_LONG) {
-            mAscending = pageToken % 2 == 0;
-        } else {
-            mAscending = ascending;
-        }
+        mAscending = PageTokenWrapper.from(pageToken, ascending).isAscending();
         mPageToken = pageToken;
     }
 
@@ -122,6 +118,7 @@
         /**
          * @param recordType Class object of {@link Record} type that needs to be read
          */
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         public Builder(@NonNull Class<T> recordType) {
             Objects.requireNonNull(recordType);
 
@@ -150,6 +147,7 @@
          *     <p>If not time range filter is present all the records will be read without any time
          *     constraints.
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder<T> setTimeRangeFilter(@Nullable TimeRangeFilter timeRangeFilter) {
             mTimeRangeFilter = timeRangeFilter;
diff --git a/framework/java/android/health/connect/RecordIdFilter.java b/framework/java/android/health/connect/RecordIdFilter.java
index dd8442a..376585e 100644
--- a/framework/java/android/health/connect/RecordIdFilter.java
+++ b/framework/java/android/health/connect/RecordIdFilter.java
@@ -35,6 +35,7 @@
      * @param clientRecordId Client identifier that was set while inserting the record.
      * @return Object of {@link RecordIdFilter}
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public static RecordIdFilter fromClientRecordId(
             @NonNull Class<? extends Record> recordType, @NonNull String clientRecordId) {
@@ -51,6 +52,7 @@
      *     HealthConnectManager#insertRecords}
      * @return Object of {@link RecordIdFilter}
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public static RecordIdFilter fromId(
             @NonNull Class<? extends Record> recordType, @NonNull String id) {
diff --git a/framework/java/android/health/connect/TimeInstantRangeFilter.java b/framework/java/android/health/connect/TimeInstantRangeFilter.java
index fa34f0f..c19b2d5 100644
--- a/framework/java/android/health/connect/TimeInstantRangeFilter.java
+++ b/framework/java/android/health/connect/TimeInstantRangeFilter.java
@@ -98,12 +98,16 @@
 
     /** Builder class for {@link TimeInstantRangeFilter} */
     public static final class Builder {
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         private Instant mStartTime;
+
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         private Instant mEndTime;
 
         /**
          * @param startTime represents start time of this filter
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setStartTime(@Nullable Instant startTime) {
             mStartTime = startTime;
@@ -113,6 +117,7 @@
         /**
          * @param endTime end time of this filter
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setEndTime(@Nullable Instant endTime) {
             mEndTime = endTime;
diff --git a/framework/java/android/health/connect/TimeRangeFilterHelper.java b/framework/java/android/health/connect/TimeRangeFilterHelper.java
index 1709546..f571ee4 100644
--- a/framework/java/android/health/connect/TimeRangeFilterHelper.java
+++ b/framework/java/android/health/connect/TimeRangeFilterHelper.java
@@ -40,6 +40,7 @@
      * @return start time epoch milliseconds for Instant time filter and epoch milliseconds using
      *     UTC zoneOffset for LocalTime filter
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public static long getFilterStartTimeMillis(@NonNull TimeRangeFilter timeRangeFilter) {
         if (isLocalTimeFilter(timeRangeFilter)) {
             return getMillisOfLocalTime(((LocalTimeRangeFilter) timeRangeFilter).getStartTime());
@@ -56,6 +57,7 @@
      * @return end time epoch milliseconds for Instant time filter and epoch milliseconds using UTC
      *     zoneOffset for LocalTime filter
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public static long getFilterEndTimeMillis(@NonNull TimeRangeFilter timeRangeFilter) {
         if (isLocalTimeFilter(timeRangeFilter)) {
             return getMillisOfLocalTime(((LocalTimeRangeFilter) timeRangeFilter).getEndTime());
diff --git a/framework/java/android/health/connect/aidl/AggregateDataRequestParcel.java b/framework/java/android/health/connect/aidl/AggregateDataRequestParcel.java
index 133b840..1d8b214 100644
--- a/framework/java/android/health/connect/aidl/AggregateDataRequestParcel.java
+++ b/framework/java/android/health/connect/aidl/AggregateDataRequestParcel.java
@@ -63,6 +63,7 @@
 
     private final boolean mLocalTimeFilter;
 
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public AggregateDataRequestParcel(AggregateRecordsRequest<?> request) {
         mStartTime = TimeRangeFilterHelper.getFilterStartTimeMillis(request.getTimeRangeFilter());
         mEndTime = TimeRangeFilterHelper.getFilterEndTimeMillis(request.getTimeRangeFilter());
@@ -86,6 +87,7 @@
         mDuration = duration;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public AggregateDataRequestParcel(AggregateRecordsRequest request, Period period) {
         this(request);
         mDuration = null;
@@ -97,6 +99,7 @@
         }
     }
 
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     protected AggregateDataRequestParcel(Parcel in) {
         mStartTime = in.readLong();
         mEndTime = in.readLong();
diff --git a/framework/java/android/health/connect/aidl/AggregateDataResponseParcel.java b/framework/java/android/health/connect/aidl/AggregateDataResponseParcel.java
index 5439c40..c6bc30c 100644
--- a/framework/java/android/health/connect/aidl/AggregateDataResponseParcel.java
+++ b/framework/java/android/health/connect/aidl/AggregateDataResponseParcel.java
@@ -66,10 +66,12 @@
     private Period mPeriod;
     private TimeRangeFilter mTimeRangeFilter;
 
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public AggregateDataResponseParcel(List<AggregateRecordsResponse<?>> aggregateRecordsResponse) {
         mAggregateRecordsResponses = aggregateRecordsResponse;
     }
 
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     protected AggregateDataResponseParcel(Parcel in) {
         final int size = in.readInt();
         mAggregateRecordsResponses = new ArrayList<>(size);
@@ -129,6 +131,7 @@
         }
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public AggregateDataResponseParcel setDuration(
             @Nullable Duration duration, @Nullable TimeRangeFilter timeRangeFilter) {
         mDuration = duration;
@@ -137,6 +140,7 @@
         return this;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public AggregateDataResponseParcel setPeriod(
             @Nullable Period period, @Nullable TimeRangeFilter timeRangeFilter) {
         mPeriod = period;
@@ -156,6 +160,7 @@
      * @return responses from {@code mAggregateRecordsResponses} grouped as per the {@code
      *     mDuration}
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public List<AggregateRecordsGroupedByDurationResponse<?>>
             getAggregateDataResponseGroupedByDuration() {
         Objects.requireNonNull(mDuration);
@@ -228,6 +233,7 @@
     /**
      * @return responses from {@code mAggregateRecordsResponses} grouped as per the {@code mPeriod}
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public List<AggregateRecordsGroupedByPeriodResponse<?>>
             getAggregateDataResponseGroupedByPeriod() {
         Objects.requireNonNull(mPeriod);
@@ -314,6 +320,7 @@
         }
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private ZoneOffset parseZoneOffset(Parcel in) {
         int zoneOffsetInSecs = in.readInt();
         ZoneOffset zoneOffset = null;
@@ -324,6 +331,7 @@
         return zoneOffset;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private LocalDateTime getPeriodEndLocalDateTime(TimeRangeFilter timeRangeFilter) {
         if (timeRangeFilter instanceof TimeInstantRangeFilter) {
             return LocalDateTime.ofInstant(
diff --git a/framework/java/android/health/connect/aidl/DeleteUsingFiltersRequestParcel.java b/framework/java/android/health/connect/aidl/DeleteUsingFiltersRequestParcel.java
index cb6bba2..233f2f8 100644
--- a/framework/java/android/health/connect/aidl/DeleteUsingFiltersRequestParcel.java
+++ b/framework/java/android/health/connect/aidl/DeleteUsingFiltersRequestParcel.java
@@ -67,6 +67,7 @@
                         RecordIdFiltersParcel.class.getClassLoader(), RecordIdFiltersParcel.class);
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public DeleteUsingFiltersRequestParcel(DeleteUsingFiltersRequest request) {
         mPackageNameFilters =
                 request.getDataOrigins().stream()
diff --git a/framework/java/android/health/connect/aidl/ReadRecordsRequestParcel.java b/framework/java/android/health/connect/aidl/ReadRecordsRequestParcel.java
index 7dd5ad3..a3a881d 100644
--- a/framework/java/android/health/connect/aidl/ReadRecordsRequestParcel.java
+++ b/framework/java/android/health/connect/aidl/ReadRecordsRequestParcel.java
@@ -90,6 +90,7 @@
         mAscending = true;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public ReadRecordsRequestParcel(ReadRecordsRequestUsingFilters<?> request) {
         mPackageFilters =
                 request.getDataOrigins().stream()
diff --git a/framework/java/android/health/connect/aidl/RecordIdFiltersParcel.java b/framework/java/android/health/connect/aidl/RecordIdFiltersParcel.java
index 05dbee8..8c2ea97 100644
--- a/framework/java/android/health/connect/aidl/RecordIdFiltersParcel.java
+++ b/framework/java/android/health/connect/aidl/RecordIdFiltersParcel.java
@@ -46,6 +46,7 @@
         mRecordIdFilters = recordIdFilters;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private RecordIdFiltersParcel(Parcel in) {
         int size = in.readInt();
         mRecordIdFilters = new ArrayList<>(size);
diff --git a/framework/java/android/health/connect/aidl/RecordsParcel.java b/framework/java/android/health/connect/aidl/RecordsParcel.java
index 1b7b541..06c440a 100644
--- a/framework/java/android/health/connect/aidl/RecordsParcel.java
+++ b/framework/java/android/health/connect/aidl/RecordsParcel.java
@@ -54,6 +54,7 @@
     private long mRecordsChunkSize;
     private List<Long> mRecordsSize;
 
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public RecordsParcel(@NonNull List<RecordInternal<?>> recordInternals) {
         mRecordInternals = recordInternals;
     }
diff --git a/framework/java/android/health/connect/datatypes/ActiveCaloriesBurnedRecord.java b/framework/java/android/health/connect/datatypes/ActiveCaloriesBurnedRecord.java
index 04de44c..b1ec184 100644
--- a/framework/java/android/health/connect/datatypes/ActiveCaloriesBurnedRecord.java
+++ b/framework/java/android/health/connect/datatypes/ActiveCaloriesBurnedRecord.java
@@ -183,6 +183,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/AppInfo.java b/framework/java/android/health/connect/datatypes/AppInfo.java
index eeed6ea..722f063 100644
--- a/framework/java/android/health/connect/datatypes/AppInfo.java
+++ b/framework/java/android/health/connect/datatypes/AppInfo.java
@@ -48,6 +48,7 @@
          * @param name name/label of the application. Optional
          * @param icon icon of the application. Optional.
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         public Builder(@NonNull String packageName, @Nullable String name, @Nullable Bitmap icon) {
             Objects.requireNonNull(packageName);
             mPackageName = packageName;
@@ -64,6 +65,7 @@
         }
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private AppInfo(@NonNull String packageName, @Nullable String name, @Nullable Bitmap icon) {
         Objects.requireNonNull(packageName);
         mPackageName = packageName;
diff --git a/framework/java/android/health/connect/datatypes/BasalBodyTemperatureRecord.java b/framework/java/android/health/connect/datatypes/BasalBodyTemperatureRecord.java
index e28bf05..f4fd54e 100644
--- a/framework/java/android/health/connect/datatypes/BasalBodyTemperatureRecord.java
+++ b/framework/java/android/health/connect/datatypes/BasalBodyTemperatureRecord.java
@@ -90,6 +90,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/BasalMetabolicRateRecord.java b/framework/java/android/health/connect/datatypes/BasalMetabolicRateRecord.java
index a7d454c..34afe26 100644
--- a/framework/java/android/health/connect/datatypes/BasalMetabolicRateRecord.java
+++ b/framework/java/android/health/connect/datatypes/BasalMetabolicRateRecord.java
@@ -75,6 +75,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/BloodGlucoseRecord.java b/framework/java/android/health/connect/datatypes/BloodGlucoseRecord.java
index 5baef1c..5af0c3d 100644
--- a/framework/java/android/health/connect/datatypes/BloodGlucoseRecord.java
+++ b/framework/java/android/health/connect/datatypes/BloodGlucoseRecord.java
@@ -214,6 +214,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/BloodPressureRecord.java b/framework/java/android/health/connect/datatypes/BloodPressureRecord.java
index fc761d7..6c45717 100644
--- a/framework/java/android/health/connect/datatypes/BloodPressureRecord.java
+++ b/framework/java/android/health/connect/datatypes/BloodPressureRecord.java
@@ -280,6 +280,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/BodyFatRecord.java b/framework/java/android/health/connect/datatypes/BodyFatRecord.java
index f5dc023..81dc80a 100644
--- a/framework/java/android/health/connect/datatypes/BodyFatRecord.java
+++ b/framework/java/android/health/connect/datatypes/BodyFatRecord.java
@@ -70,6 +70,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/BodyTemperatureRecord.java b/framework/java/android/health/connect/datatypes/BodyTemperatureRecord.java
index cadcf56..648aa8c 100644
--- a/framework/java/android/health/connect/datatypes/BodyTemperatureRecord.java
+++ b/framework/java/android/health/connect/datatypes/BodyTemperatureRecord.java
@@ -91,6 +91,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/BodyWaterMassRecord.java b/framework/java/android/health/connect/datatypes/BodyWaterMassRecord.java
index fe16af7..391ef5d 100644
--- a/framework/java/android/health/connect/datatypes/BodyWaterMassRecord.java
+++ b/framework/java/android/health/connect/datatypes/BodyWaterMassRecord.java
@@ -66,6 +66,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the object
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/BoneMassRecord.java b/framework/java/android/health/connect/datatypes/BoneMassRecord.java
index 13c27bc..8296c62 100644
--- a/framework/java/android/health/connect/datatypes/BoneMassRecord.java
+++ b/framework/java/android/health/connect/datatypes/BoneMassRecord.java
@@ -67,6 +67,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/CervicalMucusRecord.java b/framework/java/android/health/connect/datatypes/CervicalMucusRecord.java
index 998828e..f3991b9 100644
--- a/framework/java/android/health/connect/datatypes/CervicalMucusRecord.java
+++ b/framework/java/android/health/connect/datatypes/CervicalMucusRecord.java
@@ -171,6 +171,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/CyclingPedalingCadenceRecord.java b/framework/java/android/health/connect/datatypes/CyclingPedalingCadenceRecord.java
index 5043eeb..a4beae7 100644
--- a/framework/java/android/health/connect/datatypes/CyclingPedalingCadenceRecord.java
+++ b/framework/java/android/health/connect/datatypes/CyclingPedalingCadenceRecord.java
@@ -294,6 +294,7 @@
      * @param object the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(@Nullable Object object) {
         if (super.equals(object)) {
diff --git a/framework/java/android/health/connect/datatypes/DataOrigin.java b/framework/java/android/health/connect/datatypes/DataOrigin.java
index 34ac958..5e7cfac 100644
--- a/framework/java/android/health/connect/datatypes/DataOrigin.java
+++ b/framework/java/android/health/connect/datatypes/DataOrigin.java
@@ -27,6 +27,7 @@
      * @see DataOrigin
      */
     public static final class Builder {
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         private String mPackageName;
 
         /**
diff --git a/framework/java/android/health/connect/datatypes/Device.java b/framework/java/android/health/connect/datatypes/Device.java
index f3e7e48..aa34fda 100644
--- a/framework/java/android/health/connect/datatypes/Device.java
+++ b/framework/java/android/health/connect/datatypes/Device.java
@@ -39,11 +39,16 @@
      * @see Device
      */
     public static final class Builder {
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         private String mManufacturer;
+
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         private String mModel;
+
         @DeviceType private int mType = DEVICE_TYPE_UNKNOWN;
 
         /** Sets an optional client supplied manufacturer of the device */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setManufacturer(@Nullable String manufacturer) {
             mManufacturer = manufacturer;
@@ -51,6 +56,7 @@
         }
 
         /** Sets an optional client supplied model of the device */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setModel(@Nullable String model) {
             mModel = model;
diff --git a/framework/java/android/health/connect/datatypes/DistanceRecord.java b/framework/java/android/health/connect/datatypes/DistanceRecord.java
index c2dae60..85cb6d8 100644
--- a/framework/java/android/health/connect/datatypes/DistanceRecord.java
+++ b/framework/java/android/health/connect/datatypes/DistanceRecord.java
@@ -188,6 +188,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/ElevationGainedRecord.java b/framework/java/android/health/connect/datatypes/ElevationGainedRecord.java
index 8ded9c2..57cc77f 100644
--- a/framework/java/android/health/connect/datatypes/ElevationGainedRecord.java
+++ b/framework/java/android/health/connect/datatypes/ElevationGainedRecord.java
@@ -182,6 +182,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/ExerciseLap.java b/framework/java/android/health/connect/datatypes/ExerciseLap.java
index 9b14057..90ffb2f 100644
--- a/framework/java/android/health/connect/datatypes/ExerciseLap.java
+++ b/framework/java/android/health/connect/datatypes/ExerciseLap.java
@@ -39,6 +39,7 @@
     private final TimeInterval mInterval;
     private final Length mLength;
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private ExerciseLap(
             @NonNull TimeInterval interval, @Nullable Length length, boolean skipValidation) {
         Objects.requireNonNull(interval);
@@ -121,6 +122,7 @@
         private final TimeInterval mInterval;
         private Length mLength;
 
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         public Builder(@NonNull Instant startTime, @NonNull Instant endTime) {
             mInterval = new TimeInterval(startTime, endTime);
         }
diff --git a/framework/java/android/health/connect/datatypes/ExerciseRoute.java b/framework/java/android/health/connect/datatypes/ExerciseRoute.java
index 3c0cfef..6b865bd 100644
--- a/framework/java/android/health/connect/datatypes/ExerciseRoute.java
+++ b/framework/java/android/health/connect/datatypes/ExerciseRoute.java
@@ -164,6 +164,7 @@
          * @param skipValidation Boolean flag to skip validation of record values.
          * @see ExerciseRoute
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         private Location(
                 @NonNull Instant time,
                 @FloatRange(from = MIN_LATITUDE, to = MAX_LATITUDE) double latitude,
diff --git a/framework/java/android/health/connect/datatypes/ExerciseSessionRecord.java b/framework/java/android/health/connect/datatypes/ExerciseSessionRecord.java
index 9975479..6e9fe82 100644
--- a/framework/java/android/health/connect/datatypes/ExerciseSessionRecord.java
+++ b/framework/java/android/health/connect/datatypes/ExerciseSessionRecord.java
@@ -79,7 +79,7 @@
      * @param title Title of this activity
      * @param skipValidation Boolean flag to skip validation of record values.
      */
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({"unchecked", "NullAway"})
     private ExerciseSessionRecord(
             @NonNull Metadata metadata,
             @NonNull Instant startTime,
@@ -167,6 +167,7 @@
         return mHasRoute;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -215,6 +216,7 @@
          * @param exerciseType Type of exercise (e.g. walking, swimming). Required field. Allowed
          *     values: {@link ExerciseSessionType}
          */
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         public Builder(
                 @NonNull Metadata metadata,
                 @NonNull Instant startTime,
@@ -270,6 +272,7 @@
          *
          * @param notes Notes for this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setNotes(@Nullable CharSequence notes) {
             mNotes = notes;
@@ -281,6 +284,7 @@
          *
          * @param title Title of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setTitle(@Nullable CharSequence title) {
             mTitle = title;
@@ -292,6 +296,7 @@
          *
          * @param route ExerciseRoute for this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setRoute(@Nullable ExerciseRoute route) {
             mRoute = route;
diff --git a/framework/java/android/health/connect/datatypes/FloorsClimbedRecord.java b/framework/java/android/health/connect/datatypes/FloorsClimbedRecord.java
index 589aa44..46a4352 100644
--- a/framework/java/android/health/connect/datatypes/FloorsClimbedRecord.java
+++ b/framework/java/android/health/connect/datatypes/FloorsClimbedRecord.java
@@ -83,6 +83,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/HeartRateVariabilityRmssdRecord.java b/framework/java/android/health/connect/datatypes/HeartRateVariabilityRmssdRecord.java
index ac421cf..e1e1038 100644
--- a/framework/java/android/health/connect/datatypes/HeartRateVariabilityRmssdRecord.java
+++ b/framework/java/android/health/connect/datatypes/HeartRateVariabilityRmssdRecord.java
@@ -59,6 +59,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the object
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/HeightRecord.java b/framework/java/android/health/connect/datatypes/HeightRecord.java
index f7856ee..6d83150 100644
--- a/framework/java/android/health/connect/datatypes/HeightRecord.java
+++ b/framework/java/android/health/connect/datatypes/HeightRecord.java
@@ -103,6 +103,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/HydrationRecord.java b/framework/java/android/health/connect/datatypes/HydrationRecord.java
index b5369a3..82c7e0c 100644
--- a/framework/java/android/health/connect/datatypes/HydrationRecord.java
+++ b/framework/java/android/health/connect/datatypes/HydrationRecord.java
@@ -83,6 +83,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/InstantRecord.java b/framework/java/android/health/connect/datatypes/InstantRecord.java
index a712918..bb76c0e 100644
--- a/framework/java/android/health/connect/datatypes/InstantRecord.java
+++ b/framework/java/android/health/connect/datatypes/InstantRecord.java
@@ -75,6 +75,7 @@
      * @param object the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(@Nullable Object object) {
         if (super.equals(object)) {
diff --git a/framework/java/android/health/connect/datatypes/IntermenstrualBleedingRecord.java b/framework/java/android/health/connect/datatypes/IntermenstrualBleedingRecord.java
index decf369..3b690ac 100644
--- a/framework/java/android/health/connect/datatypes/IntermenstrualBleedingRecord.java
+++ b/framework/java/android/health/connect/datatypes/IntermenstrualBleedingRecord.java
@@ -46,6 +46,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the object
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         return super.equals(o);
diff --git a/framework/java/android/health/connect/datatypes/IntervalRecord.java b/framework/java/android/health/connect/datatypes/IntervalRecord.java
index 5d55c5c..8c31e50 100644
--- a/framework/java/android/health/connect/datatypes/IntervalRecord.java
+++ b/framework/java/android/health/connect/datatypes/IntervalRecord.java
@@ -96,12 +96,14 @@
     public ZoneOffset getEndZoneOffset() {
         return mEndZoneOffset;
     }
+
     /**
      * Indicates whether some other object is "equal to" this one.
      *
      * @param object the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(@Nullable Object object) {
         if (super.equals(object)) {
diff --git a/framework/java/android/health/connect/datatypes/LeanBodyMassRecord.java b/framework/java/android/health/connect/datatypes/LeanBodyMassRecord.java
index c679494..dc1a2a3 100644
--- a/framework/java/android/health/connect/datatypes/LeanBodyMassRecord.java
+++ b/framework/java/android/health/connect/datatypes/LeanBodyMassRecord.java
@@ -69,6 +69,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/MenstruationFlowRecord.java b/framework/java/android/health/connect/datatypes/MenstruationFlowRecord.java
index 6a858a5..6c3377f 100644
--- a/framework/java/android/health/connect/datatypes/MenstruationFlowRecord.java
+++ b/framework/java/android/health/connect/datatypes/MenstruationFlowRecord.java
@@ -96,6 +96,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/Metadata.java b/framework/java/android/health/connect/datatypes/Metadata.java
index a80b6e3..bb54942 100644
--- a/framework/java/android/health/connect/datatypes/Metadata.java
+++ b/framework/java/android/health/connect/datatypes/Metadata.java
@@ -247,6 +247,7 @@
 
         @RecordingMethod private int mRecordingMethod = RECORDING_METHOD_UNKNOWN;
 
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         public Builder() {}
 
         /** Sets optional client supplied device information associated with the data. */
@@ -306,6 +307,7 @@
          *
          * <p>A null value means that no clientRecordId is set
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setClientRecordId(@Nullable String clientRecordId) {
             mClientRecordId = clientRecordId;
diff --git a/framework/java/android/health/connect/datatypes/NutritionRecord.java b/framework/java/android/health/connect/datatypes/NutritionRecord.java
index 03d9717..0241219 100644
--- a/framework/java/android/health/connect/datatypes/NutritionRecord.java
+++ b/framework/java/android/health/connect/datatypes/NutritionRecord.java
@@ -95,6 +95,7 @@
          * @param startTime Start time of this activity
          * @param endTime End time of this activity
          */
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         public Builder(
                 @NonNull Metadata metadata, @NonNull Instant startTime, @NonNull Instant endTime) {
             Objects.requireNonNull(metadata);
@@ -144,6 +145,7 @@
          *
          * @param unsaturatedFat UnsaturatedFat of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setUnsaturatedFat(@Nullable Mass unsaturatedFat) {
             mUnsaturatedFat = unsaturatedFat;
@@ -155,6 +157,7 @@
          *
          * @param potassium Potassium of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setPotassium(@Nullable Mass potassium) {
             mPotassium = potassium;
@@ -166,6 +169,7 @@
          *
          * @param thiamin Thiamin of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setThiamin(@Nullable Mass thiamin) {
             mThiamin = thiamin;
@@ -188,6 +192,7 @@
          *
          * @param transFat TransFat of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setTransFat(@Nullable Mass transFat) {
             mTransFat = transFat;
@@ -199,6 +204,7 @@
          *
          * @param manganese Manganese of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setManganese(@Nullable Mass manganese) {
             mManganese = manganese;
@@ -210,6 +216,7 @@
          *
          * @param energyFromFat EnergyFromFat of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setEnergyFromFat(@Nullable Energy energyFromFat) {
             mEnergyFromFat = energyFromFat;
@@ -221,6 +228,7 @@
          *
          * @param caffeine Caffeine of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setCaffeine(@Nullable Mass caffeine) {
             mCaffeine = caffeine;
@@ -232,6 +240,7 @@
          *
          * @param dietaryFiber DietaryFiber of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setDietaryFiber(@Nullable Mass dietaryFiber) {
             mDietaryFiber = dietaryFiber;
@@ -243,6 +252,7 @@
          *
          * @param selenium Selenium of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setSelenium(@Nullable Mass selenium) {
             mSelenium = selenium;
@@ -254,6 +264,7 @@
          *
          * @param vitaminB6 VitaminB6 of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setVitaminB6(@Nullable Mass vitaminB6) {
             mVitaminB6 = vitaminB6;
@@ -265,6 +276,7 @@
          *
          * @param protein Protein of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setProtein(@Nullable Mass protein) {
             mProtein = protein;
@@ -276,6 +288,7 @@
          *
          * @param chloride Chloride of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setChloride(@Nullable Mass chloride) {
             mChloride = chloride;
@@ -287,6 +300,7 @@
          *
          * @param cholesterol Cholesterol of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setCholesterol(@Nullable Mass cholesterol) {
             mCholesterol = cholesterol;
@@ -298,6 +312,7 @@
          *
          * @param copper Copper of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setCopper(@Nullable Mass copper) {
             mCopper = copper;
@@ -309,6 +324,7 @@
          *
          * @param iodine Iodine of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setIodine(@Nullable Mass iodine) {
             mIodine = iodine;
@@ -320,6 +336,7 @@
          *
          * @param vitaminB12 VitaminB12 of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setVitaminB12(@Nullable Mass vitaminB12) {
             mVitaminB12 = vitaminB12;
@@ -331,6 +348,7 @@
          *
          * @param zinc Zinc of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setZinc(@Nullable Mass zinc) {
             mZinc = zinc;
@@ -342,6 +360,7 @@
          *
          * @param riboflavin Riboflavin of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setRiboflavin(@Nullable Mass riboflavin) {
             mRiboflavin = riboflavin;
@@ -353,6 +372,7 @@
          *
          * @param energy Energy of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setEnergy(@Nullable Energy energy) {
             mEnergy = energy;
@@ -364,6 +384,7 @@
          *
          * @param molybdenum Molybdenum of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setMolybdenum(@Nullable Mass molybdenum) {
             mMolybdenum = molybdenum;
@@ -375,6 +396,7 @@
          *
          * @param phosphorus Phosphorus of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setPhosphorus(@Nullable Mass phosphorus) {
             mPhosphorus = phosphorus;
@@ -386,6 +408,7 @@
          *
          * @param chromium Chromium of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setChromium(@Nullable Mass chromium) {
             mChromium = chromium;
@@ -397,6 +420,7 @@
          *
          * @param totalFat TotalFat of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setTotalFat(@Nullable Mass totalFat) {
             mTotalFat = totalFat;
@@ -408,6 +432,7 @@
          *
          * @param calcium Calcium of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setCalcium(@Nullable Mass calcium) {
             mCalcium = calcium;
@@ -419,6 +444,7 @@
          *
          * @param vitaminC VitaminC of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setVitaminC(@Nullable Mass vitaminC) {
             mVitaminC = vitaminC;
@@ -430,6 +456,7 @@
          *
          * @param vitaminE VitaminE of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setVitaminE(@Nullable Mass vitaminE) {
             mVitaminE = vitaminE;
@@ -441,6 +468,7 @@
          *
          * @param biotin Biotin of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setBiotin(@Nullable Mass biotin) {
             mBiotin = biotin;
@@ -452,6 +480,7 @@
          *
          * @param vitaminD VitaminD of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setVitaminD(@Nullable Mass vitaminD) {
             mVitaminD = vitaminD;
@@ -463,6 +492,7 @@
          *
          * @param niacin Niacin of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setNiacin(@Nullable Mass niacin) {
             mNiacin = niacin;
@@ -474,6 +504,7 @@
          *
          * @param magnesium Magnesium of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setMagnesium(@Nullable Mass magnesium) {
             mMagnesium = magnesium;
@@ -485,6 +516,7 @@
          *
          * @param totalCarbohydrate TotalCarbohydrate of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setTotalCarbohydrate(@Nullable Mass totalCarbohydrate) {
             mTotalCarbohydrate = totalCarbohydrate;
@@ -496,6 +528,7 @@
          *
          * @param vitaminK VitaminK of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setVitaminK(@Nullable Mass vitaminK) {
             mVitaminK = vitaminK;
@@ -507,6 +540,7 @@
          *
          * @param polyunsaturatedFat PolyunsaturatedFat of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setPolyunsaturatedFat(@Nullable Mass polyunsaturatedFat) {
             mPolyunsaturatedFat = polyunsaturatedFat;
@@ -518,6 +552,7 @@
          *
          * @param saturatedFat SaturatedFat of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setSaturatedFat(@Nullable Mass saturatedFat) {
             mSaturatedFat = saturatedFat;
@@ -529,6 +564,7 @@
          *
          * @param sodium Sodium of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setSodium(@Nullable Mass sodium) {
             mSodium = sodium;
@@ -540,6 +576,7 @@
          *
          * @param folate Folate of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setFolate(@Nullable Mass folate) {
             mFolate = folate;
@@ -551,6 +588,7 @@
          *
          * @param monounsaturatedFat MonounsaturatedFat of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setMonounsaturatedFat(@Nullable Mass monounsaturatedFat) {
             mMonounsaturatedFat = monounsaturatedFat;
@@ -562,6 +600,7 @@
          *
          * @param pantothenicAcid PantothenicAcid of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setPantothenicAcid(@Nullable Mass pantothenicAcid) {
             mPantothenicAcid = pantothenicAcid;
@@ -584,6 +623,7 @@
          *
          * @param iron Iron of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setIron(@Nullable Mass iron) {
             mIron = iron;
@@ -595,6 +635,7 @@
          *
          * @param vitaminA VitaminA of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setVitaminA(@Nullable Mass vitaminA) {
             mVitaminA = vitaminA;
@@ -606,6 +647,7 @@
          *
          * @param folicAcid FolicAcid of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setFolicAcid(@Nullable Mass folicAcid) {
             mFolicAcid = folicAcid;
@@ -617,6 +659,7 @@
          *
          * @param sugar Sugar of this activity
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setSugar(@Nullable Mass sugar) {
             mSugar = sugar;
@@ -1317,6 +1360,7 @@
      * @param sugar Sugar of this activity in {@link Mass} unit. Optional field.
      * @param skipValidation Boolean flag to skip validation of record values.
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private NutritionRecord(
             @NonNull Metadata metadata,
             @NonNull Instant startTime,
@@ -1819,6 +1863,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/OvulationTestRecord.java b/framework/java/android/health/connect/datatypes/OvulationTestRecord.java
index 1dd2299..7ccd4fc 100644
--- a/framework/java/android/health/connect/datatypes/OvulationTestRecord.java
+++ b/framework/java/android/health/connect/datatypes/OvulationTestRecord.java
@@ -114,6 +114,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/OxygenSaturationRecord.java b/framework/java/android/health/connect/datatypes/OxygenSaturationRecord.java
index b57838a..94104dd 100644
--- a/framework/java/android/health/connect/datatypes/OxygenSaturationRecord.java
+++ b/framework/java/android/health/connect/datatypes/OxygenSaturationRecord.java
@@ -71,6 +71,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/RespiratoryRateRecord.java b/framework/java/android/health/connect/datatypes/RespiratoryRateRecord.java
index 16e931f..6325d78 100644
--- a/framework/java/android/health/connect/datatypes/RespiratoryRateRecord.java
+++ b/framework/java/android/health/connect/datatypes/RespiratoryRateRecord.java
@@ -68,6 +68,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/RestingHeartRateRecord.java b/framework/java/android/health/connect/datatypes/RestingHeartRateRecord.java
index 2eab9fc..44f1681 100644
--- a/framework/java/android/health/connect/datatypes/RestingHeartRateRecord.java
+++ b/framework/java/android/health/connect/datatypes/RestingHeartRateRecord.java
@@ -107,6 +107,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/SexualActivityRecord.java b/framework/java/android/health/connect/datatypes/SexualActivityRecord.java
index 916e96b..3f349bd 100644
--- a/framework/java/android/health/connect/datatypes/SexualActivityRecord.java
+++ b/framework/java/android/health/connect/datatypes/SexualActivityRecord.java
@@ -101,6 +101,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/SleepSessionRecord.java b/framework/java/android/health/connect/datatypes/SleepSessionRecord.java
index 77a90be..5040829 100644
--- a/framework/java/android/health/connect/datatypes/SleepSessionRecord.java
+++ b/framework/java/android/health/connect/datatypes/SleepSessionRecord.java
@@ -62,6 +62,7 @@
     private final List<Stage> mStages;
     private final CharSequence mNotes;
     private final CharSequence mTitle;
+
     /**
      * Builds {@link SleepSessionRecord} instance
      *
@@ -75,7 +76,7 @@
      * @param title Title of the session. Optional field.
      * @param skipValidation Boolean flag to skip validation of record values.
      */
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({"unchecked", "NullAway"})
     private SleepSessionRecord(
             @NonNull Metadata metadata,
             @NonNull Instant startTime,
@@ -114,6 +115,7 @@
         return mStages;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -292,6 +294,7 @@
          * @param startTime Start time of this sleep session
          * @param endTime End time of this sleep session
          */
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         public Builder(
                 @NonNull Metadata metadata, @NonNull Instant startTime, @NonNull Instant endTime) {
             Objects.requireNonNull(metadata);
@@ -341,6 +344,7 @@
          *
          * @param notes Additional notes for the session. Optional field.
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setNotes(@Nullable CharSequence notes) {
             mNotes = notes;
@@ -352,6 +356,7 @@
          *
          * @param title Title of the session. Optional field.
          */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setTitle(@Nullable CharSequence title) {
             mTitle = title;
diff --git a/framework/java/android/health/connect/datatypes/StepsRecord.java b/framework/java/android/health/connect/datatypes/StepsRecord.java
index 4b04397..1e5786a 100644
--- a/framework/java/android/health/connect/datatypes/StepsRecord.java
+++ b/framework/java/android/health/connect/datatypes/StepsRecord.java
@@ -170,6 +170,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/Vo2MaxRecord.java b/framework/java/android/health/connect/datatypes/Vo2MaxRecord.java
index 8b90e45..86bcf9f 100644
--- a/framework/java/android/health/connect/datatypes/Vo2MaxRecord.java
+++ b/framework/java/android/health/connect/datatypes/Vo2MaxRecord.java
@@ -128,6 +128,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/datatypes/WeightRecord.java b/framework/java/android/health/connect/datatypes/WeightRecord.java
index 1fdf15b..cd9fe1f 100644
--- a/framework/java/android/health/connect/datatypes/WeightRecord.java
+++ b/framework/java/android/health/connect/datatypes/WeightRecord.java
@@ -102,6 +102,7 @@
      * @param o the reference object with which to compare.
      * @return {@code true} if this object is the same as the obj
      */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/framework/java/android/health/connect/internal/datatypes/AppInfoInternal.java b/framework/java/android/health/connect/internal/datatypes/AppInfoInternal.java
index 9de2c4c..7c3edcd 100644
--- a/framework/java/android/health/connect/internal/datatypes/AppInfoInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/AppInfoInternal.java
@@ -34,6 +34,7 @@
 
     private Set<Integer> mRecordTypesUsed;
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public AppInfoInternal(
             @NonNull long id,
             @NonNull String packageName,
@@ -60,6 +61,7 @@
     }
 
     /** sets or updates the value of recordTypesUsed for app info. */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public void setRecordTypesUsed(@Nullable Set<Integer> recordTypesUsed) {
         mRecordTypesUsed = recordTypesUsed;
     }
diff --git a/framework/java/android/health/connect/internal/datatypes/CyclingPedalingCadenceRecordInternal.java b/framework/java/android/health/connect/internal/datatypes/CyclingPedalingCadenceRecordInternal.java
index 2f249a7..1d80f47 100644
--- a/framework/java/android/health/connect/internal/datatypes/CyclingPedalingCadenceRecordInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/CyclingPedalingCadenceRecordInternal.java
@@ -38,6 +38,7 @@
         extends SeriesRecordInternal<
                 CyclingPedalingCadenceRecord,
                 CyclingPedalingCadenceRecord.CyclingPedalingCadenceRecordSample> {
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private Set<CyclingPedalingCadenceRecordSample> mCyclingPedalingCadenceRecordSamples;
 
     @Override
diff --git a/framework/java/android/health/connect/internal/datatypes/ExerciseLapInternal.java b/framework/java/android/health/connect/internal/datatypes/ExerciseLapInternal.java
index 9119195..4bf2b71 100644
--- a/framework/java/android/health/connect/internal/datatypes/ExerciseLapInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/ExerciseLapInternal.java
@@ -66,6 +66,7 @@
         return externalLaps;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     static List<ExerciseLapInternal> populateLapsFromParcel(Parcel parcel) {
         int lapsSize = parcel.readInt();
         if (lapsSize == 0) {
diff --git a/framework/java/android/health/connect/internal/datatypes/ExerciseSegmentInternal.java b/framework/java/android/health/connect/internal/datatypes/ExerciseSegmentInternal.java
index cb08779..54b6b83 100644
--- a/framework/java/android/health/connect/internal/datatypes/ExerciseSegmentInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/ExerciseSegmentInternal.java
@@ -52,6 +52,7 @@
                 .setSegmentType(parcel.readInt());
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     static List<ExerciseSegmentInternal> populateSegmentsFromParcel(Parcel parcel) {
         int size = parcel.readInt();
         if (size == 0) {
diff --git a/framework/java/android/health/connect/internal/datatypes/ExerciseSessionRecordInternal.java b/framework/java/android/health/connect/internal/datatypes/ExerciseSessionRecordInternal.java
index 56dc646..03ca0f1 100644
--- a/framework/java/android/health/connect/internal/datatypes/ExerciseSessionRecordInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/ExerciseSessionRecordInternal.java
@@ -35,14 +35,23 @@
 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_EXERCISE_SESSION)
 public final class ExerciseSessionRecordInternal
         extends IntervalRecordInternal<ExerciseSessionRecord> {
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private String mNotes;
+
     private int mExerciseType;
+
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private String mTitle;
+
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private ExerciseRouteInternal mExerciseRoute;
 
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private List<ExerciseLapInternal> mExerciseLaps;
 
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private List<ExerciseSegmentInternal> mExerciseSegments;
+
     private boolean mHasRoute;
 
     @Nullable
@@ -191,6 +200,7 @@
         ExerciseSegmentInternal.writeSegmentsToParcel(mExerciseSegments, parcel);
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public void populateIntervalRecordFrom(@NonNull Parcel parcel) {
         mNotes = parcel.readString();
diff --git a/framework/java/android/health/connect/internal/datatypes/HeartRateRecordInternal.java b/framework/java/android/health/connect/internal/datatypes/HeartRateRecordInternal.java
index 31d645b..b78b583 100644
--- a/framework/java/android/health/connect/internal/datatypes/HeartRateRecordInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/HeartRateRecordInternal.java
@@ -70,8 +70,10 @@
         }
     }
 
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private Set<HeartRateSample> mHeartRateHeartRateSamples;
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     @Nullable
     public Set<HeartRateSample> getSamples() {
diff --git a/framework/java/android/health/connect/internal/datatypes/NutritionRecordInternal.java b/framework/java/android/health/connect/internal/datatypes/NutritionRecordInternal.java
index a121061..855aed9 100644
--- a/framework/java/android/health/connect/internal/datatypes/NutritionRecordInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/NutritionRecordInternal.java
@@ -72,7 +72,10 @@
     private double mFolate = DEFAULT_DOUBLE;
     private double mMonounsaturatedFat = DEFAULT_DOUBLE;
     private double mPantothenicAcid = DEFAULT_DOUBLE;
+
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private String mMealName;
+
     private double mIron = DEFAULT_DOUBLE;
     private double mVitaminA = DEFAULT_DOUBLE;
     private double mFolicAcid = DEFAULT_DOUBLE;
diff --git a/framework/java/android/health/connect/internal/datatypes/PowerRecordInternal.java b/framework/java/android/health/connect/internal/datatypes/PowerRecordInternal.java
index 85575a8..443ae70 100644
--- a/framework/java/android/health/connect/internal/datatypes/PowerRecordInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/PowerRecordInternal.java
@@ -37,6 +37,7 @@
 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_POWER)
 public class PowerRecordInternal
         extends SeriesRecordInternal<PowerRecord, PowerRecord.PowerRecordSample> {
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private Set<PowerRecordSample> mPowerRecordSamples;
 
     @Override
diff --git a/framework/java/android/health/connect/internal/datatypes/RecordInternal.java b/framework/java/android/health/connect/internal/datatypes/RecordInternal.java
index 50796da..69978de 100644
--- a/framework/java/android/health/connect/internal/datatypes/RecordInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/RecordInternal.java
@@ -56,6 +56,7 @@
 
     @Metadata.RecordingMethod private int mRecordingMethod;
 
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     RecordInternal() {
         Identifier annotation = this.getClass().getAnnotation(Identifier.class);
         Objects.requireNonNull(annotation);
@@ -114,12 +115,14 @@
         return mUuid;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public RecordInternal<T> setUuid(@Nullable UUID uuid) {
         this.mUuid = uuid;
         return this;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public RecordInternal<T> setUuid(@Nullable String uuid) {
         if (uuid == null || uuid.isEmpty()) {
@@ -136,6 +139,7 @@
         return mPackageName;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public RecordInternal<T> setPackageName(@Nullable String packageName) {
         this.mPackageName = packageName;
@@ -164,6 +168,7 @@
     }
 
     /** Sets the application name for this record. */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public RecordInternal<T> setAppName(@Nullable String appName) {
         mAppName = appName;
@@ -185,6 +190,7 @@
         return mClientRecordId;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public RecordInternal<T> setClientRecordId(@Nullable String clientRecordId) {
         this.mClientRecordId = clientRecordId;
@@ -206,6 +212,7 @@
         return mManufacturer;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public RecordInternal<T> setManufacturer(@Nullable String manufacturer) {
         this.mManufacturer = manufacturer;
@@ -217,6 +224,7 @@
         return mModel;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public RecordInternal<T> setModel(@Nullable String model) {
         this.mModel = model;
@@ -270,6 +278,7 @@
     /** Child class must implement this method and return an external record for this record */
     public abstract T toExternalRecord();
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     Metadata buildMetaData() {
         return new Metadata.Builder()
diff --git a/framework/java/android/health/connect/internal/datatypes/SleepSessionRecordInternal.java b/framework/java/android/health/connect/internal/datatypes/SleepSessionRecordInternal.java
index b705b39..cd7d048 100644
--- a/framework/java/android/health/connect/internal/datatypes/SleepSessionRecordInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/SleepSessionRecordInternal.java
@@ -33,8 +33,13 @@
  */
 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_SLEEP_SESSION)
 public final class SleepSessionRecordInternal extends IntervalRecordInternal<SleepSessionRecord> {
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private List<SleepStageInternal> mStages;
+
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private String mNotes;
+
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private String mTitle;
 
     @Nullable
diff --git a/framework/java/android/health/connect/internal/datatypes/SleepStageInternal.java b/framework/java/android/health/connect/internal/datatypes/SleepStageInternal.java
index ea8729b..a118b2c 100644
--- a/framework/java/android/health/connect/internal/datatypes/SleepStageInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/SleepStageInternal.java
@@ -46,6 +46,7 @@
                 .setStageType(parcel.readInt());
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     static List<SleepStageInternal> populateStagesFromParcel(Parcel parcel) {
         int size = parcel.readInt();
         if (size == 0) {
diff --git a/framework/java/android/health/connect/internal/datatypes/SpeedRecordInternal.java b/framework/java/android/health/connect/internal/datatypes/SpeedRecordInternal.java
index 54bcdb1..b44ea49 100644
--- a/framework/java/android/health/connect/internal/datatypes/SpeedRecordInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/SpeedRecordInternal.java
@@ -37,6 +37,7 @@
 @Identifier(recordIdentifier = RecordTypeIdentifier.RECORD_TYPE_SPEED)
 public class SpeedRecordInternal
         extends SeriesRecordInternal<SpeedRecord, SpeedRecord.SpeedRecordSample> {
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private Set<SpeedRecordSample> mSpeedRecordSamples;
 
     @Override
diff --git a/framework/java/android/health/connect/internal/datatypes/StepsCadenceRecordInternal.java b/framework/java/android/health/connect/internal/datatypes/StepsCadenceRecordInternal.java
index 6143b59..5b68e3d 100644
--- a/framework/java/android/health/connect/internal/datatypes/StepsCadenceRecordInternal.java
+++ b/framework/java/android/health/connect/internal/datatypes/StepsCadenceRecordInternal.java
@@ -37,6 +37,7 @@
 public class StepsCadenceRecordInternal
         extends SeriesRecordInternal<
                 StepsCadenceRecord, StepsCadenceRecord.StepsCadenceRecordSample> {
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private Set<StepsCadenceRecordSample> mStepsCadenceRecordSamples;
 
     @Override
diff --git a/framework/java/android/health/connect/internal/datatypes/utils/AggregationTypeIdMapper.java b/framework/java/android/health/connect/internal/datatypes/utils/AggregationTypeIdMapper.java
index e506a46..a3ec86a 100644
--- a/framework/java/android/health/connect/internal/datatypes/utils/AggregationTypeIdMapper.java
+++ b/framework/java/android/health/connect/internal/datatypes/utils/AggregationTypeIdMapper.java
@@ -123,7 +123,9 @@
  * @hide
  */
 public final class AggregationTypeIdMapper {
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile AggregationTypeIdMapper sAggregationTypeIdMapper;
+
     private final Map<Integer, AggregationResultCreator> mIdToAggregateResult;
     private final Map<Integer, AggregationType<?>> mIdDataAggregationTypeMap;
     private final Map<AggregationType<?>, Integer> mDataAggregationTypeIdMap;
@@ -236,18 +238,21 @@
         return sAggregationTypeIdMapper;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public AggregateResult<?> getAggregateResultFor(
             @AggregationType.AggregationTypeIdentifier.Id int id, @NonNull Parcel parcel) {
         return mIdToAggregateResult.get(id).getAggregateResult(parcel);
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public AggregationType<?> getAggregationTypeFor(
             @AggregationType.AggregationTypeIdentifier.Id int id) {
         return mIdDataAggregationTypeMap.get(id);
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     @AggregationType.AggregationTypeIdentifier.Id
     public int getIdFor(AggregationType<?> aggregationType) {
diff --git a/framework/java/android/health/connect/internal/datatypes/utils/InternalExternalRecordConverter.java b/framework/java/android/health/connect/internal/datatypes/utils/InternalExternalRecordConverter.java
index 9095489..27ad129 100644
--- a/framework/java/android/health/connect/internal/datatypes/utils/InternalExternalRecordConverter.java
+++ b/framework/java/android/health/connect/internal/datatypes/utils/InternalExternalRecordConverter.java
@@ -35,6 +35,7 @@
  * @hide
  */
 public final class InternalExternalRecordConverter {
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile InternalExternalRecordConverter sInternalExternalRecordConverter;
 
     private final Map<Integer, Class<? extends RecordInternal<?>>>
@@ -78,6 +79,7 @@
     }
 
     /** Returns a record for {@param record} */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public List<Record> getExternalRecords(@NonNull List<RecordInternal<?>> recordInternals) {
         List<Record> externalRecordList = new ArrayList<>(recordInternals.size());
diff --git a/framework/java/android/health/connect/internal/datatypes/utils/ParcelRecordConverter.java b/framework/java/android/health/connect/internal/datatypes/utils/ParcelRecordConverter.java
index 78f3ef2..d7c8791 100644
--- a/framework/java/android/health/connect/internal/datatypes/utils/ParcelRecordConverter.java
+++ b/framework/java/android/health/connect/internal/datatypes/utils/ParcelRecordConverter.java
@@ -31,6 +31,7 @@
  * @hide
  */
 public final class ParcelRecordConverter {
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private static volatile ParcelRecordConverter sParcelRecordConverter = null;
 
     private final Map<Integer, Class<? extends RecordInternal<?>>> mDataTypeClassMap;
diff --git a/framework/java/android/health/connect/internal/datatypes/utils/RecordMapper.java b/framework/java/android/health/connect/internal/datatypes/utils/RecordMapper.java
index 800dc65..d477ac3 100644
--- a/framework/java/android/health/connect/internal/datatypes/utils/RecordMapper.java
+++ b/framework/java/android/health/connect/internal/datatypes/utils/RecordMapper.java
@@ -103,7 +103,10 @@
 /** @hide */
 public final class RecordMapper {
     private static final int NUM_ENTRIES = 35;
+
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile RecordMapper sRecordMapper;
+
     private final Map<Integer, Class<? extends RecordInternal<?>>>
             mRecordIdToInternalRecordClassMap;
     private final Map<Integer, Class<? extends Record>> mRecordIdToExternalRecordClassMap;
@@ -325,6 +328,7 @@
         return mRecordIdToExternalRecordClassMap;
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @RecordTypeIdentifier.RecordType
     public int getRecordType(Class<? extends Record> recordClass) {
         return mExternalRecordClassToRecordIdMap.get(recordClass);
diff --git a/framework/java/android/health/connect/migration/AppInfoMigrationPayload.java b/framework/java/android/health/connect/migration/AppInfoMigrationPayload.java
index 363c251..ef72446 100644
--- a/framework/java/android/health/connect/migration/AppInfoMigrationPayload.java
+++ b/framework/java/android/health/connect/migration/AppInfoMigrationPayload.java
@@ -51,6 +51,7 @@
     private final String mAppName;
     private final byte[] mAppIcon;
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private AppInfoMigrationPayload(
             @NonNull String packageName, @NonNull String appName, @Nullable byte[] appIcon) {
         mPackageName = packageName;
@@ -105,6 +106,7 @@
         private String mAppName;
         private byte[] mAppIcon;
 
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         public Builder(@NonNull String packageName, @NonNull String appName) {
             requireNonNull(packageName);
             requireNonNull(appName);
@@ -130,6 +132,7 @@
         }
 
         /** Sets the value for {@link AppInfoMigrationPayload#getAppIcon()}. */
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         public Builder setAppIcon(@Nullable byte[] appIcon) {
             mAppIcon = appIcon;
diff --git a/framework/java/android/health/connect/migration/MigrationException.java b/framework/java/android/health/connect/migration/MigrationException.java
index 8616060..bf2c1c5 100644
--- a/framework/java/android/health/connect/migration/MigrationException.java
+++ b/framework/java/android/health/connect/migration/MigrationException.java
@@ -63,6 +63,7 @@
     @ErrorCode private final int mErrorCode;
     private final String mFailedEntityId;
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public MigrationException(
             @Nullable String message, @ErrorCode int errorCode, @Nullable String failedEntityId) {
         super(message);
diff --git a/framework/java/android/health/connect/migration/PriorityMigrationPayload.java b/framework/java/android/health/connect/migration/PriorityMigrationPayload.java
index aef672b..85cecd3 100644
--- a/framework/java/android/health/connect/migration/PriorityMigrationPayload.java
+++ b/framework/java/android/health/connect/migration/PriorityMigrationPayload.java
@@ -72,6 +72,7 @@
                         .toList();
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private static DataOrigin dataOriginOf(@Nullable String packageName) {
         return new DataOrigin.Builder().setPackageName(packageName).build();
     }
diff --git a/framework/java/android/health/connect/migration/RecordMigrationPayload.java b/framework/java/android/health/connect/migration/RecordMigrationPayload.java
index 62083e5..48e638d 100644
--- a/framework/java/android/health/connect/migration/RecordMigrationPayload.java
+++ b/framework/java/android/health/connect/migration/RecordMigrationPayload.java
@@ -84,12 +84,14 @@
     }
 
     /** Returns origin package name associated with this payload. */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public String getOriginPackageName() {
         return mRecordInternal.getPackageName();
     }
 
     /** Returns origin application name associated with this payload. */
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public String getOriginAppName() {
         return mRecordInternal.getAppName();
diff --git a/framework/java/android/health/connect/ratelimiter/RateLimiter.java b/framework/java/android/health/connect/ratelimiter/RateLimiter.java
index 64ac08c..6f85a8b 100644
--- a/framework/java/android/health/connect/ratelimiter/RateLimiter.java
+++ b/framework/java/android/health/connect/ratelimiter/RateLimiter.java
@@ -199,6 +199,7 @@
         }
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private static Object getLockObject(int uid) {
         sLocks.putIfAbsent(uid, uid);
         return sLocks.get(uid);
@@ -245,6 +246,7 @@
                 uid, memoryQuotaBucketToAvailableQuotaMap, memoryQuotaBuckets, memoryCost);
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private static void checkIfResourcesAreAvailable(
             Map<Integer, Float> quotaBucketToAvailableQuotaMap,
             List<Integer> quotaBuckets,
@@ -259,6 +261,7 @@
         quota.setLastUpdatedTime(Instant.now());
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private static void spendAvailableResources(
             int uid,
             Map<Integer, Float> quotaBucketToAvailableQuotaMap,
@@ -269,6 +272,7 @@
         }
     }
 
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private static void spendResources(
             int uid, @QuotaBucket.Type int quotaBucket, float availableQuota, long cost) {
         sUserIdToQuotasMap
diff --git a/service/java/com/android/server/healthconnect/HealthConnectDeviceConfigManager.java b/service/java/com/android/server/healthconnect/HealthConnectDeviceConfigManager.java
index bee024f..8e7c734 100644
--- a/service/java/com/android/server/healthconnect/HealthConnectDeviceConfigManager.java
+++ b/service/java/com/android/server/healthconnect/HealthConnectDeviceConfigManager.java
@@ -172,7 +172,7 @@
     @VisibleForTesting
     public static final boolean ENABLE_AGGREGATION_SOURCE_CONTROLS_DEFAULT_FLAG_VALUE = true;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static HealthConnectDeviceConfigManager sDeviceConfigManager;
 
     private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
diff --git a/service/java/com/android/server/healthconnect/HealthConnectRoundRobinScheduler.java b/service/java/com/android/server/healthconnect/HealthConnectRoundRobinScheduler.java
index a6bc1a2..ff40921 100644
--- a/service/java/com/android/server/healthconnect/HealthConnectRoundRobinScheduler.java
+++ b/service/java/com/android/server/healthconnect/HealthConnectRoundRobinScheduler.java
@@ -40,7 +40,7 @@
     @GuardedBy("mLock")
     private boolean mPauseScheduler;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     @GuardedBy("mLock")
     private Integer mLastKeyUsed;
 
@@ -50,7 +50,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     void addTask(int uid, Runnable task) {
         synchronized (mLock) {
             // If the scheduler is currently paused (this can happen if the platform is doing a user
diff --git a/service/java/com/android/server/healthconnect/HealthConnectServiceImpl.java b/service/java/com/android/server/healthconnect/HealthConnectServiceImpl.java
index 9e72002..003b810 100644
--- a/service/java/com/android/server/healthconnect/HealthConnectServiceImpl.java
+++ b/service/java/com/android/server/healthconnect/HealthConnectServiceImpl.java
@@ -50,6 +50,7 @@
 import android.health.connect.HealthConnectManager.DataDownloadState;
 import android.health.connect.HealthDataCategory;
 import android.health.connect.HealthPermissions;
+import android.health.connect.PageTokenWrapper;
 import android.health.connect.RecordTypeInfoResponse;
 import android.health.connect.accesslog.AccessLog;
 import android.health.connect.accesslog.AccessLogsResponseParcel;
@@ -698,11 +699,12 @@
                                                 readTransactionRequest);
                                 pageToken = DEFAULT_LONG;
                             } else {
-                                Pair<List<RecordInternal<?>>, Long> readRecordsResponse =
-                                        mTransactionManager.readRecordsAndPageToken(
-                                                readTransactionRequest);
+                                Pair<List<RecordInternal<?>>, PageTokenWrapper>
+                                        readRecordsResponse =
+                                                mTransactionManager.readRecordsAndPageToken(
+                                                        readTransactionRequest);
                                 records = readRecordsResponse.first;
-                                pageToken = readRecordsResponse.second;
+                                pageToken = readRecordsResponse.second.encode();
                             }
                             logger.setNumberOfRecords(records.size());
 
diff --git a/service/java/com/android/server/healthconnect/backuprestore/BackupRestore.java b/service/java/com/android/server/healthconnect/backuprestore/BackupRestore.java
index ce36bab..352a95e 100644
--- a/service/java/com/android/server/healthconnect/backuprestore/BackupRestore.java
+++ b/service/java/com/android/server/healthconnect/backuprestore/BackupRestore.java
@@ -31,6 +31,7 @@
 import static android.health.connect.HealthConnectManager.DATA_DOWNLOAD_RETRY;
 import static android.health.connect.HealthConnectManager.DATA_DOWNLOAD_STARTED;
 import static android.health.connect.HealthConnectManager.DATA_DOWNLOAD_STATE_UNKNOWN;
+import static android.health.connect.PageTokenWrapper.EMPTY_PAGE_TOKEN;
 
 import static com.android.server.healthconnect.backuprestore.BackupRestore.BackupRestoreJobService.EXTRA_JOB_NAME_KEY;
 import static com.android.server.healthconnect.backuprestore.BackupRestore.BackupRestoreJobService.EXTRA_USER_ID;
@@ -54,6 +55,7 @@
 import android.health.connect.HealthConnectDataState;
 import android.health.connect.HealthConnectException;
 import android.health.connect.HealthConnectManager.DataDownloadState;
+import android.health.connect.PageTokenWrapper;
 import android.health.connect.ReadRecordsRequestUsingFilters;
 import android.health.connect.aidl.IDataStagingFinishedCallback;
 import android.health.connect.datatypes.Record;
@@ -210,7 +212,7 @@
 
     private volatile UserHandle mCurrentForegroundUser;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public BackupRestore(
             FirstGrantTimeManager firstGrantTimeManager,
             MigrationStateManager migrationStateManager,
@@ -358,7 +360,7 @@
         var backupFilesByFileNames = getBackupFilesByFileNames(userHandle);
         pfdsByFileName.forEach(
                 (fileName, pfd) -> {
-                    @SuppressWarnings("NullAway")
+                    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
                     Path sourceFilePath = backupFilesByFileNames.get(fileName).toPath();
                     try (FileOutputStream outputStream =
                             new FileOutputStream(pfd.getFileDescriptor())) {
@@ -399,7 +401,7 @@
     }
 
     /** Deletes all the staged data and resets all the states. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public void deleteAndResetEverything(@NonNull UserHandle userHandle) {
         // Don't delete anything while we are in the process of merging staged data.
         synchronized (mMergingLock) {
@@ -991,7 +993,7 @@
                 mCurrentForegroundUser, userGrantTimeState);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private void mergeDatabase() {
         synchronized (mMergingLock) {
             if (!mStagedDbContext.getDatabasePath(STAGED_DATABASE_NAME).exists()) {
@@ -1029,7 +1031,7 @@
                 RecordHelperProvider.getInstance().getRecordHelper(recordType);
         // Read all the records of the given type from the staged db and insert them into the
         // existing healthconnect db.
-        long token = DEFAULT_LONG;
+        PageTokenWrapper token = EMPTY_PAGE_TOKEN;
         do {
             var recordsToMergeAndToken = getRecordsToMerge(recordTypeClass, token, recordHelper);
             if (recordsToMergeAndToken.first.isEmpty()) {
@@ -1051,14 +1053,14 @@
                     .insertAll(upsertTransactionRequest.getUpsertRequests());
 
             token = recordsToMergeAndToken.second;
-        } while (token != DEFAULT_LONG);
+        } while (!token.isEmpty());
 
         // Once all the records of this type have been merged we can delete the table.
 
         // Passing -1 for startTime and endTime as we don't want to have time based filtering in the
         // final query.
         Slog.d(TAG, "Deleting table for: " + recordTypeClass);
-        @SuppressWarnings("NullAway")
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         DeleteTableRequest deleteTableRequest =
                 recordHelper.getDeleteTableRequest(
                         null /* packageFilters */,
@@ -1068,12 +1070,12 @@
         getStagedDatabase().getWritableDatabase().execSQL(deleteTableRequest.getDeleteCommand());
     }
 
-    private <T extends Record> Pair<List<RecordInternal<?>>, Long> getRecordsToMerge(
-            Class<T> recordTypeClass, long requestToken, RecordHelper<?> recordHelper) {
+    private <T extends Record> Pair<List<RecordInternal<?>>, PageTokenWrapper> getRecordsToMerge(
+            Class<T> recordTypeClass, PageTokenWrapper requestToken, RecordHelper<?> recordHelper) {
         ReadRecordsRequestUsingFilters<T> readRecordsRequest =
                 new ReadRecordsRequestUsingFilters.Builder<>(recordTypeClass)
                         .setPageSize(2000)
-                        .setPageToken(requestToken)
+                        .setPageToken(requestToken.encode())
                         .build();
 
         Map<String, Boolean> extraReadPermsMapping = new ArrayMap<>();
@@ -1084,7 +1086,7 @@
 
         // Working with startDateAccess of -1 as we don't want to have time based filtering in the
         // query.
-        @SuppressWarnings("NullAway")
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         ReadTransactionRequest readTransactionRequest =
                 new ReadTransactionRequest(
                         null,
@@ -1094,10 +1096,10 @@
                         extraReadPermsMapping);
 
         List<RecordInternal<?>> recordInternalList;
-        long token;
+        PageTokenWrapper token;
         ReadTableRequest readTableRequest = readTransactionRequest.getReadRequests().get(0);
         try (Cursor cursor = read(readTableRequest)) {
-            Pair<List<RecordInternal<?>>, Long> readResult =
+            Pair<List<RecordInternal<?>>, PageTokenWrapper> readResult =
                     recordHelper.getNextInternalRecordsPageAndToken(
                             cursor,
                             readTransactionRequest.getPageSize().orElse(DEFAULT_PAGE_SIZE),
@@ -1209,7 +1211,7 @@
         public static final String EXTRA_JOB_NAME_KEY = "job_name";
         private static final int BACKUP_RESTORE_JOB_ID = 1000;
 
-        @SuppressWarnings("NullAway.Init")
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         static volatile BackupRestore sBackupRestore;
 
         @Override
diff --git a/service/java/com/android/server/healthconnect/migration/MigrationStateManager.java b/service/java/com/android/server/healthconnect/migration/MigrationStateManager.java
index e9fdc54..5a9c55e 100644
--- a/service/java/com/android/server/healthconnect/migration/MigrationStateManager.java
+++ b/service/java/com/android/server/healthconnect/migration/MigrationStateManager.java
@@ -79,7 +79,7 @@
  * @hide
  */
 public final class MigrationStateManager {
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     @GuardedBy("sInstanceLock")
     private static MigrationStateManager sMigrationStateManager;
 
@@ -95,7 +95,7 @@
     private volatile MigrationBroadcastScheduler mMigrationBroadcastScheduler;
     private int mUserId;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private MigrationStateManager(@UserIdInt int userId) {
         mUserId = userId;
     }
@@ -136,7 +136,7 @@
      * Clears the initialized instance such that {@link #initializeInstance} will create a new
      * instance, for use in tests.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @VisibleForTesting
     public static void resetInitializedInstanceForTest() {
         synchronized (sInstanceLock) {
@@ -615,7 +615,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     String getAllowedStateTimeout() {
         String allowedStateStartTime =
                 PreferenceHelper.getInstance().getPreference(ALLOWED_STATE_START_TIME_KEY);
diff --git a/service/java/com/android/server/healthconnect/migration/MigrationUtils.java b/service/java/com/android/server/healthconnect/migration/MigrationUtils.java
index a561182..60a55f0 100644
--- a/service/java/com/android/server/healthconnect/migration/MigrationUtils.java
+++ b/service/java/com/android/server/healthconnect/migration/MigrationUtils.java
@@ -125,7 +125,7 @@
     }
 
     /** Computes the SHA256 digest of the input data. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public static String computeSha256DigestBytes(@NonNull byte[] data) {
         MessageDigest messageDigest;
         try {
diff --git a/service/java/com/android/server/healthconnect/migration/MigratorPackageChangesReceiver.java b/service/java/com/android/server/healthconnect/migration/MigratorPackageChangesReceiver.java
index d6c2a83..16cd9d5 100644
--- a/service/java/com/android/server/healthconnect/migration/MigratorPackageChangesReceiver.java
+++ b/service/java/com/android/server/healthconnect/migration/MigratorPackageChangesReceiver.java
@@ -99,14 +99,14 @@
         return filter;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     private String getPackageName(@NonNull Intent intent) {
         Uri uri = intent.getData();
         return uri != null ? uri.getSchemeSpecificPart() : null;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     private UserHandle getUserHandle(@NonNull Intent intent) {
         final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
diff --git a/service/java/com/android/server/healthconnect/migration/PriorityMigrationHelper.java b/service/java/com/android/server/healthconnect/migration/PriorityMigrationHelper.java
index 90c4651..14dac87 100644
--- a/service/java/com/android/server/healthconnect/migration/PriorityMigrationHelper.java
+++ b/service/java/com/android/server/healthconnect/migration/PriorityMigrationHelper.java
@@ -61,13 +61,13 @@
 
     private static final Object sPriorityMigrationHelperLock = new Object();
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile PriorityMigrationHelper sPriorityMigrationHelper;
 
     private final Object mPriorityMigrationHelperInstanceLock = new Object();
     private Map<Integer, List<Long>> mPreMigrationPriorityCache;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private PriorityMigrationHelper() {}
 
     /**
@@ -120,7 +120,7 @@
     }
 
     /** Delete pre-migration priority data when migration is finished. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public void clearData(@NonNull TransactionManager transactionManager) {
         synchronized (mPriorityMigrationHelperInstanceLock) {
             transactionManager.delete(new DeleteTableRequest(PRE_MIGRATION_TABLE_NAME));
diff --git a/service/java/com/android/server/healthconnect/migration/notification/HealthConnectResourcesContext.java b/service/java/com/android/server/healthconnect/migration/notification/HealthConnectResourcesContext.java
index 43f2c23..eda4cb6 100644
--- a/service/java/com/android/server/healthconnect/migration/notification/HealthConnectResourcesContext.java
+++ b/service/java/com/android/server/healthconnect/migration/notification/HealthConnectResourcesContext.java
@@ -83,7 +83,7 @@
         initialisePackageNames();
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private void initialisePackageNames() {
         ResolveInfo info = resolvePackageInfo();
 
diff --git a/service/java/com/android/server/healthconnect/migration/notification/IntentsUtil.java b/service/java/com/android/server/healthconnect/migration/notification/IntentsUtil.java
index 1cdfefa..bb80a0b 100644
--- a/service/java/com/android/server/healthconnect/migration/notification/IntentsUtil.java
+++ b/service/java/com/android/server/healthconnect/migration/notification/IntentsUtil.java
@@ -80,7 +80,7 @@
     }
 
     /** Convenience method that looks up the installerPackageName for you. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Nullable
     public static Intent createAppStoreIntent(
             @NonNull Context context, @NonNull String packageName) {
diff --git a/service/java/com/android/server/healthconnect/migration/notification/MigrationNotificationFactory.java b/service/java/com/android/server/healthconnect/migration/notification/MigrationNotificationFactory.java
index c8c8611..24a7180 100644
--- a/service/java/com/android/server/healthconnect/migration/notification/MigrationNotificationFactory.java
+++ b/service/java/com/android/server/healthconnect/migration/notification/MigrationNotificationFactory.java
@@ -102,7 +102,7 @@
 
     @VisibleForTesting static final String APP_ICON_DRAWABLE_NAME = "health_connect_logo";
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public MigrationNotificationFactory(@NonNull Context context) {
         mContext = context;
         mResContext = new HealthConnectResourcesContext(mContext);
@@ -153,13 +153,13 @@
     }
 
     /** Retrieves a string resource by name from the Health Connect resources. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public String getStringResource(@NonNull String name) {
         return mResContext.getStringByName(name);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     private String getStringResourceWithArgs(@NonNull String name, Object... formatArgs) {
         return mResContext.getStringByNameWithArgs(name, formatArgs);
@@ -380,7 +380,7 @@
         return getPendingIntent(intent);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Nullable
     private PendingIntent getAppStorePendingIntent() {
         String dataMigratorPackageName =
@@ -401,7 +401,7 @@
         return getPendingIntent(intent);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @VisibleForTesting
     @Nullable
     Icon getAppIcon() {
diff --git a/service/java/com/android/server/healthconnect/migration/notification/MigrationNotificationSender.java b/service/java/com/android/server/healthconnect/migration/notification/MigrationNotificationSender.java
index 275c3e6..abcf8f8 100644
--- a/service/java/com/android/server/healthconnect/migration/notification/MigrationNotificationSender.java
+++ b/service/java/com/android/server/healthconnect/migration/notification/MigrationNotificationSender.java
@@ -93,7 +93,7 @@
         return contextAsUser.getSystemService(NotificationManager.class);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private void notifyFromSystem(
             @Nullable NotificationManager notificationManager, @NonNull Notification notification) {
         // This call is needed to send a notification from the system and this also grants the
@@ -109,7 +109,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private void cancelFromSystem(@Nullable NotificationManager notificationManager) {
         final long callingId = Binder.clearCallingIdentity();
         try {
@@ -122,7 +122,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private void createNotificationChannel(@NonNull UserHandle userHandle) {
 
         final String channelGroupName =
diff --git a/service/java/com/android/server/healthconnect/permission/FirstGrantTimeDatastoreXmlPersistence.java b/service/java/com/android/server/healthconnect/permission/FirstGrantTimeDatastoreXmlPersistence.java
index 9142111..ac63646 100644
--- a/service/java/com/android/server/healthconnect/permission/FirstGrantTimeDatastoreXmlPersistence.java
+++ b/service/java/com/android/server/healthconnect/permission/FirstGrantTimeDatastoreXmlPersistence.java
@@ -40,7 +40,7 @@
      *
      * @hide
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Nullable
     @Override
     public UserGrantTimeState readForUser(@NonNull UserHandle user, @DataType int dataType) {
diff --git a/service/java/com/android/server/healthconnect/permission/FirstGrantTimeManager.java b/service/java/com/android/server/healthconnect/permission/FirstGrantTimeManager.java
index 2463e82..6fe5313 100644
--- a/service/java/com/android/server/healthconnect/permission/FirstGrantTimeManager.java
+++ b/service/java/com/android/server/healthconnect/permission/FirstGrantTimeManager.java
@@ -82,7 +82,7 @@
     }
 
     /** Get the date when the first health permission was granted. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Nullable
     public Instant getFirstGrantTime(@NonNull String packageName, @NonNull UserHandle user)
             throws IllegalArgumentException {
@@ -256,7 +256,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @GuardedBy("mGrantTimeLock")
     private Instant getGrantTimeReadLocked(Integer uid) {
         mGrantTimeLock.readLock().lock();
@@ -284,7 +284,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @GuardedBy("mGrantTimeLock")
     private boolean tryUpdateGrantTimeFromStagedDataLocked(UserHandle user, Integer uid) {
         UserGrantTimeState backupState = mDatastore.readForUser(user, DATA_TYPE_STAGED);
@@ -595,7 +595,7 @@
                     packageNameToGrantTime, sharedUserToGrantTime, CURRENT_VERSION);
         }
 
-        @SuppressWarnings("NullAway")
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         @NonNull
         UserGrantTimeState extractUserBackupGrantTimeState(@NonNull UserHandle user) {
             Map<String, Instant> sharedUserToGrantTime = new ArrayMap<>();
diff --git a/service/java/com/android/server/healthconnect/permission/GrantTimeXmlHelper.java b/service/java/com/android/server/healthconnect/permission/GrantTimeXmlHelper.java
index 65ecad5..00db4e7 100644
--- a/service/java/com/android/server/healthconnect/permission/GrantTimeXmlHelper.java
+++ b/service/java/com/android/server/healthconnect/permission/GrantTimeXmlHelper.java
@@ -85,7 +85,7 @@
      * @param file the file from which the data should be parsed.
      * @return the grant times.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public static UserGrantTimeState parseGrantTime(File file) {
         try (FileInputStream inputStream = new AtomicFile(file).openRead()) {
             XmlPullParser parser = Xml.newPullParser();
diff --git a/service/java/com/android/server/healthconnect/permission/HealthConnectPermissionHelper.java b/service/java/com/android/server/healthconnect/permission/HealthConnectPermissionHelper.java
index a2bdb0e..0cba62b 100644
--- a/service/java/com/android/server/healthconnect/permission/HealthConnectPermissionHelper.java
+++ b/service/java/com/android/server/healthconnect/permission/HealthConnectPermissionHelper.java
@@ -163,7 +163,7 @@
     }
 
     /** See {@link HealthConnectManager#revokeAllHealthPermissions}. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public void revokeAllHealthPermissions(
             @NonNull String packageName, @Nullable String reason, @NonNull UserHandle user) {
         Objects.requireNonNull(packageName);
@@ -269,7 +269,7 @@
      * throws {@link IllegalAccessException} if health permission is in an incorrect state where
      * first grant time can't be fetched.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public Instant getHealthDataStartDateAccessOrThrow(String packageName, UserHandle user) {
         Instant startDateAccess = getHealthDataStartDateAccess(packageName, user);
diff --git a/service/java/com/android/server/healthconnect/permission/HealthPermissionIntentAppsTracker.java b/service/java/com/android/server/healthconnect/permission/HealthPermissionIntentAppsTracker.java
index 7f11fc9..cdc0938 100644
--- a/service/java/com/android/server/healthconnect/permission/HealthPermissionIntentAppsTracker.java
+++ b/service/java/com/android/server/healthconnect/permission/HealthPermissionIntentAppsTracker.java
@@ -160,7 +160,7 @@
                 userHandle);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private String extractPackageName(ResolveInfo info) {
         if (info == null
                 || info.activityInfo == null
diff --git a/service/java/com/android/server/healthconnect/permission/PackageInfoUtils.java b/service/java/com/android/server/healthconnect/permission/PackageInfoUtils.java
index ff3adcc..f25cbc4 100644
--- a/service/java/com/android/server/healthconnect/permission/PackageInfoUtils.java
+++ b/service/java/com/android/server/healthconnect/permission/PackageInfoUtils.java
@@ -42,7 +42,7 @@
 public class PackageInfoUtils {
     private static final String TAG = "HealthConnectPackageInfoUtils";
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile PackageInfoUtils sPackageInfoUtils;
 
     /**
@@ -93,7 +93,7 @@
         return healthAppsInfos;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     boolean hasGrantedHealthPermissions(
             @NonNull String[] packageNames, @NonNull UserHandle user, @NonNull Context context) {
         for (String packageName : packageNames) {
@@ -153,7 +153,7 @@
 
     @Nullable
     String getSharedUserNameFromUid(int uid, Context context) {
-        @SuppressWarnings("NullAway")
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         String[] packages =
                 mUsersPackageManager
                         .get(UserHandle.getUserHandleForUid(uid))
@@ -185,7 +185,7 @@
 
     @Nullable
     String[] getPackageNamesForUid(int uid) {
-        @SuppressWarnings("NullAway")
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         String[] packages =
                 mUsersPackageManager
                         .get(UserHandle.getUserHandleForUid(uid))
diff --git a/service/java/com/android/server/healthconnect/permission/PermissionPackageChangesOrchestrator.java b/service/java/com/android/server/healthconnect/permission/PermissionPackageChangesOrchestrator.java
index 5b6abad..bd22d95 100644
--- a/service/java/com/android/server/healthconnect/permission/PermissionPackageChangesOrchestrator.java
+++ b/service/java/com/android/server/healthconnect/permission/PermissionPackageChangesOrchestrator.java
@@ -150,13 +150,13 @@
         return filter;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private String getPackageName(Intent intent) {
         Uri uri = intent.getData();
         return uri != null ? uri.getSchemeSpecificPart() : null;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private UserHandle getUserHandle(Intent intent) {
         final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
         if (uid >= 0) {
diff --git a/service/java/com/android/server/healthconnect/storage/TransactionManager.java b/service/java/com/android/server/healthconnect/storage/TransactionManager.java
index 3504241..e5de8f9 100644
--- a/service/java/com/android/server/healthconnect/storage/TransactionManager.java
+++ b/service/java/com/android/server/healthconnect/storage/TransactionManager.java
@@ -16,10 +16,10 @@
 
 package com.android.server.healthconnect.storage;
 
-import static android.health.connect.Constants.DEFAULT_LONG;
 import static android.health.connect.Constants.DEFAULT_PAGE_SIZE;
 import static android.health.connect.Constants.PARENT_KEY;
 import static android.health.connect.HealthConnectException.ERROR_INTERNAL;
+import static android.health.connect.PageTokenWrapper.EMPTY_PAGE_TOKEN;
 
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.healthconnect.storage.datatypehelpers.RecordHelper.APP_INFO_ID_COLUMN_NAME;
@@ -38,6 +38,7 @@
 import android.database.sqlite.SQLiteException;
 import android.health.connect.Constants;
 import android.health.connect.HealthConnectException;
+import android.health.connect.PageTokenWrapper;
 import android.health.connect.internal.datatypes.RecordInternal;
 import android.os.UserHandle;
 import android.util.Pair;
@@ -79,7 +80,7 @@
     private static final ConcurrentHashMap<UserHandle, HealthConnectDatabase>
             mUserHandleToDatabaseMap = new ConcurrentHashMap<>();
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile TransactionManager sTransactionManager;
 
     private volatile HealthConnectDatabase mHealthConnectDatabase;
@@ -190,7 +191,7 @@
      *
      * @param request a delete request.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public int deleteAll(@NonNull DeleteTransactionRequest request) throws SQLiteException {
         final SQLiteDatabase db = getWritableDb();
         db.beginTransaction();
@@ -298,7 +299,7 @@
      * @return Pair containing records list read {@link RecordInternal} from the table and a page
      *     token for pagination.
      */
-    public Pair<List<RecordInternal<?>>, Long> readRecordsAndPageToken(
+    public Pair<List<RecordInternal<?>>, PageTokenWrapper> readRecordsAndPageToken(
             @NonNull ReadTransactionRequest request) throws SQLiteException {
         // TODO(b/308158714): Make this build time check once we have different classes.
         checkArgument(
@@ -310,12 +311,12 @@
         requireNonNull(helper);
         if (!helper.isRecordOperationsEnabled()) {
             recordInternalList = new ArrayList<>(0);
-            return Pair.create(recordInternalList, DEFAULT_LONG);
+            return Pair.create(recordInternalList, EMPTY_PAGE_TOKEN);
         }
 
-        long pageToken;
+        PageTokenWrapper pageToken;
         try (Cursor cursor = read(readTableRequest)) {
-            Pair<List<RecordInternal<?>>, Long> readResult =
+            Pair<List<RecordInternal<?>>, PageTokenWrapper> readResult =
                     helper.getNextInternalRecordsPageAndToken(
                             cursor,
                             request.getPageSize().orElse(DEFAULT_PAGE_SIZE),
@@ -771,7 +772,7 @@
     }
 
     /** Clear the static instance held in memory, so unit tests can perform correctly. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @VisibleForTesting
     public static void clearInstance() {
         sTransactionManager = null;
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/AccessLogsHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/AccessLogsHelper.java
index 1177860..4b310ab 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/AccessLogsHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/AccessLogsHelper.java
@@ -60,7 +60,7 @@
     private static final int NUM_COLS = 5;
     private static final int DEFAULT_ACCESS_LOG_TIME_PERIOD_IN_DAYS = 7;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile AccessLogsHelper sAccessLogsHelper;
 
     private AccessLogsHelper() {}
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ActiveCaloriesBurnedRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ActiveCaloriesBurnedRecordHelper.java
index def4de8..1bb74b6 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ActiveCaloriesBurnedRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ActiveCaloriesBurnedRecordHelper.java
@@ -52,7 +52,7 @@
         super(RecordTypeIdentifier.RECORD_TYPE_ACTIVE_CALORIES_BURNED);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType, double aggregation) {
@@ -72,7 +72,7 @@
         return ACTIVE_CALORIES_BURNED_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ActivityDateHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ActivityDateHelper.java
index 47f8647..78994ae 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ActivityDateHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ActivityDateHelper.java
@@ -57,7 +57,7 @@
     private static final String EPOCH_DAYS_COLUMN_NAME = "epoch_days";
     private static final String RECORD_TYPE_ID_COLUMN_NAME = "record_type_id";
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile ActivityDateHelper sActivityDateHelper;
 
     private ActivityDateHelper() {}
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/AppInfoHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/AppInfoHelper.java
index 8f2ccbe..2d62d93 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/AppInfoHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/AppInfoHelper.java
@@ -92,7 +92,7 @@
     private static final String RECORD_TYPES_USED_COLUMN_NAME = "record_types_used";
     private static final int COMPRESS_FACTOR = 100;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile AppInfoHelper sAppInfoHelper;
 
     /**
@@ -110,10 +110,10 @@
      */
     private volatile ConcurrentHashMap<String, AppInfoInternal> mAppInfoMap;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private AppInfoHelper() {}
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public synchronized void clearCache() {
         mAppInfoMap = null;
@@ -176,7 +176,7 @@
             boolean onlyUpdate) {
         if (!isAppInstalled(context, packageName)) {
             // using pre-existing value of recordTypesUsed.
-            @SuppressWarnings("NullAway")
+            @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
             var recordTypesUsed =
                     containsAppInfo(packageName)
                             ? mAppInfoMap.get(packageName).getRecordTypesUsed()
@@ -240,7 +240,7 @@
     }
 
     /** Gets the package name corresponding to the {@code packageId}. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public String getPackageName(long packageId) {
         return getIdPackageNameMap().get(packageId);
@@ -431,7 +431,7 @@
      * This method updates recordTypesUsed for all packages and hence is a heavy operation. This
      * method is used during AutoDeleteService and is run once per day.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @SuppressLint("LongLogTag")
     private synchronized void syncAppInfoMapRecordTypesUsed(
             @NonNull Map<Integer, HashSet<String>> recordTypeToContributingPackagesMap) {
@@ -488,7 +488,7 @@
      * Checks and deletes record types in app info table for which the package is no longer
      * contributing data. This is done after delete records operation has been performed.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @SuppressLint("LongLogTag")
     private synchronized void deleteRecordTypesForPackagesIfRequiredInternal(
             Set<Integer> recordTypesToBeDeleted,
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/BasalMetabolicRateRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/BasalMetabolicRateRecordHelper.java
index 5c86731..7e43636 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/BasalMetabolicRateRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/BasalMetabolicRateRecordHelper.java
@@ -53,7 +53,7 @@
         super(RecordTypeIdentifier.RECORD_TYPE_BASAL_METABOLIC_RATE);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType, double result) {
@@ -72,7 +72,7 @@
         return BASAL_METABOLIC_RATE_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/BloodPressureRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/BloodPressureRecordHelper.java
index 6824898..e560a88 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/BloodPressureRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/BloodPressureRecordHelper.java
@@ -90,7 +90,7 @@
         contentValues.put(BODY_POSITION_COLUMN_NAME, bloodPressureRecord.getBodyPosition());
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -112,7 +112,7 @@
         return new AggregateResult<>(aggregateValue).setZoneOffset(getZoneOffset(results));
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         List<String> columnNames;
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ChangeLogsHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ChangeLogsHelper.java
index 003fe5d..c087b23 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ChangeLogsHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ChangeLogsHelper.java
@@ -71,7 +71,7 @@
     private static final String TIME_COLUMN_NAME = "time";
     private static final int NUM_COLS = 5;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile ChangeLogsHelper sChangeLogsHelper;
 
     private ChangeLogsHelper() {}
@@ -154,7 +154,7 @@
         return TransactionManager.getInitialisedInstance().getLastRowIdFor(TABLE_NAME);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private int addChangeLogs(Cursor cursor, Map<Integer, ChangeLogs> changeLogs) {
         @RecordTypeIdentifier.RecordType
         int recordType = getCursorInt(cursor, RECORD_TYPE_COLUMN_NAME);
@@ -252,7 +252,7 @@
          *     or delete.
          * @param timeStamp Time when the change log is added.
          */
-        @SuppressWarnings("NullAway")
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         public ChangeLogs(@OperationType.OperationTypes int operationType, long timeStamp) {
             mOperationType = operationType;
             mChangeLogTimeStamp = timeStamp;
@@ -284,7 +284,7 @@
         }
 
         /** Function to add an uuid corresponding to given pair of @recordType and @appId */
-        @SuppressWarnings("NullAway")
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         public void addUUID(
                 @RecordTypeIdentifier.RecordType int recordType,
                 @NonNull long appId,
@@ -322,7 +322,7 @@
         }
 
         /** Adds {@code uuids} to {@link ChangeLogs}. */
-        @SuppressWarnings("NullAway")
+        @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
         public ChangeLogs addUUIDs(
                 @RecordTypeIdentifier.RecordType int recordType,
                 @NonNull long appId,
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ChangeLogsRequestHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ChangeLogsRequestHelper.java
index c23a3a9..9fa4c22 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ChangeLogsRequestHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ChangeLogsRequestHelper.java
@@ -65,7 +65,7 @@
     private static final String ROW_ID_CHANGE_LOGS_TABLE_COLUMN_NAME = "row_id_change_logs_table";
     private static final String TIME_COLUMN_NAME = "time";
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile ChangeLogsRequestHelper sChangeLogsRequestHelper;
 
     private ChangeLogsRequestHelper() {}
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/CyclingPedalingCadenceRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/CyclingPedalingCadenceRecordHelper.java
index 1e83acd..4b41c70 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/CyclingPedalingCadenceRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/CyclingPedalingCadenceRecordHelper.java
@@ -113,7 +113,7 @@
         contentValues.put(EPOCH_MILLIS_COLUMN_NAME, cyclingPedalingCadenceRecord.getEpochMillis());
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public final AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -130,7 +130,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     final AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/DeviceInfoHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/DeviceInfoHelper.java
index 64a0c0b..ccb019a 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/DeviceInfoHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/DeviceInfoHelper.java
@@ -56,15 +56,15 @@
     private static final String MODEL_COLUMN_NAME = "model";
     private static final String DEVICE_TYPE_COLUMN_NAME = "device_type";
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile DeviceInfoHelper sDeviceInfoHelper;
 
     /** Map to store deviceInfoId -> DeviceInfo mapping for populating record for read */
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private volatile ConcurrentHashMap<Long, DeviceInfo> mIdDeviceInfoMap;
 
     /** ArrayMap to store DeviceInfo -> rowId mapping (model,manufacturer,device_type -> rowId) */
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private volatile ConcurrentHashMap<DeviceInfo, Long> mDeviceInfoMap;
 
     /**
@@ -108,7 +108,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public synchronized void clearCache() {
         mDeviceInfoMap = null;
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/DistanceRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/DistanceRecordHelper.java
index 0c56dd8..51da08b 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/DistanceRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/DistanceRecordHelper.java
@@ -53,7 +53,7 @@
         return DISTANCE_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ElevationGainedRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ElevationGainedRecordHelper.java
index 8479dae..8e107ad 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ElevationGainedRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ElevationGainedRecordHelper.java
@@ -56,7 +56,7 @@
         return ELEVATION_GAINED_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ExerciseSessionRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ExerciseSessionRecordHelper.java
index a8827d6..c76625d 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/ExerciseSessionRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/ExerciseSessionRecordHelper.java
@@ -137,7 +137,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         List<String> sessionColumns = new ArrayList<>(super.getPriorityAggregationColumnNames());
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/FloorsClimbedRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/FloorsClimbedRecordHelper.java
index d3ebfa8..9b0ea64 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/FloorsClimbedRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/FloorsClimbedRecordHelper.java
@@ -55,7 +55,7 @@
         return FLOORS_CLIMBED_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/HealthDataCategoryPriorityHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/HealthDataCategoryPriorityHelper.java
index 7bf06f3..1ff4c9e 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/HealthDataCategoryPriorityHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/HealthDataCategoryPriorityHelper.java
@@ -77,7 +77,7 @@
 
     public static final String INACTIVE_APPS_ADDED = "inactive_apps_added";
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile HealthDataCategoryPriorityHelper sHealthDataCategoryPriorityHelper;
 
     /**
@@ -86,7 +86,7 @@
      */
     private volatile ConcurrentHashMap<Integer, List<Long>> mHealthDataCategoryToAppIdPriorityMap;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private HealthDataCategoryPriorityHelper() {}
 
     /**
@@ -105,7 +105,7 @@
      * <p>Inactive apps are added at the bottom of the priority list even if they are the default
      * app.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public synchronized void appendToPriorityList(
             @NonNull String packageName,
             @HealthDataCategory.Type int dataCategory,
@@ -174,7 +174,7 @@
      * <p>If the new aggregation source control flag is off, apps that don't have write permissions
      * are removed regardless of whether they hold data in that category.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public synchronized void updateHealthDataPriority(
             @NonNull String[] packageNames, @NonNull UserHandle user, @NonNull Context context) {
         Objects.requireNonNull(packageNames);
@@ -291,7 +291,7 @@
         super.clearData(transactionManager);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public synchronized void clearCache() {
         mHealthDataCategoryToAppIdPriorityMap = null;
@@ -507,7 +507,7 @@
      * aggregation source control, the packages are not removed if they still have data in these
      * categories.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private synchronized void maybeRemoveAppsFromPriorityList(
             Map<Integer, Set<Long>> dataCategoryToAppIdsWithoutPermissions) {
         for (int dataCategory : dataCategoryToAppIdsWithoutPermissions.keySet()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/HeartRateRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/HeartRateRecordHelper.java
index 8520c70..bed31cc 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/HeartRateRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/HeartRateRecordHelper.java
@@ -63,7 +63,7 @@
         super(RecordTypeIdentifier.RECORD_TYPE_HEART_RATE);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public final AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -86,7 +86,7 @@
         return TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     final AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/HeightRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/HeightRecordHelper.java
index 491f994..d8b34a2 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/HeightRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/HeightRecordHelper.java
@@ -51,7 +51,7 @@
         super(RecordTypeIdentifier.RECORD_TYPE_HEIGHT);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -74,7 +74,7 @@
         return HEIGHT_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         List<String> columnNames;
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/HydrationRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/HydrationRecordHelper.java
index d71af9c..4701ce3 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/HydrationRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/HydrationRecordHelper.java
@@ -47,7 +47,7 @@
         super(RecordTypeIdentifier.RECORD_TYPE_HYDRATION);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -74,7 +74,7 @@
         hydrationRecord.setVolume(getCursorDouble(cursor, VOLUME_COLUMN_NAME));
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/InstantRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/InstantRecordHelper.java
index 0615d03..b5da577 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/InstantRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/InstantRecordHelper.java
@@ -150,7 +150,7 @@
         return ZONE_OFFSET_COLUMN_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     final ZoneOffset getZoneOffset(Cursor cursor) {
         ZoneOffset zoneOffset = null;
         if (cursor.getCount() > 0 && cursor.getColumnIndex(ZONE_OFFSET_COLUMN_NAME) != -1) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/IntervalRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/IntervalRecordHelper.java
index 9f4a9f1..2f0ec73 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/IntervalRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/IntervalRecordHelper.java
@@ -136,7 +136,7 @@
         return LOCAL_DATE_COLUMN_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     final ZoneOffset getZoneOffset(Cursor cursor) {
         ZoneOffset zoneOffset = null;
         if (cursor.getCount() > 0 && cursor.getColumnIndex(START_ZONE_OFFSET_COLUMN_NAME) != -1) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/MergeDataHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/MergeDataHelper.java
index 8b65949..82a405b 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/MergeDataHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/MergeDataHelper.java
@@ -104,7 +104,7 @@
 
     private final boolean mUseLocalTime;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public MergeDataHelper(
             @NonNull Cursor cursor,
             @NonNull List<Long> priorityList,
@@ -250,7 +250,7 @@
         return emptyIntervals;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private TreeSet<RecordData> eliminateEarliestRecordOverlaps(TreeSet<RecordData> bufferWindow) {
         RecordData firstBufferData = bufferWindow.pollFirst();
         if (firstBufferData == null) {
@@ -321,7 +321,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private RecordData getRecordData(Cursor cursor) {
         if (cursor != null) {
             double factor = 1;
@@ -364,7 +364,7 @@
      * the data column value based on a multiplying factor calculated for the duration of
      * non-overlapping time interval. This data will be added to form a new buffer window.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private RecordData trimRecordData(RecordData data, Instant startTime, Instant endTime) {
         if (startTime.isAfter(data.getEndTime())) {
             // throw new IllegalArgumentException("startTime must be before data.endTime to trim.");
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/MigrationEntityHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/MigrationEntityHelper.java
index b445918..f10ffbc 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/MigrationEntityHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/MigrationEntityHelper.java
@@ -50,7 +50,7 @@
     private static final Object sGetInstanceLock = new Object();
     private static final int DB_VERSION_TABLE_CREATED = 3;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile MigrationEntityHelper sInstance;
 
     private MigrationEntityHelper() {}
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/NutritionRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/NutritionRecordHelper.java
index eeb081c..599d5f1 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/NutritionRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/NutritionRecordHelper.java
@@ -136,7 +136,7 @@
         super(RecordTypeIdentifier.RECORD_TYPE_NUTRITION);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -288,7 +288,7 @@
         return NUTRITION_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         List<String> columnNames;
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/PowerRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/PowerRecordHelper.java
index 6fb60d7..78f8f22 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/PowerRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/PowerRecordHelper.java
@@ -61,7 +61,7 @@
         super(RecordTypeIdentifier.RECORD_TYPE_POWER);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public final AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -83,7 +83,7 @@
         return TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     final AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/PreferenceHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/PreferenceHelper.java
index 83beb70..16a16fa 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/PreferenceHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/PreferenceHelper.java
@@ -53,12 +53,12 @@
             Collections.singletonList(new Pair<>(KEY_COLUMN_NAME, TYPE_STRING));
     private static final String VALUE_COLUMN_NAME = "value";
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile PreferenceHelper sPreferenceHelper;
 
     protected volatile ConcurrentHashMap<String, String> mPreferences;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     protected PreferenceHelper() {}
 
     /** Note: Overrides existing preference (if it exists) with the new value */
@@ -102,7 +102,7 @@
         return getPreferences().get(key);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public synchronized void clearCache() {
         mPreferences = null;
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/RecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/RecordHelper.java
index a21fbd8..13be24f 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/RecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/RecordHelper.java
@@ -20,6 +20,7 @@
 import static android.health.connect.Constants.DEFAULT_LONG;
 import static android.health.connect.Constants.MAXIMUM_ALLOWED_CURSOR_COUNT;
 import static android.health.connect.Constants.MAXIMUM_PAGE_SIZE;
+import static android.health.connect.PageTokenWrapper.EMPTY_PAGE_TOKEN;
 
 import static com.android.server.healthconnect.storage.datatypehelpers.IntervalRecordHelper.END_TIME_COLUMN_NAME;
 import static com.android.server.healthconnect.storage.request.ReadTransactionRequest.TYPE_NOT_PRESENT_PACKAGE_NAME;
@@ -42,6 +43,7 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.health.connect.AggregateResult;
+import android.health.connect.PageTokenWrapper;
 import android.health.connect.aidl.ReadRecordsRequestParcel;
 import android.health.connect.aidl.RecordIdFiltersParcel;
 import android.health.connect.datatypes.AggregationType;
@@ -61,8 +63,6 @@
 import com.android.server.healthconnect.storage.request.ReadTableRequest;
 import com.android.server.healthconnect.storage.request.UpsertTableRequest;
 import com.android.server.healthconnect.storage.utils.OrderByClause;
-import com.android.server.healthconnect.storage.utils.PageTokenUtil;
-import com.android.server.healthconnect.storage.utils.PageTokenWrapper;
 import com.android.server.healthconnect.storage.utils.SqlJoin;
 import com.android.server.healthconnect.storage.utils.StorageUtils;
 import com.android.server.healthconnect.storage.utils.WhereClauses;
@@ -190,7 +190,7 @@
      *
      * @return {@link AggregateResult} for {@link AggregationType}
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public AggregateResult<?> getAggregateResult(
             Cursor cursor, AggregationType<?> aggregationType) {
         return null;
@@ -202,7 +202,7 @@
      *
      * @return {@link AggregateResult} for {@link AggregationType}
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType, double total) {
         return null;
@@ -211,7 +211,7 @@
     /**
      * Used to calculate and get aggregate results for data types that support derived aggregates
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public double[] deriveAggregate(Cursor cursor, AggregateTableRequest request) {
         return null;
     }
@@ -236,7 +236,7 @@
     }
 
     /** Gets {@link UpsertTableRequest} from {@code recordInternal}. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public UpsertTableRequest getUpsertTableRequest(RecordInternal<?> recordInternal) {
         return getUpsertTableRequest(recordInternal, null);
     }
@@ -451,7 +451,7 @@
      *
      * @see #getNextInternalRecordsPageAndToken(Cursor, int, PageTokenWrapper, Map)
      */
-    public Pair<List<RecordInternal<?>>, Long> getNextInternalRecordsPageAndToken(
+    public Pair<List<RecordInternal<?>>, PageTokenWrapper> getNextInternalRecordsPageAndToken(
             Cursor cursor, int requestSize, PageTokenWrapper pageToken) {
         return getNextInternalRecordsPageAndToken(
                 cursor, requestSize, pageToken, /* packageNamesByAppIds= */ null);
@@ -479,7 +479,7 @@
      *
      * @see #getLimitSize(ReadRecordsRequestParcel)
      */
-    public Pair<List<RecordInternal<?>>, Long> getNextInternalRecordsPageAndToken(
+    public Pair<List<RecordInternal<?>>, PageTokenWrapper> getNextInternalRecordsPageAndToken(
             Cursor cursor,
             int requestSize,
             PageTokenWrapper prevPageToken,
@@ -510,7 +510,7 @@
         currentStartTime = DEFAULT_LONG;
         int offset = 0;
         List<RecordInternal<?>> recordInternalList = new ArrayList<>();
-        long nextToken = DEFAULT_LONG;
+        PageTokenWrapper nextPageToken = EMPTY_PAGE_TOKEN;
         while (cursor.moveToNext()) {
             prevStartTime = currentStartTime;
             currentStartTime = getCursorLong(cursor, getStartTimeColumnName());
@@ -519,9 +519,8 @@
             }
 
             if (recordInternalList.size() >= requestSize) {
-                PageTokenWrapper nextPageToken =
+                nextPageToken =
                         PageTokenWrapper.of(prevPageToken.isAscending(), currentStartTime, offset);
-                nextToken = PageTokenUtil.encode(nextPageToken);
                 break;
             } else {
                 T record = getRecord(cursor, packageNamesByAppIds);
@@ -531,13 +530,13 @@
         }
 
         Trace.traceEnd(TRACE_TAG_RECORD_HELPER);
-        return Pair.create(recordInternalList, nextToken);
+        return Pair.create(recordInternalList, nextPageToken);
     }
 
     @SuppressWarnings("unchecked") // uncheck cast to T
     private T getRecord(Cursor cursor, @Nullable Map<Long, String> packageNamesByAppIds) {
         try {
-            @SuppressWarnings("NullAway")
+            @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
             T record =
                     (T)
                             RecordMapper.getInstance()
@@ -612,12 +611,12 @@
 
     public abstract String getLocalStartTimeColumnName();
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public String getLocalEndTimeColumnName() {
         return null;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public String getEndTimeColumnName() {
         return null;
     }
@@ -639,7 +638,7 @@
     abstract String getMainTableName();
 
     /** Returns the information required to perform aggregate operation. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         return null;
     }
@@ -672,7 +671,7 @@
         return Collections.emptyList();
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     SqlJoin getJoinForReadRequest() {
         return null;
     }
@@ -685,7 +684,7 @@
         //      available to return for the next read.
         if (request.getRecordIdFiltersParcel() == null) {
             int pageOffset =
-                    PageTokenUtil.decode(request.getPageToken(), request.isAscending()).offset();
+                    PageTokenWrapper.from(request.getPageToken(), request.isAscending()).offset();
             return request.getPageSize() + pageOffset + 1;
         } else {
             return MAXIMUM_PAGE_SIZE;
@@ -720,7 +719,7 @@
 
             // page token filter
             PageTokenWrapper pageToken =
-                    PageTokenUtil.decode(request.getPageToken(), request.isAscending());
+                    PageTokenWrapper.from(request.getPageToken(), request.isAscending());
             if (pageToken.isTimestampSet()) {
                 long timestamp = pageToken.timeMillis();
                 if (pageToken.isAscending()) {
@@ -806,7 +805,7 @@
             return new OrderByClause();
         }
         PageTokenWrapper pageToken =
-                PageTokenUtil.decode(request.getPageToken(), request.isAscending());
+                PageTokenWrapper.from(request.getPageToken(), request.isAscending());
         return new OrderByClause()
                 .addOrderByClause(getStartTimeColumnName(), pageToken.isAscending())
                 .addOrderByClause(PRIMARY_COLUMN_NAME, /* isAscending= */ true);
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/RestingHeartRateRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/RestingHeartRateRecordHelper.java
index 88e77df..0edd77a 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/RestingHeartRateRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/RestingHeartRateRecordHelper.java
@@ -52,7 +52,7 @@
         super(RecordTypeIdentifier.RECORD_TYPE_RESTING_HEART_RATE);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -76,7 +76,7 @@
         return RESTING_HEART_RATE_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         List<String> columnNames;
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/SleepSessionRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/SleepSessionRecordHelper.java
index 260d0ee..8886418 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/SleepSessionRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/SleepSessionRecordHelper.java
@@ -69,7 +69,7 @@
         return SLEEP_SESSION_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         if (aggregateRequest.getAggregationTypeIdentifier() == SLEEP_SESSION_DURATION_TOTAL) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/SpeedRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/SpeedRecordHelper.java
index be9f135..69e803b 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/SpeedRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/SpeedRecordHelper.java
@@ -99,7 +99,7 @@
         record.setSamples(speedRecordSampleSet);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -115,7 +115,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/StepsCadenceRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/StepsCadenceRecordHelper.java
index 1f2f9c2..22b486b 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/StepsCadenceRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/StepsCadenceRecordHelper.java
@@ -107,7 +107,7 @@
         contentValues.put(EPOCH_MILLIS_COLUMN_NAME, stepsCadenceRecord.getEpochMillis());
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -123,7 +123,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/StepsRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/StepsRecordHelper.java
index 35dd08c..6296f1b 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/StepsRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/StepsRecordHelper.java
@@ -56,7 +56,7 @@
         return STEPS_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/TotalCaloriesBurnedRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/TotalCaloriesBurnedRecordHelper.java
index acc8005..e836538 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/TotalCaloriesBurnedRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/TotalCaloriesBurnedRecordHelper.java
@@ -59,7 +59,7 @@
         super(RecordTypeIdentifier.RECORD_TYPE_TOTAL_CALORIES_BURNED);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType, double aggregation) {
@@ -79,7 +79,7 @@
         return TOTAL_CALORIES_BURNED_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/WeightRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/WeightRecordHelper.java
index b8cfbdd..f01f2b9 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/WeightRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/WeightRecordHelper.java
@@ -50,7 +50,7 @@
         super(RecordTypeIdentifier.RECORD_TYPE_WEIGHT);
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     public AggregateResult<?> getAggregateResult(
             Cursor results, AggregationType<?> aggregationType) {
@@ -73,7 +73,7 @@
         return WEIGHT_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         List<String> columnNames;
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/WheelchairPushesRecordHelper.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/WheelchairPushesRecordHelper.java
index 867b2f9..ae07819 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/WheelchairPushesRecordHelper.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/WheelchairPushesRecordHelper.java
@@ -56,7 +56,7 @@
         return WHEELCHAIR_PUSHES_RECORD_TABLE_NAME;
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @Override
     AggregateParams getAggregateParams(AggregationType<?> aggregateRequest) {
         switch (aggregateRequest.getAggregationTypeIdentifier()) {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/AggregationRecordData.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/AggregationRecordData.java
index f075280..e0dd2a0 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/AggregationRecordData.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/AggregationRecordData.java
@@ -45,7 +45,7 @@
     private int mPriority;
     private long mLastModifiedTime;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private ZoneOffset mStartTimeZoneOffset;
 
     long getStartTime() {
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/AggregationTimestamp.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/AggregationTimestamp.java
index 96f7da2..716bfb0 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/AggregationTimestamp.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/AggregationTimestamp.java
@@ -41,7 +41,7 @@
     private final long mTime;
     private AggregationRecordData mParentRecord;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public AggregationTimestamp(int type, long time) {
         mTime = time;
         mType = type;
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/PriorityRecordsAggregator.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/PriorityRecordsAggregator.java
index c4a31a7..5e7e725 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/PriorityRecordsAggregator.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/PriorityRecordsAggregator.java
@@ -210,13 +210,13 @@
     }
 
     /** Returns result for the given group */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public Double getResultForGroup(Integer groupNumber) {
         return mGroupToAggregationResult.get(groupNumber);
     }
 
     /** Returns start time zone offset for the given group */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public ZoneOffset getZoneOffsetForGroup(Integer groupNumber) {
         return mGroupToFirstZoneOffset.get(groupNumber);
     }
diff --git a/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/SessionDurationAggregationData.java b/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/SessionDurationAggregationData.java
index a5db3f1..3d1d3a0 100644
--- a/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/SessionDurationAggregationData.java
+++ b/service/java/com/android/server/healthconnect/storage/datatypehelpers/aggregation/SessionDurationAggregationData.java
@@ -44,7 +44,7 @@
     List<Long> mExcludeStarts;
     List<Long> mExcludeEnds;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public SessionDurationAggregationData(
             String excludeIntervalStartTimeColumn, String excludeIntervalEndTimeColumn) {
         mExcludeIntervalStartTimeColumn = excludeIntervalStartTimeColumn;
diff --git a/service/java/com/android/server/healthconnect/storage/request/AggregateParams.java b/service/java/com/android/server/healthconnect/storage/request/AggregateParams.java
index 926284f..d4cc169 100644
--- a/service/java/com/android/server/healthconnect/storage/request/AggregateParams.java
+++ b/service/java/com/android/server/healthconnect/storage/request/AggregateParams.java
@@ -43,19 +43,19 @@
 
     // Additional column used for time filtering. End time for interval records,
     // null for other records.
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private String mExtraTimeColumnName = null;
 
     private String mTimeOffsetColumnName;
 
     private PriorityAggregationExtraParams mPriorityAggregationExtraParams;
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public AggregateParams(String tableName, List<String> columnsToFetch) {
         this(tableName, columnsToFetch, null);
     }
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public AggregateParams(
             String tableName, List<String> columnsToFetch, Class<?> priorityColumnDataType) {
         mTableName = tableName;
@@ -151,14 +151,14 @@
         private String mExcludeIntervalEndColumnName;
         private String mExcludeIntervalStartColumnName;
 
-        @SuppressWarnings("NullAway.Init")
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         public PriorityAggregationExtraParams(
                 String excludeIntervalStartColumnName, String excludeIntervalEndColumnName) {
             mExcludeIntervalStartColumnName = excludeIntervalStartColumnName;
             mExcludeIntervalEndColumnName = excludeIntervalEndColumnName;
         }
 
-        @SuppressWarnings("NullAway.Init")
+        @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
         public PriorityAggregationExtraParams(
                 String columnToAggregateName, Class<?> aggregationType) {
             mColumnToAggregateName = columnToAggregateName;
diff --git a/service/java/com/android/server/healthconnect/storage/request/AggregateTableRequest.java b/service/java/com/android/server/healthconnect/storage/request/AggregateTableRequest.java
index a1e7b46..2626eeb 100644
--- a/service/java/com/android/server/healthconnect/storage/request/AggregateTableRequest.java
+++ b/service/java/com/android/server/healthconnect/storage/request/AggregateTableRequest.java
@@ -87,7 +87,7 @@
     private final boolean mUseLocalTime;
     private List<Long> mTimeSplits;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public AggregateTableRequest(
             AggregateParams params,
             AggregationType<?> aggregationType,
@@ -285,7 +285,7 @@
         }
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private static String getSqlCommandFor(@AggregationType.AggregateOperationType int type) {
         return switch (type) {
             case MAX -> "MAX";
@@ -341,7 +341,7 @@
         return builder.toString();
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private void updateResultWithDataOriginPackageNames(Cursor metaDataCursor) {
         List<Long> packageIds = new ArrayList<>();
         while (metaDataCursor.moveToNext()) {
diff --git a/service/java/com/android/server/healthconnect/storage/request/DeleteTableRequest.java b/service/java/com/android/server/healthconnect/storage/request/DeleteTableRequest.java
index ace744d..d740941 100644
--- a/service/java/com/android/server/healthconnect/storage/request/DeleteTableRequest.java
+++ b/service/java/com/android/server/healthconnect/storage/request/DeleteTableRequest.java
@@ -60,7 +60,7 @@
     private WhereClauses mCustomWhereClauses;
     private long mLessThanOrEqualValue;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public DeleteTableRequest(
             @NonNull String tableName, @RecordTypeIdentifier.RecordType int recordType) {
         Objects.requireNonNull(tableName);
@@ -69,7 +69,7 @@
         mRecordType = recordType;
     }
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public DeleteTableRequest(@NonNull String tableName) {
         Objects.requireNonNull(tableName);
 
diff --git a/service/java/com/android/server/healthconnect/storage/request/DeleteTransactionRequest.java b/service/java/com/android/server/healthconnect/storage/request/DeleteTransactionRequest.java
index 6515bf1..0355acc 100644
--- a/service/java/com/android/server/healthconnect/storage/request/DeleteTransactionRequest.java
+++ b/service/java/com/android/server/healthconnect/storage/request/DeleteTransactionRequest.java
@@ -51,7 +51,7 @@
     private ChangeLogsHelper.ChangeLogs mChangeLogs;
     private boolean mHasHealthDataManagementPermission;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public DeleteTransactionRequest(String packageName, DeleteUsingFiltersRequestParcel request) {
         Objects.requireNonNull(packageName);
         mDeleteTableRequests = new ArrayList<>(request.getRecordTypeFilters().size());
diff --git a/service/java/com/android/server/healthconnect/storage/request/ReadTableRequest.java b/service/java/com/android/server/healthconnect/storage/request/ReadTableRequest.java
index 4ad5575..66c19d6 100644
--- a/service/java/com/android/server/healthconnect/storage/request/ReadTableRequest.java
+++ b/service/java/com/android/server/healthconnect/storage/request/ReadTableRequest.java
@@ -55,7 +55,7 @@
     private List<ReadTableRequest> mExtraReadRequests;
     private List<ReadTableRequest> mUnionReadRequests;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public ReadTableRequest(@NonNull String tableName) {
         Objects.requireNonNull(tableName);
 
@@ -185,7 +185,7 @@
     }
 
     /** Sets union read requests. */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public ReadTableRequest setUnionReadRequests(
             @Nullable List<ReadTableRequest> unionReadRequests) {
         mUnionReadRequests = unionReadRequests;
diff --git a/service/java/com/android/server/healthconnect/storage/request/ReadTransactionRequest.java b/service/java/com/android/server/healthconnect/storage/request/ReadTransactionRequest.java
index a38413d..a928e15 100644
--- a/service/java/com/android/server/healthconnect/storage/request/ReadTransactionRequest.java
+++ b/service/java/com/android/server/healthconnect/storage/request/ReadTransactionRequest.java
@@ -20,11 +20,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.health.connect.PageTokenWrapper;
 import android.health.connect.aidl.ReadRecordsRequestParcel;
 
 import com.android.server.healthconnect.storage.datatypehelpers.RecordHelper;
-import com.android.server.healthconnect.storage.utils.PageTokenUtil;
-import com.android.server.healthconnect.storage.utils.PageTokenWrapper;
 import com.android.server.healthconnect.storage.utils.RecordHelperProvider;
 
 import java.util.ArrayList;
@@ -67,7 +66,7 @@
                                 startDateAccessMillis,
                                 extraPermsState));
         if (request.getRecordIdFiltersParcel() == null) {
-            mPageToken = PageTokenUtil.decode(request.getPageToken(), request.isAscending());
+            mPageToken = PageTokenWrapper.from(request.getPageToken(), request.isAscending());
             mPageSize = request.getPageSize();
         } else {
             mPageSize = DEFAULT_INT;
diff --git a/service/java/com/android/server/healthconnect/storage/request/UpsertTableRequest.java b/service/java/com/android/server/healthconnect/storage/request/UpsertTableRequest.java
index cc7ad35..92ba971 100644
--- a/service/java/com/android/server/healthconnect/storage/request/UpsertTableRequest.java
+++ b/service/java/com/android/server/healthconnect/storage/request/UpsertTableRequest.java
@@ -64,7 +64,7 @@
         this(table, contentValues, Collections.emptyList());
     }
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public UpsertTableRequest(
             @NonNull String table,
             @NonNull ContentValues contentValues,
@@ -92,7 +92,7 @@
      * Use this if you want to add row_id of the parent table to all the child entries in {@code
      * parentCol}
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     @NonNull
     public UpsertTableRequest setParentColumnForChildTables(@Nullable String parentCol) {
         mParentCol = parentCol;
diff --git a/service/java/com/android/server/healthconnect/storage/request/UpsertTransactionRequest.java b/service/java/com/android/server/healthconnect/storage/request/UpsertTransactionRequest.java
index caaa4fe..856feb2 100644
--- a/service/java/com/android/server/healthconnect/storage/request/UpsertTransactionRequest.java
+++ b/service/java/com/android/server/healthconnect/storage/request/UpsertTransactionRequest.java
@@ -97,7 +97,7 @@
                 Collections.emptyMap());
     }
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public UpsertTransactionRequest(
             @Nullable String packageName,
             @NonNull List<RecordInternal<?>> recordInternals,
diff --git a/service/java/com/android/server/healthconnect/storage/utils/OrderByClause.java b/service/java/com/android/server/healthconnect/storage/utils/OrderByClause.java
index 36aafbc..3cc74b0 100644
--- a/service/java/com/android/server/healthconnect/storage/utils/OrderByClause.java
+++ b/service/java/com/android/server/healthconnect/storage/utils/OrderByClause.java
@@ -23,7 +23,7 @@
 
 /** @hide */
 public final class OrderByClause {
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     List<Pair<String, Boolean>> mOrderList;
 
     /**
diff --git a/service/java/com/android/server/healthconnect/storage/utils/PageTokenUtil.java b/service/java/com/android/server/healthconnect/storage/utils/PageTokenUtil.java
deleted file mode 100644
index 8221461..0000000
--- a/service/java/com/android/server/healthconnect/storage/utils/PageTokenUtil.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.healthconnect.storage.utils;
-
-import static android.health.connect.Constants.DEFAULT_LONG;
-
-import static com.android.internal.util.Preconditions.checkArgument;
-
-/**
- * A util class handles encoding and decoding page token.
- *
- * @hide
- */
-// TODO(b/296846629): Move this util to under framework/, so we can use this on client side, and use
-//  this in {@link ReadRecordsRequestUsingFilters}
-public final class PageTokenUtil {
-    static final long MAX_ALLOWED_TIME_MILLIS = (1L << 44) - 1;
-    static final long MAX_ALLOWED_OFFSET = (1 << 18) - 1;
-
-    private static final int OFFSET_START_BIT = 45;
-    private static final int TIMESTAMP_START_BIT = 1;
-
-    /**
-     * Encodes a {@link PageTokenWrapper} to page token.
-     *
-     * <p>Page token is structured as following from right (least significant bit) to left (most
-     * significant bit):
-     * <li>Least significant bit: 0 = isAscending true, 1 = isAscending false
-     * <li>Next 44 bits: timestamp, represents epoch time millis
-     * <li>Next 18 bits: offset, represents number of records processed in the previous page
-     * <li>Sign bit: not used for encoding, page token is a signed long
-     */
-    public static long encode(PageTokenWrapper wrapper) {
-        return ((long) wrapper.offset() << OFFSET_START_BIT)
-                | (wrapper.timeMillis() << TIMESTAMP_START_BIT)
-                | (wrapper.isAscending() ? 0 : 1);
-    }
-
-    /**
-     * Decodes a {@code pageToken} to {@link PageTokenWrapper}.
-     *
-     * <p>When {@code pageToken} is not set, in which case we can not get {@code isAscending} from
-     * the token, it falls back to {@code defaultIsAscending}.
-     *
-     * <p>{@code pageToken} must be a non-negative long number (except for using the sentinel value
-     * {@code DEFAULT_LONG}, whose current value is {@code -1}, which represents page token not set)
-     */
-    public static PageTokenWrapper decode(long pageToken, boolean defaultIsAscending) {
-        if (pageToken == DEFAULT_LONG) {
-            return PageTokenWrapper.ofAscending(defaultIsAscending);
-        }
-        checkArgument(pageToken >= 0, "pageToken cannot be negative");
-        return PageTokenWrapper.of(
-                getIsAscending(pageToken), getTimestamp(pageToken), getOffset(pageToken));
-    }
-
-    /**
-     * Take the least significant bit in the given {@code pageToken} to retrieve isAscending
-     * information.
-     *
-     * <p>If the last bit of the token is 1, isAscending is false; otherwise isAscending is true.
-     */
-    private static boolean getIsAscending(long pageToken) {
-        return (pageToken & 1) == 0;
-    }
-
-    /** Shifts bits in the given {@code pageToken} to retrieve timestamp information. */
-    private static long getTimestamp(long pageToken) {
-        long mask = MAX_ALLOWED_TIME_MILLIS << TIMESTAMP_START_BIT;
-        return (pageToken & mask) >> TIMESTAMP_START_BIT;
-    }
-
-    /** Shifts bits in the given {@code pageToken} to retrieve offset information. */
-    private static int getOffset(long pageToken) {
-        return (int) (pageToken >> OFFSET_START_BIT);
-    }
-
-    private PageTokenUtil() {}
-}
diff --git a/service/java/com/android/server/healthconnect/storage/utils/PageTokenWrapper.java b/service/java/com/android/server/healthconnect/storage/utils/PageTokenWrapper.java
deleted file mode 100644
index 812d63f..0000000
--- a/service/java/com/android/server/healthconnect/storage/utils/PageTokenWrapper.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.healthconnect.storage.utils;
-
-import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.server.healthconnect.storage.utils.PageTokenUtil.MAX_ALLOWED_OFFSET;
-import static com.android.server.healthconnect.storage.utils.PageTokenUtil.MAX_ALLOWED_TIME_MILLIS;
-
-import static java.lang.Integer.min;
-
-import java.util.Objects;
-
-/**
- * A wrapper object contains information encoded in the {@code long} page token.
- *
- * @hide
- */
-public final class PageTokenWrapper {
-    private final boolean mIsAscending;
-    private final long mTimeMillis;
-    private final int mOffset;
-    private final boolean mIsTimestampSet;
-
-    /** isAscending stored in the page token. */
-    public boolean isAscending() {
-        return mIsAscending;
-    }
-
-    /** Timestamp stored in the page token. */
-    public long timeMillis() {
-        return mTimeMillis;
-    }
-
-    /** Offset stored in the page token. */
-    public int offset() {
-        return mOffset;
-    }
-
-    /** Whether or not the timestamp is set. */
-    public boolean isTimestampSet() {
-        return mIsTimestampSet;
-    }
-
-    /**
-     * Both {@code timeMillis} and {@code offset} have to be non-negative; {@code timeMillis} cannot
-     * exceed 2^44-1.
-     *
-     * <p>Note that due to space constraints, {@code offset} cannot exceed 2^18-1 (262143). If the
-     * {@code offset} parameter exceeds the maximum allowed value, it'll fallback to the max value.
-     *
-     * <p>More details see go/hc-page-token
-     */
-    public static PageTokenWrapper of(boolean isAscending, long timeMillis, int offset) {
-        checkArgument(timeMillis >= 0, "timestamp can not be negative");
-        checkArgument(timeMillis <= MAX_ALLOWED_TIME_MILLIS, "timestamp too large");
-        checkArgument(offset >= 0, "offset can not be negative");
-        int boundedOffset = min((int) MAX_ALLOWED_OFFSET, offset);
-        return new PageTokenWrapper(
-                isAscending, timeMillis, boundedOffset, /* isTimestampSet= */ true);
-    }
-
-    /**
-     * Generate a page token that contains only {@code isAscending} information. Timestamp and
-     * offset are not set.
-     */
-    public static PageTokenWrapper ofAscending(boolean isAscending) {
-        return new PageTokenWrapper(
-                isAscending, /* timeMillis= */ 0, /* offset= */ 0, /* isTimestampSet= */ false);
-    }
-
-    @Override
-    public String toString() {
-        return "PageTokenWrapper{"
-                + "isAscending = "
-                + mIsAscending
-                + ", timeMillis = "
-                + mTimeMillis
-                + ", offset = "
-                + mOffset
-                + "}";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof PageTokenWrapper that)) return false;
-        return mIsAscending == that.mIsAscending
-                && mTimeMillis == that.mTimeMillis
-                && mOffset == that.mOffset;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mIsAscending, mOffset, mTimeMillis);
-    }
-
-    private PageTokenWrapper(
-            boolean isAscending, long timeMillis, int offset, boolean isTimestampSet) {
-        this.mIsAscending = isAscending;
-        this.mTimeMillis = timeMillis;
-        this.mOffset = offset;
-        this.mIsTimestampSet = isTimestampSet;
-    }
-}
diff --git a/service/java/com/android/server/healthconnect/storage/utils/RecordHelperProvider.java b/service/java/com/android/server/healthconnect/storage/utils/RecordHelperProvider.java
index fa2f16c..802d23d 100644
--- a/service/java/com/android/server/healthconnect/storage/utils/RecordHelperProvider.java
+++ b/service/java/com/android/server/healthconnect/storage/utils/RecordHelperProvider.java
@@ -71,7 +71,7 @@
  * @hide
  */
 public final class RecordHelperProvider {
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     private static volatile RecordHelperProvider sRecordHelperProvider;
 
     private final Map<Integer, RecordHelper<?>> mRecordIDToHelperMap;
diff --git a/service/java/com/android/server/healthconnect/storage/utils/SqlJoin.java b/service/java/com/android/server/healthconnect/storage/utils/SqlJoin.java
index 3ef7cba..10fa3a8 100644
--- a/service/java/com/android/server/healthconnect/storage/utils/SqlJoin.java
+++ b/service/java/com/android/server/healthconnect/storage/utils/SqlJoin.java
@@ -53,10 +53,10 @@
     private List<SqlJoin> mAttachedJoins;
     private String mJoinType = SQL_JOIN_INNER;
 
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     private WhereClauses mTableToJoinWhereClause = null;
 
-    @SuppressWarnings("NullAway.Init")
+    @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
     public SqlJoin(
             String selfTableName,
             String tableNameToJoinOn,
diff --git a/service/java/com/android/server/healthconnect/storage/utils/StorageUtils.java b/service/java/com/android/server/healthconnect/storage/utils/StorageUtils.java
index 8d15dca..f5b1248 100644
--- a/service/java/com/android/server/healthconnect/storage/utils/StorageUtils.java
+++ b/service/java/com/android/server/healthconnect/storage/utils/StorageUtils.java
@@ -263,7 +263,7 @@
     /**
      * Reads ZoneOffset using given cursor. Returns null of column name is not present in the table.
      */
-    @SuppressWarnings("NullAway")
+    @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
     public static ZoneOffset getZoneOffset(Cursor cursor, String startZoneOffsetColumnName) {
         ZoneOffset zoneOffset = null;
         if (cursor.getColumnIndex(startZoneOffsetColumnName) != -1) {
diff --git a/tests/cts/Android.bp b/tests/cts/Android.bp
index e0b35de..712094e 100644
--- a/tests/cts/Android.bp
+++ b/tests/cts/Android.bp
@@ -36,6 +36,7 @@
         "cts",
         "general-tests",
         "mts-healthfitness",
+        "mcts-healthfitness",
     ],
     static_libs: [
         "androidx.test.rules",
@@ -72,6 +73,7 @@
         "cts",
         "general-tests",
         "mts-healthfitness",
+        "mcts-healthfitness",
     ],
     static_libs: [
         "androidx.test.rules",
@@ -106,6 +108,7 @@
         "cts",
         "general-tests",
         "mts-healthfitness",
+        "mcts-healthfitness",
     ],
     static_libs: [
         "androidx.test.rules",
@@ -142,6 +145,7 @@
         "cts",
         "general-tests",
         "mts-healthfitness",
+        "mcts-healthfitness",
     ],
     static_libs: [
         "androidx.test.rules",
@@ -183,6 +187,7 @@
         "cts",
         "general-tests",
         "mts-healthfitness",
+        "mcts-healthfitness",
     ],
     static_libs: [
         "androidx.test.rules",
diff --git a/tests/cts/hostsidetests/healthconnect/Android.bp b/tests/cts/hostsidetests/healthconnect/Android.bp
index a276766..2db19a2 100644
--- a/tests/cts/hostsidetests/healthconnect/Android.bp
+++ b/tests/cts/hostsidetests/healthconnect/Android.bp
@@ -37,46 +37,41 @@
 android_test_helper_app {
     name: "CtsHealthConnectTestAppAWithNormalReadWritePermission",
     manifest: "HealthConnectTestHelper/CtsHealthConnectTestAppAWithNormalReadWritePermission.xml",
-    static_libs: ["cts-healthconnect-lib"],
+    static_libs: ["cts-healthconnect-lib", "cts-healthconnect-test-helper"],
     sdk_version: "test_current",
     min_sdk_version: "34",
-    srcs: ["HealthConnectTestHelper/src/**/*.java"],
 }
 
 android_test_helper_app {
     name: "CtsHealthConnectTestAppBWithNormalReadWritePermission",
     manifest: "HealthConnectTestHelper/CtsHealthConnectTestAppBWithNormalReadWritePermission.xml",
-    static_libs: ["cts-healthconnect-lib"],
+    static_libs: ["cts-healthconnect-lib", "cts-healthconnect-test-helper"],
     sdk_version: "test_current",
     min_sdk_version: "34",
-    srcs: ["HealthConnectTestHelper/src/**/*.java"],
 }
 
 android_test_helper_app {
     name: "CtsHealthConnectTestAppWithDataManagePermission",
     manifest: "HealthConnectTestHelper/CtsHealthConnectTestAppWithDataManagePermission.xml",
-    static_libs: ["cts-healthconnect-lib"],
+    static_libs: ["cts-healthconnect-lib", "cts-healthconnect-test-helper"],
     sdk_version: "test_current",
     min_sdk_version: "34",
-    srcs: ["HealthConnectTestHelper/src/**/*.java"],
 }
 
 android_test_helper_app {
     name: "CtsHealthConnectTestAppWithNoPermission",
     manifest: "HealthConnectTestHelper/CtsHealthConnectTestAppWithNoPermission.xml",
-    static_libs: ["cts-healthconnect-lib"],
+    static_libs: ["cts-healthconnect-lib", "cts-healthconnect-test-helper"],
     sdk_version: "test_current",
     min_sdk_version: "34",
-    srcs: ["HealthConnectTestHelper/src/**/*.java"],
 }
 
 android_test_helper_app {
     name: "CtsHealthConnectTestAppWithWritePermissionsOnly",
     manifest: "HealthConnectTestHelper/CtsHealthConnectTestAppWithWritePermissionsOnly.xml",
-    static_libs: ["cts-healthconnect-lib"],
+    static_libs: ["cts-healthconnect-lib", "cts-healthconnect-test-helper"],
     sdk_version: "test_current",
     min_sdk_version: "34",
-    srcs: ["HealthConnectTestHelper/src/**/*.java"],
 }
 
 android_test {
@@ -103,6 +98,7 @@
         "cts",
         "general-tests",
         "mts-healthfitness",
+        "mcts-healthfitness",
     ],
     sdk_version: "test_current",
     min_sdk_version: "34",
@@ -132,6 +128,7 @@
         "cts",
         "general-tests",
         "mts-healthfitness",
+        "mcts-healthfitness",
     ],
     libs: [
         "compatibility-host-util",
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/Android.bp b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/Android.bp
new file mode 100644
index 0000000..5d9e252
--- /dev/null
+++ b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "cts-healthconnect-test-helper",
+    srcs: [
+          "src/**/*.java",
+          "src/**/*.kt"
+    ],
+    static_libs: [
+                 "androidx.appcompat_appcompat",
+                 "androidx.test.rules",
+                  "cts-install-lib",
+                  "platform-test-annotations",
+                  "cts-healthconnect-utils",
+                  "cts-healthconnect-lib",
+    ],
+    sdk_version: "test_current"
+}
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestApp.xml b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestApp.xml
index d9f87ec..f6df452 100644
--- a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestApp.xml
+++ b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestApp.xml
@@ -36,7 +36,7 @@
     <application android:label="CtsHealthConnectTestApp">
         <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="android.healthconnect.cts.testhelper.HealthConnectTestHelper"
+        <activity android:name="android.healthconnect.cts.testhelper.TestAppActivity"
                   android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
@@ -50,6 +50,9 @@
                 <action android:name="any.action"/>
             </intent-filter>
         </activity>
+
+        <receiver android:name="android.healthconnect.cts.testhelper.TestAppReceiver"
+            android:exported="true"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppAWithNormalReadWritePermission.xml b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppAWithNormalReadWritePermission.xml
index 2ee841e..d63e110 100644
--- a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppAWithNormalReadWritePermission.xml
+++ b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppAWithNormalReadWritePermission.xml
@@ -96,7 +96,7 @@
 
 
     <application android:label="CtsHealthConnectTestAppAWithNormalReadWritePermission">
-        <activity android:name="android.healthconnect.cts.testhelper.HealthConnectTestHelper"
+        <activity android:name="android.healthconnect.cts.testhelper.TestAppActivity"
                   android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -107,5 +107,7 @@
             </intent-filter>
         </activity>
 
+        <receiver android:name="android.healthconnect.cts.testhelper.TestAppReceiver"
+            android:exported="true"/>
     </application>
 </manifest>
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppBWithNormalReadWritePermission.xml b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppBWithNormalReadWritePermission.xml
index 36f9a2f..6a03c40 100644
--- a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppBWithNormalReadWritePermission.xml
+++ b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppBWithNormalReadWritePermission.xml
@@ -96,7 +96,7 @@
 
 
     <application android:label="CtsHealthConnectTestAppBWithNormalReadWritePermission">
-        <activity android:name="android.healthconnect.cts.testhelper.HealthConnectTestHelper"
+        <activity android:name="android.healthconnect.cts.testhelper.TestAppActivity"
                   android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -107,5 +107,7 @@
             </intent-filter>
         </activity>
 
+        <receiver android:name="android.healthconnect.cts.testhelper.TestAppReceiver"
+            android:exported="true"/>
     </application>
 </manifest>
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithDataManagePermission.xml b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithDataManagePermission.xml
index 8edc831..ca88551 100644
--- a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithDataManagePermission.xml
+++ b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithDataManagePermission.xml
@@ -24,7 +24,7 @@
 
 
     <application android:label="CtsHealthConnectTestAppWithDataManagePermission">
-        <activity android:name="android.healthconnect.cts.testhelper.HealthConnectTestHelper"
+        <activity android:name="android.healthconnect.cts.testhelper.TestAppActivity"
                   android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -35,5 +35,7 @@
             </intent-filter>
         </activity>
 
+        <receiver android:name="android.healthconnect.cts.testhelper.TestAppReceiver"
+            android:exported="true"/>
     </application>
 </manifest>
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithNoPermission.xml b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithNoPermission.xml
index a201ee9..ec4ae5c 100644
--- a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithNoPermission.xml
+++ b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithNoPermission.xml
@@ -21,7 +21,7 @@
           android:versionName="1.0">
 
     <application android:label="CtsHealthConnectTestAppWithNoPermission">
-        <activity android:name="android.healthconnect.cts.testhelper.HealthConnectTestHelper"
+        <activity android:name="android.healthconnect.cts.testhelper.TestAppActivity"
                   android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -32,5 +32,7 @@
             </intent-filter>
         </activity>
 
+        <receiver android:name="android.healthconnect.cts.testhelper.TestAppReceiver"
+            android:exported="true"/>
     </application>
 </manifest>
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithWritePermissionsOnly.xml b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithWritePermissionsOnly.xml
index 945ae4e..adb6933 100644
--- a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithWritePermissionsOnly.xml
+++ b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/CtsHealthConnectTestAppWithWritePermissionsOnly.xml
@@ -60,7 +60,7 @@
 
 
     <application android:label="CtsHealthConnectTestAppWithWritePermissionsOnly">
-        <activity android:name="android.healthconnect.cts.testhelper.HealthConnectTestHelper"
+        <activity android:name="android.healthconnect.cts.testhelper.TestAppActivity"
                   android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -71,5 +71,7 @@
             </intent-filter>
         </activity>
 
+        <receiver android:name="android.healthconnect.cts.testhelper.TestAppReceiver"
+            android:exported="true"/>
     </application>
 </manifest>
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/HealthConnectTestHelper.java b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/HealthConnectTestHelper.java
deleted file mode 100644
index 07d4a75..0000000
--- a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/HealthConnectTestHelper.java
+++ /dev/null
@@ -1,663 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.healthconnect.cts.testhelper;
-
-import static android.healthconnect.cts.lib.MultiAppTestUtils.APP_PKG_NAME_USED_IN_DATA_ORIGIN;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.CHANGE_LOGS_RESPONSE;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.CHANGE_LOG_TOKEN;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.CLIENT_ID;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.DATA_ORIGIN_FILTER_PACKAGE_NAMES;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.DELETE_RECORDS_QUERY;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.END_TIME;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.EXERCISE_SESSION;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.GET_CHANGE_LOG_TOKEN_QUERY;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.INSERT_RECORD_QUERY;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.INTENT_EXCEPTION;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.PAUSE_END;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.PAUSE_START;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.QUERY_TYPE;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.READ_CHANGE_LOGS_QUERY;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.READ_RECORDS_QUERY;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.READ_RECORDS_SIZE;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.READ_RECORD_CLASS_NAME;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.READ_USING_DATA_ORIGIN_FILTERS;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.RECORD_IDS;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.RECORD_TYPE;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.START_TIME;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.STEPS_COUNT;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.STEPS_RECORD;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.SUCCESS;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.UPDATE_EXERCISE_ROUTE;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.UPDATE_RECORDS_QUERY;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.UPSERT_EXERCISE_ROUTE;
-import static android.healthconnect.cts.utils.TestUtils.buildExerciseSession;
-import static android.healthconnect.cts.utils.TestUtils.buildStepsRecord;
-import static android.healthconnect.cts.utils.TestUtils.getChangeLogs;
-import static android.healthconnect.cts.utils.TestUtils.getExerciseSessionRecord;
-import static android.healthconnect.cts.utils.TestUtils.getTestRecords;
-import static android.healthconnect.cts.utils.TestUtils.insertRecords;
-import static android.healthconnect.cts.utils.TestUtils.insertRecordsAndGetIds;
-import static android.healthconnect.cts.utils.TestUtils.readRecords;
-import static android.healthconnect.cts.utils.TestUtils.updateRecords;
-import static android.healthconnect.cts.utils.TestUtils.verifyDeleteRecords;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.health.connect.ReadRecordsRequestUsingFilters;
-import android.health.connect.RecordIdFilter;
-import android.health.connect.changelog.ChangeLogTokenRequest;
-import android.health.connect.changelog.ChangeLogTokenResponse;
-import android.health.connect.changelog.ChangeLogsRequest;
-import android.health.connect.changelog.ChangeLogsResponse;
-import android.health.connect.datatypes.DataOrigin;
-import android.health.connect.datatypes.ExerciseSessionRecord;
-import android.health.connect.datatypes.Record;
-import android.health.connect.datatypes.StepsRecord;
-import android.healthconnect.cts.utils.TestUtils;
-import android.os.Bundle;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class HealthConnectTestHelper extends Activity {
-    private static final String TAG = "HealthConnectTestHelper";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        final Context context = getApplicationContext();
-        Bundle bundle = getIntent().getExtras();
-        String queryType = bundle.getString(QUERY_TYPE);
-        Intent returnIntent;
-        try {
-            switch (queryType) {
-                case INSERT_RECORD_QUERY:
-                    if (bundle.containsKey(APP_PKG_NAME_USED_IN_DATA_ORIGIN)) {
-                        returnIntent =
-                                insertRecordsWithDifferentPkgName(
-                                        queryType,
-                                        bundle.getString(APP_PKG_NAME_USED_IN_DATA_ORIGIN),
-                                        context);
-                        break;
-                    }
-                    if (bundle.containsKey(CLIENT_ID)) {
-                        returnIntent =
-                                insertRecordsWithGivenClientId(
-                                        queryType, bundle.getDouble(CLIENT_ID), context);
-                        break;
-                    }
-                    if (bundle.containsKey(RECORD_TYPE)) {
-                        if (bundle.getString(RECORD_TYPE).equals(STEPS_RECORD)) {
-                            returnIntent =
-                                    insertStepsRecord(
-                                            queryType,
-                                            bundle.getString(START_TIME),
-                                            bundle.getString(END_TIME),
-                                            bundle.getInt(STEPS_COUNT),
-                                            context);
-                            break;
-                        } else if (bundle.getString(RECORD_TYPE).equals(EXERCISE_SESSION)) {
-                            returnIntent =
-                                    insertExerciseSession(
-                                            queryType,
-                                            bundle.getString(START_TIME),
-                                            bundle.getString(END_TIME),
-                                            bundle.getString(PAUSE_START),
-                                            bundle.getString(PAUSE_END),
-                                            context);
-                            break;
-                        }
-                    }
-                    returnIntent = insertRecord(queryType, context);
-                    break;
-                case DELETE_RECORDS_QUERY:
-                    returnIntent =
-                            deleteRecords(
-                                    queryType,
-                                    (List<TestUtils.RecordTypeAndRecordIds>)
-                                            bundle.getSerializable(RECORD_IDS),
-                                    context);
-                    break;
-                case UPDATE_EXERCISE_ROUTE:
-                    returnIntent = updateRouteAs(queryType, context);
-                    break;
-                case UPSERT_EXERCISE_ROUTE:
-                    returnIntent = upsertRouteAs(queryType, context);
-                    break;
-                case UPDATE_RECORDS_QUERY:
-                    returnIntent =
-                            updateRecordsAs(
-                                    queryType,
-                                    (List<TestUtils.RecordTypeAndRecordIds>)
-                                            bundle.getSerializable(RECORD_IDS),
-                                    context);
-                    break;
-                case READ_RECORDS_QUERY:
-                    if (bundle.containsKey(READ_USING_DATA_ORIGIN_FILTERS)) {
-                        List<String> dataOriginPackageNames =
-                                bundle.containsKey(DATA_ORIGIN_FILTER_PACKAGE_NAMES)
-                                        ?
-                                        // if a set of data origin filters is specified, use that
-                                        bundle.getStringArrayList(DATA_ORIGIN_FILTER_PACKAGE_NAMES)
-                                        :
-                                        // otherwise default to this app's package name
-                                        List.of(context.getPackageName());
-                        returnIntent =
-                                readRecordsUsingDataOriginFilters(
-                                        queryType,
-                                        bundle.getStringArrayList(READ_RECORD_CLASS_NAME),
-                                        dataOriginPackageNames,
-                                        context);
-                        break;
-                    }
-                    returnIntent =
-                            readRecordsAs(
-                                    queryType,
-                                    bundle.getStringArrayList(READ_RECORD_CLASS_NAME),
-                                    context);
-                    break;
-                case READ_CHANGE_LOGS_QUERY:
-                    returnIntent =
-                            readChangeLogsUsingDataOriginFilters(
-                                    queryType, bundle.getString(CHANGE_LOG_TOKEN), context);
-                    break;
-                case GET_CHANGE_LOG_TOKEN_QUERY:
-                    if (bundle.containsKey(READ_RECORD_CLASS_NAME)) {
-                        returnIntent =
-                                getChangeLogToken(
-                                        queryType,
-                                        bundle.getString(APP_PKG_NAME_USED_IN_DATA_ORIGIN),
-                                        bundle.getStringArrayList(READ_RECORD_CLASS_NAME),
-                                        context);
-                        break;
-                    }
-                    returnIntent =
-                            getChangeLogToken(
-                                    queryType,
-                                    bundle.getString(APP_PKG_NAME_USED_IN_DATA_ORIGIN),
-                                    context);
-                    break;
-                default:
-                    throw new IllegalStateException(
-                            "Unknown query received from launcher app: " + queryType);
-            }
-        } catch (Exception e) {
-            returnIntent = new Intent(queryType);
-            returnIntent.putExtra(INTENT_EXCEPTION, e);
-        }
-
-        sendBroadcast(returnIntent);
-        this.finish();
-    }
-
-    /**
-     * Method to get test records, insert them, and put the list of recordId and recordClass in the
-     * intent
-     *
-     * @param queryType - specifies the action, here it should be INSERT_RECORDS_QUERY
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     */
-    private Intent insertRecord(String queryType, Context context) {
-        List<Record> records = getTestRecords(context.getPackageName());
-        final Intent intent = new Intent(queryType);
-        try {
-            List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                    insertRecordsAndGetIds(records, context);
-            intent.putExtra(RECORD_IDS, (Serializable) listOfRecordIdsAndClass);
-            intent.putExtra(SUCCESS, true);
-        } catch (Exception e) {
-            intent.putExtra(SUCCESS, false);
-            intent.putExtra(INTENT_EXCEPTION, e);
-        }
-
-        return intent;
-    }
-
-    /**
-     * Method to delete the records and put the Exception in the intent if deleting records throws
-     * an exception
-     *
-     * @param queryType - specifies the action, here it should be DELETE_RECORDS_QUERY
-     * @param listOfRecordIdsAndClassName - list of recordId and recordClass of records to be
-     *     deleted
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     * @throws ClassNotFoundException if a record category class is not found for any class name
-     *     present in the list @listOfRecordIdsAndClassName
-     */
-    private Intent deleteRecords(
-            String queryType,
-            List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClassName,
-            Context context)
-            throws ClassNotFoundException {
-        final Intent intent = new Intent(queryType);
-
-        List<RecordIdFilter> recordIdFilters = new ArrayList<>();
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds :
-                listOfRecordIdsAndClassName) {
-            for (String recordId : recordTypeAndRecordIds.getRecordIds()) {
-                recordIdFilters.add(
-                        RecordIdFilter.fromId(
-                                (Class<? extends Record>)
-                                        Class.forName(recordTypeAndRecordIds.getRecordType()),
-                                recordId));
-            }
-        }
-        try {
-            verifyDeleteRecords(recordIdFilters, context);
-            intent.putExtra(SUCCESS, true);
-        } catch (Exception e) {
-            intent.putExtra(INTENT_EXCEPTION, e);
-        }
-        return intent;
-    }
-
-    /**
-     * Method to update the records and put the exception in the intent if updating the records
-     * throws an exception
-     *
-     * @param queryType - specifies the action, here it should be UPDATE_RECORDS_QUERY
-     * @param listOfRecordIdsAndClassName - list of recordId and recordClass of records to be
-     *     updated
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     */
-    private Intent updateRecordsAs(
-            String queryType,
-            List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClassName,
-            Context context) {
-        final Intent intent = new Intent(queryType);
-
-        try {
-            for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds :
-                    listOfRecordIdsAndClassName) {
-                List<? extends Record> recordsToBeUpdated =
-                        readRecords(
-                                new ReadRecordsRequestUsingFilters.Builder<>(
-                                                (Class<? extends Record>)
-                                                        Class.forName(
-                                                                recordTypeAndRecordIds
-                                                                        .getRecordType()))
-                                        .build(),
-                                context);
-                updateRecords((List<Record>) recordsToBeUpdated, context);
-            }
-            intent.putExtra(SUCCESS, true);
-        } catch (Exception e) {
-            intent.putExtra(INTENT_EXCEPTION, e);
-            intent.putExtra(SUCCESS, false);
-        }
-
-        return intent;
-    }
-
-    /**
-     * Method to update the session record to the session without route and put the exception in the
-     * intent if updating the record throws an exception
-     *
-     * @param queryType - specifies the action, here it should be UPDATE_RECORDS_QUERY
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     */
-    private Intent updateRouteAs(String queryType, Context context) {
-        final Intent intent = new Intent(queryType);
-        try {
-            ExerciseSessionRecord existingSession =
-                    readRecords(
-                                    new ReadRecordsRequestUsingFilters.Builder<>(
-                                                    ExerciseSessionRecord.class)
-                                            .build(),
-                                    context)
-                            .get(0);
-            updateRecords(
-                    List.of(
-                            getExerciseSessionRecord(
-                                    context.getPackageName(),
-                                    Double.parseDouble(
-                                            existingSession.getMetadata().getClientRecordId()),
-                                    false)),
-                    context);
-            intent.putExtra(SUCCESS, true);
-        } catch (Exception e) {
-            intent.putExtra(INTENT_EXCEPTION, e);
-            intent.putExtra(SUCCESS, false);
-        }
-
-        return intent;
-    }
-
-    /**
-     * Method to upsert the session record to the session without route and put the exception in the
-     * intent if updating the record throws an exception
-     *
-     * @param queryType - specifies the action, here it should be UPDATE_RECORDS_QUERY
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     */
-    private Intent upsertRouteAs(String queryType, Context context) {
-        final Intent intent = new Intent(queryType);
-        try {
-            ExerciseSessionRecord existingSession =
-                    readRecords(
-                                    new ReadRecordsRequestUsingFilters.Builder<>(
-                                                    ExerciseSessionRecord.class)
-                                            .build(),
-                                    context)
-                            .get(0);
-            insertRecords(
-                    List.of(
-                            getExerciseSessionRecord(
-                                    context.getPackageName(),
-                                    Double.parseDouble(
-                                            existingSession.getMetadata().getClientRecordId()),
-                                    false)),
-                    context);
-            intent.putExtra(SUCCESS, true);
-        } catch (Exception e) {
-            intent.putExtra(INTENT_EXCEPTION, e);
-            intent.putExtra(SUCCESS, false);
-        }
-
-        return intent;
-    }
-
-    /**
-     * Method to insert records with different package name in dataOrigin of the record and add the
-     * details in the intent
-     *
-     * @param queryType - specifies the action, here it should be INSERT_RECORDS_QUERY
-     * @param pkgNameUsedInDataOrigin - package name to be added in the dataOrigin of the records
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     * @throws InterruptedException
-     */
-    private Intent insertRecordsWithDifferentPkgName(
-            String queryType, String pkgNameUsedInDataOrigin, Context context)
-            throws InterruptedException {
-        final Intent intent = new Intent(queryType);
-
-        List<Record> recordsToBeInserted = getTestRecords(pkgNameUsedInDataOrigin);
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                insertRecordsAndGetIds(recordsToBeInserted, context);
-
-        intent.putExtra(RECORD_IDS, (Serializable) listOfRecordIdsAndClass);
-        return intent;
-    }
-
-    /**
-     * Method to read records and put the number of records read in the intent or put the exception
-     * in the intent in case reading records throws exception
-     *
-     * @param queryType - specifies the action, here it should be READ_RECORDS_QUERY
-     * @param recordClassesToRead - List of Record Class names for the records to be read
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     */
-    private Intent readRecordsAs(
-            String queryType, ArrayList<String> recordClassesToRead, Context context) {
-        final Intent intent = new Intent(queryType);
-        int recordsSize = 0;
-        try {
-            for (String recordClass : recordClassesToRead) {
-                List<? extends Record> recordsRead =
-                        readRecords(
-                                new ReadRecordsRequestUsingFilters.Builder<>(
-                                                (Class<? extends Record>)
-                                                        Class.forName(recordClass))
-                                        .build(),
-                                context);
-
-                recordsSize += recordsRead.size();
-            }
-            intent.putExtra(SUCCESS, true);
-        } catch (Exception e) {
-            intent.putExtra(INTENT_EXCEPTION, e);
-            intent.putExtra(SUCCESS, false);
-        }
-
-        intent.putExtra(READ_RECORDS_SIZE, recordsSize);
-
-        return intent;
-    }
-
-    /**
-     * Method to insert records with given clientId in their dataOrigin and put SUCCESS as true if
-     * insertion is successfule or SUCCESS as false if insertion throws an exception
-     *
-     * @param queryType - specifies the action, here it should be INSERT_RECORDS_QUERY
-     * @param clientId - clientId to be specified in the dataOrigin of the records to be inserted
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     */
-    private Intent insertRecordsWithGivenClientId(
-            String queryType, double clientId, Context context) {
-        final Intent intent = new Intent(queryType);
-
-        List<Record> records = getTestRecords(context.getPackageName(), clientId);
-
-        try {
-            insertRecords(records, context);
-            intent.putExtra(SUCCESS, true);
-        } catch (Exception e) {
-            intent.putExtra(SUCCESS, false);
-        }
-
-        return intent;
-    }
-
-    /**
-     * Method to read records using data origin filters and add number of records read to the intent
-     *
-     * @param queryType - specifies the action, here it should be READ_RECORDS_QUERY
-     * @param recordClassesToRead - List of Record Class names for the records to be read
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     */
-    private Intent readRecordsUsingDataOriginFilters(
-            String queryType,
-            ArrayList<String> recordClassesToRead,
-            List<String> dataOriginPackageNames,
-            Context context) {
-        final Intent intent = new Intent(queryType);
-
-        int recordsSize = 0;
-        try {
-            for (String recordClass : recordClassesToRead) {
-                ReadRecordsRequestUsingFilters.Builder requestBuilder =
-                        new ReadRecordsRequestUsingFilters.Builder<>(
-                                (Class<? extends Record>) Class.forName(recordClass));
-                dataOriginPackageNames.forEach(
-                        packageName ->
-                                requestBuilder.addDataOrigins(
-                                        new DataOrigin.Builder()
-                                                .setPackageName(packageName)
-                                                .build()));
-                List<? extends Record> recordsRead = readRecords(requestBuilder.build(), context);
-                recordsSize += recordsRead.size();
-            }
-        } catch (Exception e) {
-            intent.putExtra(READ_RECORDS_SIZE, 0);
-            intent.putExtra(INTENT_EXCEPTION, e);
-        }
-
-        intent.putExtra(READ_RECORDS_SIZE, recordsSize);
-
-        return intent;
-    }
-
-    /**
-     * Method to read changeLogs using dataOriginFilters and add the changeLogToken
-     *
-     * @param queryType - specifies the action, here it should be
-     *     READ_CHANGE_LOGS_USING_DATA_ORIGIN_FILTERS_QUERY
-     * @param context - application context
-     * @param changeLogToken - Token corresponding to which changeLogs have to be read
-     * @return Intent to send back to the main app which is running the tests
-     */
-    private Intent readChangeLogsUsingDataOriginFilters(
-            String queryType, String changeLogToken, Context context) {
-        final Intent intent = new Intent(queryType);
-
-        ChangeLogsRequest changeLogsRequest = new ChangeLogsRequest.Builder(changeLogToken).build();
-
-        try {
-            ChangeLogsResponse response = getChangeLogs(changeLogsRequest, context);
-            intent.putExtra(CHANGE_LOGS_RESPONSE, response);
-        } catch (Exception e) {
-            intent.putExtra(INTENT_EXCEPTION, e);
-        }
-
-        return intent;
-    }
-
-    /**
-     * Method to get changeLogToken for an app
-     *
-     * @param queryType - specifies the action, here it should be GET_CHANGE_LOG_TOKEN_QUERY
-     * @param pkgName - pkgName of the app whose changeLogs we have to read using the returned token
-     * @param context - application context
-     * @return - Intent to send back to the main app which is running the tests
-     */
-    private Intent getChangeLogToken(String queryType, String pkgName, Context context)
-            throws Exception {
-        final Intent intent = new Intent(queryType);
-
-        ChangeLogTokenResponse tokenResponse =
-                TestUtils.getChangeLogToken(
-                        new ChangeLogTokenRequest.Builder()
-                                .addDataOriginFilter(
-                                        new DataOrigin.Builder().setPackageName(pkgName).build())
-                                .build(),
-                        context);
-
-        intent.putExtra(CHANGE_LOG_TOKEN, tokenResponse.getToken());
-        return intent;
-    }
-
-    /**
-     * Method to get changeLogToken for an app
-     *
-     * @param queryType - specifies the action, here it should be GET_CHANGE_LOG_TOKEN_QUERY
-     * @param pkgName - pkgName of the app whose changeLogs we have to read using the returned token
-     * @param recordClassesToRead - Record Classes whose changeLogs to be read using the returned
-     *     token
-     * @param context - application context
-     * @return - Intent to send back to the main app which is running the tests
-     */
-    private Intent getChangeLogToken(
-            String queryType,
-            String pkgName,
-            ArrayList<String> recordClassesToRead,
-            Context context)
-            throws Exception {
-        final Intent intent = new Intent(queryType);
-
-        ChangeLogTokenRequest.Builder changeLogTokenRequestBuilder =
-                new ChangeLogTokenRequest.Builder()
-                        .addDataOriginFilter(
-                                new DataOrigin.Builder().setPackageName(pkgName).build());
-        for (String recordClass : recordClassesToRead) {
-            changeLogTokenRequestBuilder.addRecordType(
-                    (Class<? extends Record>) Class.forName(recordClass));
-        }
-        ChangeLogTokenResponse tokenResponse =
-                TestUtils.getChangeLogToken(changeLogTokenRequestBuilder.build(), context);
-
-        intent.putExtra(CHANGE_LOG_TOKEN, tokenResponse.getToken());
-        return intent;
-    }
-
-    /**
-     * Method to build steps record and insert them
-     *
-     * @param queryType - specifies the action, here it should be INSERT_RECORDS_QUERY
-     * @param startTime - start time for the steps record to build
-     * @param endTime - end time for the steps record to build
-     * @param stepsCount - number of steps to be added in the steps record
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     */
-    private Intent insertStepsRecord(
-            String queryType, String startTime, String endTime, int stepsCount, Context context) {
-        final Intent intent = new Intent(queryType);
-        try {
-            List<Record> recordToInsert =
-                    Arrays.asList(
-                            buildStepsRecord(
-                                    startTime, endTime, stepsCount, context.getPackageName()));
-            List<Record> insertedRecords = insertRecords(recordToInsert, context);
-            List<TestUtils.RecordTypeAndRecordIds> recordTypeAndRecordIdsList =
-                    new ArrayList<TestUtils.RecordTypeAndRecordIds>();
-            recordTypeAndRecordIdsList.add(
-                    new TestUtils.RecordTypeAndRecordIds(
-                            StepsRecord.class.getName(),
-                            List.of(insertedRecords.get(0).getMetadata().getId())));
-            intent.putExtra(SUCCESS, true);
-            intent.putExtra(RECORD_IDS, (Serializable) recordTypeAndRecordIdsList);
-        } catch (Exception e) {
-            intent.putExtra(SUCCESS, false);
-        }
-        return intent;
-    }
-
-    /**
-     * Method to build Exercise Session records and insert them
-     *
-     * @param queryType - specifies the action, here it should be INSERT_RECORDS_QUERY
-     * @param sessionStartTime - start time of the exercise session to build
-     * @param sessionEndTime - end time of the exercise session t build
-     * @param pauseStart - start time of the pause segment in the exercise session
-     * @param pauseEnd - end time of the pause segment in the exercise session
-     * @param context - application context
-     * @return Intent to send back to the main app which is running the tests
-     */
-    private Intent insertExerciseSession(
-            String queryType,
-            String sessionStartTime,
-            String sessionEndTime,
-            String pauseStart,
-            String pauseEnd,
-            Context context) {
-        final Intent intent = new Intent(queryType);
-        try {
-            List<Record> recordToInsert;
-            if (pauseStart == null) {
-                recordToInsert =
-                        Arrays.asList(
-                                buildExerciseSession(sessionStartTime, sessionEndTime, context));
-            } else {
-                recordToInsert =
-                        Arrays.asList(
-                                buildExerciseSession(
-                                        sessionStartTime,
-                                        sessionEndTime,
-                                        pauseStart,
-                                        pauseEnd,
-                                        context));
-            }
-            insertRecords(recordToInsert, context);
-            intent.putExtra(SUCCESS, true);
-        } catch (Exception e) {
-            intent.putExtra(SUCCESS, false);
-        }
-        return intent;
-    }
-}
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/TestAppActivity.java b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/TestAppActivity.java
new file mode 100644
index 0000000..d2166a1
--- /dev/null
+++ b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/TestAppActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.healthconnect.cts.testhelper;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Receives requests from test cases and forwards to Health Connect.
+ *
+ * <p>Used for testing HC API calls on behalf of other apps in the foreground.
+ */
+public class TestAppActivity extends Activity {
+    private static final String TAG = TestAppActivity.class.getSimpleName();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+        Intent returnIntent =
+                TestAppHelper.handleRequest(getApplicationContext(), getIntent().getExtras());
+
+        sendBroadcast(returnIntent);
+        finish();
+    }
+}
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/TestAppHelper.java b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/TestAppHelper.java
new file mode 100644
index 0000000..e49aad0
--- /dev/null
+++ b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/TestAppHelper.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.healthconnect.cts.testhelper;
+
+import static android.healthconnect.cts.lib.BundleHelper.DELETE_RECORDS_QUERY;
+import static android.healthconnect.cts.lib.BundleHelper.GET_CHANGE_LOG_TOKEN_QUERY;
+import static android.healthconnect.cts.lib.BundleHelper.INSERT_RECORDS_QUERY;
+import static android.healthconnect.cts.lib.BundleHelper.INTENT_EXCEPTION;
+import static android.healthconnect.cts.lib.BundleHelper.QUERY_TYPE;
+import static android.healthconnect.cts.lib.BundleHelper.READ_CHANGE_LOGS_QUERY;
+import static android.healthconnect.cts.lib.BundleHelper.READ_RECORDS_QUERY;
+import static android.healthconnect.cts.lib.BundleHelper.UPDATE_RECORDS_QUERY;
+
+import android.content.Context;
+import android.content.Intent;
+import android.health.connect.ReadRecordsRequestUsingFilters;
+import android.health.connect.RecordIdFilter;
+import android.health.connect.changelog.ChangeLogTokenRequest;
+import android.health.connect.changelog.ChangeLogTokenResponse;
+import android.health.connect.changelog.ChangeLogsRequest;
+import android.health.connect.changelog.ChangeLogsResponse;
+import android.health.connect.datatypes.Metadata;
+import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.lib.BundleHelper;
+import android.healthconnect.cts.utils.TestUtils;
+import android.os.Bundle;
+
+import java.util.List;
+
+final class TestAppHelper {
+
+    static Intent handleRequest(Context context, Bundle bundle) {
+        String queryType = bundle.getString(QUERY_TYPE);
+        Intent response = new Intent(queryType);
+        try {
+            Bundle responseBundle = handleRequestUnchecked(context, bundle, queryType);
+            response.putExtras(responseBundle);
+        } catch (Exception e) {
+            response.putExtra(INTENT_EXCEPTION, e);
+        }
+        return response;
+    }
+
+    private static Bundle handleRequestUnchecked(Context context, Bundle bundle, String queryType)
+            throws Exception {
+        return switch (queryType) {
+            case INSERT_RECORDS_QUERY -> handleInsertRecords(context, bundle);
+            case DELETE_RECORDS_QUERY -> handleDeleteRecords(context, bundle);
+            case UPDATE_RECORDS_QUERY -> handleUpdateRecords(context, bundle);
+            case READ_RECORDS_QUERY -> handleReadRecords(context, bundle);
+            case READ_CHANGE_LOGS_QUERY -> handleGetChangeLogs(context, bundle);
+            case GET_CHANGE_LOG_TOKEN_QUERY -> handleGetChangeLogToken(context, bundle);
+            default -> throw new IllegalStateException(
+                    "Unknown query received from launcher app: " + queryType);
+        };
+    }
+
+    private static Bundle handleInsertRecords(Context context, Bundle bundle) throws Exception {
+        List<? extends Record> records = BundleHelper.toInsertRecordsRequest(bundle);
+        List<Record> insertedRecords = TestUtils.insertRecords(records, context);
+        List<String> response =
+                insertedRecords.stream().map(Record::getMetadata).map(Metadata::getId).toList();
+        return BundleHelper.fromInsertRecordsResponse(response);
+    }
+
+    private static Bundle handleReadRecords(Context context, Bundle bundle) throws Exception {
+        ReadRecordsRequestUsingFilters<? extends Record> request =
+                BundleHelper.toReadRecordsRequestUsingFilters(bundle);
+        List<? extends Record> records = TestUtils.readRecords(request, context);
+        return BundleHelper.fromReadRecordsResponse(records);
+    }
+
+    private static Bundle handleDeleteRecords(Context context, Bundle bundle) throws Exception {
+        List<RecordIdFilter> recordIdFilters = BundleHelper.toDeleteRecordsByIdsRequest(bundle);
+
+        TestUtils.verifyDeleteRecords(recordIdFilters, context);
+
+        return new Bundle();
+    }
+
+    private static Bundle handleUpdateRecords(Context context, Bundle bundle) throws Exception {
+        List<? extends Record> records = BundleHelper.toUpdateRecordsRequest(bundle);
+        TestUtils.updateRecords(records, context);
+        return new Bundle();
+    }
+
+    private static Bundle handleGetChangeLogToken(Context context, Bundle bundle) throws Exception {
+        ChangeLogTokenRequest request = BundleHelper.toChangeLogTokenRequest(bundle);
+        ChangeLogTokenResponse response = TestUtils.getChangeLogToken(request, context);
+        return BundleHelper.fromChangeLogTokenResponse(response.getToken());
+    }
+
+    private static Bundle handleGetChangeLogs(Context context, Bundle bundle) throws Exception {
+        ChangeLogsRequest request = BundleHelper.toChangeLogsRequest(bundle);
+        ChangeLogsResponse response = TestUtils.getChangeLogs(request, context);
+        return BundleHelper.fromChangeLogsResponse(response);
+    }
+
+    private TestAppHelper() {}
+}
diff --git a/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/TestAppReceiver.java b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/TestAppReceiver.java
new file mode 100644
index 0000000..3f457aa
--- /dev/null
+++ b/tests/cts/hostsidetests/healthconnect/HealthConnectTestHelper/src/android/healthconnect/cts/testhelper/TestAppReceiver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.healthconnect.cts.testhelper;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * Receives requests from test cases and forwards to Health Connect.
+ *
+ * <p>Used for testing HC API calls on behalf of other apps in the background.
+ */
+public class TestAppReceiver extends BroadcastReceiver {
+
+    private static final String TAG = TestAppReceiver.class.getSimpleName();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "onReceive");
+        Intent returnIntent = TestAppHelper.handleRequest(context, intent.getExtras());
+        context.sendBroadcast(returnIntent);
+    }
+}
diff --git a/tests/cts/hostsidetests/healthconnect/device/src/android/healthconnect/cts/device/ExerciseRouteAccessTest.java b/tests/cts/hostsidetests/healthconnect/device/src/android/healthconnect/cts/device/ExerciseRouteAccessTest.java
index fae49f2..a794f64 100644
--- a/tests/cts/hostsidetests/healthconnect/device/src/android/healthconnect/cts/device/ExerciseRouteAccessTest.java
+++ b/tests/cts/hostsidetests/healthconnect/device/src/android/healthconnect/cts/device/ExerciseRouteAccessTest.java
@@ -17,20 +17,16 @@
 package android.healthconnect.cts.device;
 
 import static android.health.connect.HealthPermissions.WRITE_EXERCISE_ROUTE;
-import static android.healthconnect.cts.device.HealthConnectDeviceTest.APP_A_WITH_READ_WRITE_PERMS;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.READ_RECORDS_SIZE;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.RECORD_IDS;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.SUCCESS;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.insertRecordAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.insertSessionNoRouteAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.readRecordsAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.updateRouteAs;
 import static android.healthconnect.cts.utils.TestUtils.READ_EXERCISE_ROUTE_PERMISSION;
 import static android.healthconnect.cts.utils.TestUtils.deleteAllStagedRemoteData;
 import static android.healthconnect.cts.utils.TestUtils.deleteTestData;
 import static android.healthconnect.cts.utils.TestUtils.getChangeLogToken;
 import static android.healthconnect.cts.utils.TestUtils.getChangeLogs;
-import static android.healthconnect.cts.utils.TestUtils.getExerciseSessionRecord;
+import static android.healthconnect.cts.utils.TestUtils.getEmptyMetadata;
+import static android.healthconnect.cts.utils.TestUtils.getExerciseRoute;
+import static android.healthconnect.cts.utils.TestUtils.getLocation;
+import static android.healthconnect.cts.utils.TestUtils.getMetadataForClientId;
+import static android.healthconnect.cts.utils.TestUtils.getMetadataForId;
 import static android.healthconnect.cts.utils.TestUtils.insertRecords;
 import static android.healthconnect.cts.utils.TestUtils.readRecords;
 
@@ -41,6 +37,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
+import static java.time.Duration.ofMinutes;
+
 import android.app.UiAutomation;
 import android.health.connect.HealthConnectException;
 import android.health.connect.ReadRecordsRequestUsingFilters;
@@ -49,19 +49,22 @@
 import android.health.connect.changelog.ChangeLogsRequest;
 import android.health.connect.changelog.ChangeLogsResponse;
 import android.health.connect.datatypes.ExerciseSessionRecord;
-import android.healthconnect.cts.utils.TestUtils.RecordTypeAndRecordIds;
-import android.os.Bundle;
+import android.health.connect.datatypes.ExerciseSessionType;
+import android.health.connect.datatypes.Metadata;
+import android.healthconnect.cts.lib.TestAppProxy;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
+import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.After;
-import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.Collection;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
@@ -69,8 +72,18 @@
 
 public class ExerciseRouteAccessTest {
 
+    private static final TestAppProxy APP_A_WITH_READ_WRITE_PERMS =
+            TestAppProxy.forPackageName("android.healthconnect.cts.testapp.readWritePerms.A");
+    private static final Instant NOW = Instant.now().truncatedTo(ChronoUnit.MILLIS);
+    private static final Instant START_TIME = NOW.minus(ofMinutes(30));
+    private static final Instant END_TIME = NOW;
     private UiAutomation mAutomation;
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() {
         mAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -91,7 +104,8 @@
 
     @Test
     public void readRecords_usingFilters_cannotAccessOtherAppRoute() throws Exception {
-        assertThat(insertRecordAs(APP_A_WITH_READ_WRITE_PERMS).getBoolean(SUCCESS)).isTrue();
+        ExerciseSessionRecord sessionWithRoute = getExerciseSessionWithRoute(getEmptyMetadata());
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(sessionWithRoute);
 
         List<ExerciseSessionRecord> records =
                 readRecords(
@@ -107,7 +121,8 @@
     @Test
     public void readRecords_usingFilters_withReadExerciseRoutePermission_canAccessOtherAppRoute()
             throws Exception {
-        assertThat(insertRecordAs(APP_A_WITH_READ_WRITE_PERMS).getBoolean(SUCCESS)).isTrue();
+        ExerciseSessionRecord sessionWithRoute = getExerciseSessionWithRoute(getEmptyMetadata());
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(sessionWithRoute);
         mAutomation.adoptShellPermissionIdentity(READ_EXERCISE_ROUTE_PERMISSION);
 
         List<ExerciseSessionRecord> records =
@@ -123,10 +138,8 @@
 
     @Test
     public void readRecords_usingFilters_canAccessOwnRoute() throws Exception {
-        ExerciseSessionRecord record =
-                getExerciseSessionRecord(
-                        getApplicationContext().getPackageName(), 0.0, /* withRoute= */ true);
-        insertRecords(List.of(record), getApplicationContext());
+        ExerciseSessionRecord sessionWithRoute = getExerciseSessionWithRoute(getEmptyMetadata());
+        insertRecords(List.of(sessionWithRoute), getApplicationContext());
 
         List<ExerciseSessionRecord> records =
                 readRecords(
@@ -136,17 +149,15 @@
         assertThat(records).isNotNull();
         assertThat(records).hasSize(1);
         assertThat(records.get(0).hasRoute()).isTrue();
-        assertThat(records.get(0).getRoute()).isEqualTo(record.getRoute());
+        assertThat(records.get(0).getRoute()).isEqualTo(sessionWithRoute.getRoute());
     }
 
     @Test
     public void readRecords_usingFilters_mixedOwnAndOtherAppSession() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-        String otherAppSessionId = getInsertedSessionId(bundle);
-        ExerciseSessionRecord ownSession =
-                getExerciseSessionRecord(
-                        getApplicationContext().getPackageName(), 0.0, /* withRoute= */ true);
+        ExerciseSessionRecord otherAppSession = getExerciseSessionWithRoute(getEmptyMetadata());
+        String otherAppSessionId =
+                APP_A_WITH_READ_WRITE_PERMS.insertRecords(otherAppSession).get(0);
+        ExerciseSessionRecord ownSession = getExerciseSessionWithRoute(getEmptyMetadata());
         String ownSessionId =
                 insertRecords(List.of(ownSession), getApplicationContext())
                         .get(0)
@@ -174,9 +185,8 @@
 
     @Test
     public void readRecords_usingIds_cannotAccessOtherAppRoute() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-        String sessionId = getInsertedSessionId(bundle);
+        ExerciseSessionRecord otherAppSession = getExerciseSessionWithRoute(getEmptyMetadata());
+        String sessionId = APP_A_WITH_READ_WRITE_PERMS.insertRecords(otherAppSession).get(0);
 
         List<ExerciseSessionRecord> records =
                 readRecords(
@@ -193,9 +203,8 @@
     @Test
     public void readRecords_usingIds_withReadExerciseRoutePermission_canAccessOtherAppRoute()
             throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-        String sessionId = getInsertedSessionId(bundle);
+        ExerciseSessionRecord otherAppSession = getExerciseSessionWithRoute(getEmptyMetadata());
+        String sessionId = APP_A_WITH_READ_WRITE_PERMS.insertRecords(otherAppSession).get(0);
         mAutomation.adoptShellPermissionIdentity(READ_EXERCISE_ROUTE_PERMISSION);
 
         List<ExerciseSessionRecord> records =
@@ -207,14 +216,12 @@
         assertThat(records).isNotNull();
         assertThat(records).hasSize(1);
         assertThat(records.get(0).hasRoute()).isTrue();
-        assertThat(records.get(0).getRoute()).isNotNull();
+        assertThat(records.get(0).getRoute()).isEqualTo(otherAppSession.getRoute());
     }
 
     @Test
     public void readRecords_usingIds_canAccessOwnRoute() throws Exception {
-        ExerciseSessionRecord record =
-                getExerciseSessionRecord(
-                        getApplicationContext().getPackageName(), 0.0, /* withRoute= */ true);
+        ExerciseSessionRecord record = getExerciseSessionWithRoute(getEmptyMetadata());
         String sessionId =
                 insertRecords(List.of(record), getApplicationContext())
                         .get(0)
@@ -235,12 +242,10 @@
 
     @Test
     public void readRecords_usingIds_mixedOwnAndOtherAppSession() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-        String otherAppSessionId = getInsertedSessionId(bundle);
-        ExerciseSessionRecord ownSession =
-                getExerciseSessionRecord(
-                        getApplicationContext().getPackageName(), 0.0, /* withRoute= */ true);
+        ExerciseSessionRecord otherAppSession = getExerciseSessionWithRoute(getEmptyMetadata());
+        String otherAppSessionId =
+                APP_A_WITH_READ_WRITE_PERMS.insertRecords(otherAppSession).get(0);
+        ExerciseSessionRecord ownSession = getExerciseSessionWithRoute(getEmptyMetadata());
         String ownSessionId =
                 insertRecords(List.of(ownSession), getApplicationContext())
                         .get(0)
@@ -277,8 +282,9 @@
                                         .build(),
                                 getApplicationContext())
                         .getToken();
-        assertThat(insertRecordAs(APP_A_WITH_READ_WRITE_PERMS).getBoolean(SUCCESS)).isTrue();
 
+        ExerciseSessionRecord otherAppSession = getExerciseSessionWithRoute(getEmptyMetadata());
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(List.of(otherAppSession));
         ChangeLogsResponse response =
                 getChangeLogs(
                         new ChangeLogsRequest.Builder(token).build(), getApplicationContext());
@@ -303,7 +309,8 @@
                                         .build(),
                                 getApplicationContext())
                         .getToken();
-        assertThat(insertRecordAs(APP_A_WITH_READ_WRITE_PERMS).getBoolean(SUCCESS)).isTrue();
+        ExerciseSessionRecord otherAppSession = getExerciseSessionWithRoute(getEmptyMetadata());
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(otherAppSession);
         mAutomation.adoptShellPermissionIdentity(READ_EXERCISE_ROUTE_PERMISSION);
 
         ChangeLogsResponse response =
@@ -317,7 +324,7 @@
         assertThat(records).isNotNull();
         assertThat(records).hasSize(1);
         assertThat(records.get(0).hasRoute()).isTrue();
-        assertThat(records.get(0).getRoute()).isNotNull();
+        assertThat(records.get(0).getRoute()).isEqualTo(otherAppSession.getRoute());
     }
 
     @Test
@@ -329,9 +336,7 @@
                                         .build(),
                                 getApplicationContext())
                         .getToken();
-        ExerciseSessionRecord record =
-                getExerciseSessionRecord(
-                        getApplicationContext().getPackageName(), 0.0, /* withRoute= */ true);
+        ExerciseSessionRecord record = getExerciseSessionWithRoute(getEmptyMetadata());
         insertRecords(List.of(record), getApplicationContext());
 
         ChangeLogsResponse response =
@@ -357,12 +362,10 @@
                                         .build(),
                                 getApplicationContext())
                         .getToken();
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-        String otherAppSessionId = getInsertedSessionId(bundle);
-        ExerciseSessionRecord ownSession =
-                getExerciseSessionRecord(
-                        getApplicationContext().getPackageName(), 0.0, /* withRoute= */ true);
+        ExerciseSessionRecord otherAppSession = getExerciseSessionWithRoute(getEmptyMetadata());
+        String otherAppSessionId =
+                APP_A_WITH_READ_WRITE_PERMS.insertRecords(otherAppSession).get(0);
+        ExerciseSessionRecord ownSession = getExerciseSessionWithRoute(getEmptyMetadata());
         String ownSessionId =
                 insertRecords(List.of(ownSession), getApplicationContext())
                         .get(0)
@@ -392,16 +395,14 @@
     public void testRouteInsert_cannotInsertRouteWithoutPerm() throws Exception {
         mAutomation.revokeRuntimePermission(
                 APP_A_WITH_READ_WRITE_PERMS.getPackageName(), WRITE_EXERCISE_ROUTE);
+        ExerciseSessionRecord otherAppSession = getExerciseSessionWithRoute(getEmptyMetadata());
 
-        try {
-            insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-            Assert.fail("Should have thrown an Security Exception!");
-        } catch (HealthConnectException e) {
-            assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
-        } finally {
-            mAutomation.grantRuntimePermission(
-                    APP_A_WITH_READ_WRITE_PERMS.getPackageName(), WRITE_EXERCISE_ROUTE);
-        }
+        HealthConnectException e =
+                assertThrows(
+                        HealthConnectException.class,
+                        () -> APP_A_WITH_READ_WRITE_PERMS.insertRecords(otherAppSession));
+
+        assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
     }
 
     @Test
@@ -411,8 +412,8 @@
                         new ReadRecordsRequestUsingFilters.Builder<>(ExerciseSessionRecord.class)
                                 .build());
         assertThat(records).isEmpty();
-
-        assertThat(insertRecordAs(APP_A_WITH_READ_WRITE_PERMS).getBoolean(SUCCESS)).isTrue();
+        ExerciseSessionRecord exerciseRecord = getExerciseSessionWithRoute(getEmptyMetadata());
+        String exerciseRecordId = APP_A_WITH_READ_WRITE_PERMS.insertRecords(exerciseRecord).get(0);
         records =
                 readRecords(
                         new ReadRecordsRequestUsingFilters.Builder<>(ExerciseSessionRecord.class)
@@ -420,8 +421,10 @@
         assertThat(records).isNotNull();
         assertThat(records).hasSize(1);
         assertThat(records.get(0).hasRoute()).isTrue();
+        ExerciseSessionRecord updatedSessionWithoutRoute =
+                getExerciseSessionWithoutRoute(getMetadataForId(exerciseRecordId));
 
-        assertThat(updateRouteAs(APP_A_WITH_READ_WRITE_PERMS).getBoolean(SUCCESS)).isTrue();
+        APP_A_WITH_READ_WRITE_PERMS.updateRecords(updatedSessionWithoutRoute);
 
         records =
                 readRecords(
@@ -430,25 +433,31 @@
         assertThat(records).isNotNull();
         assertThat(records).hasSize(1);
         assertThat(records.get(0).hasRoute()).isFalse();
-
+        assertThat(records.get(0).getRoute()).isNull();
         // Check that the route has been actually deleted, so no exceptions from incorrect record
         // state.
-        Bundle bundle =
-                readRecordsAs(
-                        APP_A_WITH_READ_WRITE_PERMS,
-                        new ArrayList<>(List.of(ExerciseSessionRecord.class.getName())));
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-        assertThat(bundle.getInt(READ_RECORDS_SIZE)).isEqualTo(1);
+        records =
+                APP_A_WITH_READ_WRITE_PERMS.readRecords(
+                        new ReadRecordsRequestUsingFilters.Builder<>(ExerciseSessionRecord.class)
+                                .build());
+        assertThat(records).hasSize(1);
+        assertThat(records.get(0).hasRoute()).isFalse();
+        assertThat(records.get(0).getRoute()).isNull();
     }
 
     @Test
     public void testRouteUpdate_updateRouteWithoutPerm_hasRouteAfterUpdate() throws Exception {
-        assertThat(insertRecordAs(APP_A_WITH_READ_WRITE_PERMS).getBoolean(SUCCESS)).isTrue();
+        ExerciseSessionRecord sessionWithRoute = getExerciseSessionWithRoute(getEmptyMetadata());
+        String otherAppSessionId =
+                APP_A_WITH_READ_WRITE_PERMS.insertRecords(sessionWithRoute).get(0);
         mAutomation.revokeRuntimePermission(
                 APP_A_WITH_READ_WRITE_PERMS.getPackageName(), WRITE_EXERCISE_ROUTE);
+        ExerciseSessionRecord updatedSessionWithoutRoute =
+                getExerciseSessionWithoutRoute(getMetadataForId(otherAppSessionId));
 
-        updateRouteAs(APP_A_WITH_READ_WRITE_PERMS);
+        APP_A_WITH_READ_WRITE_PERMS.updateRecords(updatedSessionWithoutRoute);
 
+        mAutomation.adoptShellPermissionIdentity(READ_EXERCISE_ROUTE_PERMISSION);
         List<ExerciseSessionRecord> records =
                 readRecords(
                         new ReadRecordsRequestUsingFilters.Builder<>(ExerciseSessionRecord.class)
@@ -456,17 +465,23 @@
         assertThat(records).isNotNull();
         assertThat(records).hasSize(1);
         assertThat(records.get(0).hasRoute()).isTrue();
+        assertThat(records.get(0).getRoute()).isEqualTo(sessionWithRoute.getRoute());
     }
 
     @Test
     public void testRouteUpsert_insertRecordNoRouteWithoutRoutePerm_hasRouteAfterInsert()
             throws Exception {
-        assertThat(insertRecordAs(APP_A_WITH_READ_WRITE_PERMS).getBoolean(SUCCESS)).isTrue();
+        ExerciseSessionRecord sessionWithRoute =
+                getExerciseSessionWithRoute(getMetadataForClientId("client id"));
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(sessionWithRoute);
         mAutomation.revokeRuntimePermission(
                 APP_A_WITH_READ_WRITE_PERMS.getPackageName(), WRITE_EXERCISE_ROUTE);
+        ExerciseSessionRecord updatedSessionWithoutRoute =
+                getExerciseSessionWithoutRoute(getMetadataForClientId("client id"));
 
-        insertSessionNoRouteAs(APP_A_WITH_READ_WRITE_PERMS);
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(updatedSessionWithoutRoute);
 
+        mAutomation.adoptShellPermissionIdentity(READ_EXERCISE_ROUTE_PERMISSION);
         List<ExerciseSessionRecord> records =
                 readRecords(
                         new ReadRecordsRequestUsingFilters.Builder<>(ExerciseSessionRecord.class)
@@ -474,13 +489,19 @@
         assertThat(records).isNotNull();
         assertThat(records).hasSize(1);
         assertThat(records.get(0).hasRoute()).isTrue();
+        assertThat(records.get(0).getRoute()).isEqualTo(sessionWithRoute.getRoute());
     }
 
     @Test
     public void testRouteUpsert_insertRecordNoRouteWithRoutePerm_noRouteAfterInsert()
             throws Exception {
-        assertThat(insertRecordAs(APP_A_WITH_READ_WRITE_PERMS).getBoolean(SUCCESS)).isTrue();
-        insertSessionNoRouteAs(APP_A_WITH_READ_WRITE_PERMS);
+        ExerciseSessionRecord sessionWithRoute =
+                getExerciseSessionWithRoute(getMetadataForClientId("client id"));
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(sessionWithRoute);
+        ExerciseSessionRecord updatedSessionWithoutRoute =
+                getExerciseSessionWithoutRoute(getMetadataForClientId("client id"));
+
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(updatedSessionWithoutRoute);
 
         List<ExerciseSessionRecord> records =
                 readRecords(
@@ -489,22 +510,25 @@
         assertThat(records).isNotNull();
         assertThat(records).hasSize(1);
         assertThat(records.get(0).hasRoute()).isFalse();
+        assertThat(records.get(0).getRoute()).isNull();
     }
 
-    private static String getInsertedSessionId(Bundle bundle) {
-        List<String> ids =
-                ((List<RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS))
-                        .stream()
-                                .filter(
-                                        it ->
-                                                it.getRecordType()
-                                                        .equals(
-                                                                ExerciseSessionRecord.class
-                                                                        .getName()))
-                                .map(RecordTypeAndRecordIds::getRecordIds)
-                                .flatMap(Collection::stream)
-                                .toList();
-        assertThat(ids).hasSize(1);
-        return ids.get(0);
+    private static ExerciseSessionRecord getExerciseSessionWithRoute(Metadata metadata) {
+        return getExerciseSessionRecordBuilder(metadata)
+                .setRoute(
+                        getExerciseRoute(
+                                getLocation(START_TIME, 52., 48.),
+                                getLocation(START_TIME.plusSeconds(2), 51., 49.)))
+                .build();
+    }
+
+    private static ExerciseSessionRecord getExerciseSessionWithoutRoute(Metadata metadata) {
+        return getExerciseSessionRecordBuilder(metadata).build();
+    }
+
+    private static ExerciseSessionRecord.Builder getExerciseSessionRecordBuilder(
+            Metadata metadata) {
+        return new ExerciseSessionRecord.Builder(
+                metadata, START_TIME, END_TIME, ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING);
     }
 }
diff --git a/tests/cts/hostsidetests/healthconnect/device/src/android/healthconnect/cts/device/HealthConnectDeviceTest.java b/tests/cts/hostsidetests/healthconnect/device/src/android/healthconnect/cts/device/HealthConnectDeviceTest.java
index b6121f8..a7c61ab 100644
--- a/tests/cts/hostsidetests/healthconnect/device/src/android/healthconnect/cts/device/HealthConnectDeviceTest.java
+++ b/tests/cts/hostsidetests/healthconnect/device/src/android/healthconnect/cts/device/HealthConnectDeviceTest.java
@@ -16,32 +16,23 @@
 
 package android.healthconnect.cts.device;
 
+import static android.health.connect.datatypes.ExerciseSegmentType.EXERCISE_SEGMENT_TYPE_PAUSE;
 import static android.health.connect.datatypes.ExerciseSessionRecord.EXERCISE_DURATION_TOTAL;
+import static android.health.connect.datatypes.ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING;
 import static android.health.connect.datatypes.StepsRecord.STEPS_COUNT_TOTAL;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.CHANGE_LOGS_RESPONSE;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.CHANGE_LOG_TOKEN;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.READ_RECORDS_SIZE;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.RECORD_IDS;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.SUCCESS;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.deleteRecordsAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.getChangeLogTokenAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.getDataOriginPriorityOrder;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.insertExerciseSessionAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.insertRecordAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.insertRecordWithAnotherAppPackageName;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.insertRecordWithGivenClientId;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.insertStepsRecordAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.readChangeLogsUsingDataOriginFiltersAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.readRecordsAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.readRecordsUsingDataOriginFiltersAs;
-import static android.healthconnect.cts.lib.MultiAppTestUtils.updateRecordsAs;
 import static android.healthconnect.cts.utils.TestUtils.deleteAllStagedRemoteData;
 import static android.healthconnect.cts.utils.TestUtils.deleteTestData;
 import static android.healthconnect.cts.utils.TestUtils.fetchDataOriginsPriorityOrder;
 import static android.healthconnect.cts.utils.TestUtils.getAggregateResponse;
 import static android.healthconnect.cts.utils.TestUtils.getApplicationInfo;
+import static android.healthconnect.cts.utils.TestUtils.getDataOrigin;
+import static android.healthconnect.cts.utils.TestUtils.getDataOrigins;
+import static android.healthconnect.cts.utils.TestUtils.getEmptyMetadata;
 import static android.healthconnect.cts.utils.TestUtils.getGrantedHealthPermissions;
-import static android.healthconnect.cts.utils.TestUtils.getInstantTime;
+import static android.healthconnect.cts.utils.TestUtils.getMetadata;
+import static android.healthconnect.cts.utils.TestUtils.getMetadataForClientId;
+import static android.healthconnect.cts.utils.TestUtils.getMetadataForId;
+import static android.healthconnect.cts.utils.TestUtils.getRecordIdFilters;
 import static android.healthconnect.cts.utils.TestUtils.grantPermission;
 import static android.healthconnect.cts.utils.TestUtils.insertRecordsForPriority;
 import static android.healthconnect.cts.utils.TestUtils.readRecords;
@@ -50,14 +41,15 @@
 import static android.healthconnect.cts.utils.TestUtils.revokePermission;
 import static android.healthconnect.cts.utils.TestUtils.updateDataOriginPriorityOrder;
 import static android.healthconnect.cts.utils.TestUtils.verifyDeleteRecords;
-
-import static com.android.compatibility.common.util.FeatureUtil.AUTOMOTIVE_FEATURE;
-import static com.android.compatibility.common.util.FeatureUtil.hasSystemFeature;
+import static android.healthconnect.cts.utils.TestUtils.yesterdayAt;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
+import static java.time.Duration.ofMinutes;
+
 import android.app.UiAutomation;
 import android.content.Context;
 import android.health.connect.AggregateRecordsRequest;
@@ -70,37 +62,39 @@
 import android.health.connect.RecordIdFilter;
 import android.health.connect.TimeInstantRangeFilter;
 import android.health.connect.UpdateDataOriginPriorityOrderRequest;
+import android.health.connect.changelog.ChangeLogTokenRequest;
+import android.health.connect.changelog.ChangeLogsRequest;
 import android.health.connect.changelog.ChangeLogsResponse;
 import android.health.connect.datatypes.AggregationType;
+import android.health.connect.datatypes.AppInfo;
+import android.health.connect.datatypes.BasalMetabolicRateRecord;
 import android.health.connect.datatypes.DataOrigin;
+import android.health.connect.datatypes.ExerciseSegment;
 import android.health.connect.datatypes.ExerciseSessionRecord;
 import android.health.connect.datatypes.HeartRateRecord;
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.StepsRecord;
+import android.health.connect.datatypes.units.Power;
+import android.healthconnect.cts.lib.TestAppProxy;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
-import android.os.Bundle;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.cts.install.lib.TestApp;
-
 import org.junit.After;
-import org.junit.Assert;
-import org.junit.Assume;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Optional;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 @RunWith(AndroidJUnit4.class)
@@ -111,37 +105,27 @@
     private static final int ASYNC_RETRIES = 3;
     private static final int ASYNC_RETRY_DELAY_MILLIS = 500;
 
-    static final TestApp APP_A_WITH_READ_WRITE_PERMS =
-            new TestApp(
-                    "TestAppA",
-                    "android.healthconnect.cts.testapp.readWritePerms.A",
-                    VERSION_CODE,
-                    false,
-                    "CtsHealthConnectTestAppA.apk");
+    private static final Instant NOW = Instant.now().truncatedTo(ChronoUnit.MILLIS);
 
-    private static final TestApp APP_B_WITH_READ_WRITE_PERMS =
-            new TestApp(
-                    "TestAppB",
-                    "android.healthconnect.cts.testapp.readWritePerms.B",
-                    VERSION_CODE,
-                    false,
-                    "CtsHealthConnectTestAppB.apk");
+    private static final List<Record> TEST_RECORDS =
+            List.of(
+                    getStepsRecord(getEmptyMetadata()),
+                    getHeartRateRecord(getEmptyMetadata()),
+                    getBasalMetabolicRateRecord(getEmptyMetadata()),
+                    getExerciseSessionRecord(getEmptyMetadata()));
 
-    private static final TestApp APP_WITH_WRITE_PERMS_ONLY =
-            new TestApp(
-                    "TestAppC",
-                    "android.healthconnect.cts.testapp.writePermsOnly",
-                    VERSION_CODE,
-                    false,
-                    "CtsHealthConnectTestAppWithWritePermissionsOnly.apk");
+    private static final TestAppProxy APP_A_WITH_READ_WRITE_PERMS =
+            TestAppProxy.forPackageName("android.healthconnect.cts.testapp.readWritePerms.A");
 
-    private static final TestApp APP_WITH_DATA_MANAGE_PERMS_ONLY =
-            new TestApp(
-                    "TestAppD",
-                    "android.healthconnect.cts.testapp.data.manage.permissions",
-                    VERSION_CODE,
-                    false,
-                    "CtsHealthConnectTestAppWithDataManagePermission.apk");
+    private static final TestAppProxy APP_B_WITH_READ_WRITE_PERMS =
+            TestAppProxy.forPackageName("android.healthconnect.cts.testapp.readWritePerms.B");
+
+    private static final TestAppProxy APP_WITH_WRITE_PERMS_ONLY =
+            TestAppProxy.forPackageName("android.healthconnect.cts.testapp.writePermsOnly");
+
+    private static final TestAppProxy APP_WITH_DATA_MANAGE_PERMS_ONLY =
+            TestAppProxy.forPackageName(
+                    "android.healthconnect.cts.testapp.data.manage.permissions");
 
     private static final String STEPS_1000_CLIENT_ID = "client-id-1";
     private static final String STEPS_2000_CLIENT_ID = "client-id-2";
@@ -166,9 +150,13 @@
 
     private Context mContext;
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() {
-        Assume.assumeFalse(hasSystemFeature(AUTOMOTIVE_FEATURE));
         mContext = ApplicationProvider.getApplicationContext();
     }
 
@@ -180,173 +168,138 @@
 
     @Test
     public void testAppWithNormalReadWritePermCanInsertRecord() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        StepsRecord record = getStepsRecord(getEmptyMetadata());
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(record);
     }
 
     @Test
     public void testAnAppCantDeleteAnotherAppEntry() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        StepsRecord record = getStepsRecord(getEmptyMetadata());
+        String recordId = APP_A_WITH_READ_WRITE_PERMS.insertRecords(record).get(0);
+        RecordIdFilter recordIdFilter = RecordIdFilter.fromId(StepsRecord.class, recordId);
 
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
+        HealthConnectException e =
+                assertThrows(
+                        HealthConnectException.class,
+                        () -> APP_B_WITH_READ_WRITE_PERMS.deleteRecords(recordIdFilter));
 
-        try {
-            deleteRecordsAs(APP_B_WITH_READ_WRITE_PERMS, listOfRecordIdsAndClass);
-            Assert.fail("Should have thrown an Invalid Argument Exception!");
-        } catch (HealthConnectException e) {
-
-            assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_INVALID_ARGUMENT);
-        }
+        assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_INVALID_ARGUMENT);
     }
 
     @Test
     public void testAnAppCantUpdateAnotherAppEntry() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        StepsRecord record = getStepsRecord(getEmptyMetadata());
+        String recordId = APP_A_WITH_READ_WRITE_PERMS.insertRecords(record).get(0);
+        StepsRecord updatedRecord = getStepsRecord(getMetadataForId(recordId));
 
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
+        HealthConnectException e =
+                assertThrows(
+                        HealthConnectException.class,
+                        () -> APP_B_WITH_READ_WRITE_PERMS.updateRecords(updatedRecord));
 
-        try {
-            updateRecordsAs(APP_B_WITH_READ_WRITE_PERMS, listOfRecordIdsAndClass);
-            Assert.fail("Should have thrown an Invalid Argument Exception!");
-        } catch (HealthConnectException e) {
-            assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_INVALID_ARGUMENT);
-        }
+        assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_INVALID_ARGUMENT);
     }
 
     @Test
     public void testDataOriginGetsOverriddenBySelfPackageName() throws Exception {
-        Bundle bundle =
-                insertRecordWithAnotherAppPackageName(
-                        APP_A_WITH_READ_WRITE_PERMS, APP_B_WITH_READ_WRITE_PERMS);
+        ExerciseSessionRecord record =
+                getExerciseSessionRecord(getMetadata(getDataOrigin("ignored.package.name")));
+        String recordId = APP_A_WITH_READ_WRITE_PERMS.insertRecords(record).get(0);
 
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
+        List<ExerciseSessionRecord> records =
+                readRecords(
+                        new ReadRecordsRequestUsingFilters.Builder<>(ExerciseSessionRecord.class)
+                                .build());
 
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds : listOfRecordIdsAndClass) {
-            Class<? extends Record> recordType =
-                    (Class<? extends Record>) Class.forName(recordTypeAndRecordIds.getRecordType());
-            if (!recordType.equals(ExerciseSessionRecord.class)) {
-                // skip other record types since we don't have read permissions for these.
-                continue;
-            }
-            List<Record> records =
-                    (List<Record>)
-                            readRecords(
-                                    new ReadRecordsRequestUsingFilters.Builder<>(recordType)
-                                            .build());
-            assertThat(records).isNotEmpty();
-            for (Record record : records) {
-                assertThat(record.getMetadata().getDataOrigin().getPackageName())
-                        .isEqualTo(APP_A_WITH_READ_WRITE_PERMS.getPackageName());
-            }
-        }
+        assertThat(records).hasSize(1);
+        assertThat(records.get(0).getMetadata())
+                .isEqualTo(
+                        new Metadata.Builder()
+                                .setId(recordId)
+                                .setDataOrigin(
+                                        getDataOrigin(APP_A_WITH_READ_WRITE_PERMS.getPackageName()))
+                                .build());
     }
 
     @Test
     public void testAppWithWritePermsOnly_readOwnData_success() throws Exception {
-        Bundle bundle = insertRecordAs(APP_WITH_WRITE_PERMS_ONLY);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        StepsRecord record = getStepsRecord(getEmptyMetadata());
+        String recordId = APP_WITH_WRITE_PERMS_ONLY.insertRecords(record).get(0);
 
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
+        List<StepsRecord> records =
+                APP_WITH_WRITE_PERMS_ONLY.readRecords(
+                        new ReadRecordsRequestUsingFilters.Builder<>(StepsRecord.class)
+                                .addDataOrigins(
+                                        getDataOrigin(APP_WITH_WRITE_PERMS_ONLY.getPackageName()))
+                                .build());
 
-        ArrayList<String> recordClassesToRead = new ArrayList<>();
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds : listOfRecordIdsAndClass) {
-            recordClassesToRead.add(recordTypeAndRecordIds.getRecordType());
-        }
-
-        bundle =
-                readRecordsAs(
-                        APP_WITH_WRITE_PERMS_ONLY,
-                        recordClassesToRead,
-                        /* dataOriginFilterPackageNames= */ Optional.of(
-                                List.of(APP_WITH_WRITE_PERMS_ONLY.getPackageName())));
-        assertThat(bundle.getInt(READ_RECORDS_SIZE)).isEqualTo(listOfRecordIdsAndClass.size());
+        assertThat(records).hasSize(1);
+        assertThat(records.get(0))
+                .isEqualTo(
+                        getStepsRecord(
+                                getMetadataForId(
+                                        recordId,
+                                        getDataOrigin(
+                                                APP_WITH_WRITE_PERMS_ONLY.getPackageName()))));
     }
 
     @Test
     public void testAppWithWritePermsOnly_readDataFromAllApps_throwsError() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        StepsRecord record = getStepsRecord(getEmptyMetadata());
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(record).get(0);
 
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
-
-        ArrayList<String> recordClassesToRead = new ArrayList<>();
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds : listOfRecordIdsAndClass) {
-            recordClassesToRead.add(recordTypeAndRecordIds.getRecordType());
-        }
-
-        try {
-            bundle =
-                    readRecordsAs(
-                            APP_WITH_WRITE_PERMS_ONLY,
-                            recordClassesToRead,
-                            // empty data implies all data is requested
-                            /* dataOriginFilterPackageNames= */ Optional.of(List.of()));
-            fail("Expected to fail with HealthConnectException but didn't");
-        } catch (Exception e) {
-            assertThat(e).isInstanceOf(HealthConnectException.class);
-            assertThat(((HealthConnectException) e).getErrorCode())
-                    .isEqualTo(HealthConnectException.ERROR_SECURITY);
-        }
+        HealthConnectException e =
+                assertThrows(
+                        HealthConnectException.class,
+                        () ->
+                                APP_WITH_WRITE_PERMS_ONLY.readRecords(
+                                        new ReadRecordsRequestUsingFilters.Builder<>(
+                                                        StepsRecord.class)
+                                                .build()));
+        assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
     }
 
     @Test
     public void testAppWithWritePermsOnly_readDataFromOtherApps_throwsError() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        StepsRecord record = getStepsRecord(getEmptyMetadata());
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(record);
 
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
-
-        ArrayList<String> recordClassesToRead = new ArrayList<>();
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds : listOfRecordIdsAndClass) {
-            recordClassesToRead.add(recordTypeAndRecordIds.getRecordType());
-        }
-
-        try {
-            readRecordsAs(
-                    APP_WITH_WRITE_PERMS_ONLY,
-                    recordClassesToRead,
-                    /* dataOriginFilterPackageNames= */ Optional.of(
-                            List.of(
-                                    APP_WITH_WRITE_PERMS_ONLY.getPackageName(),
-                                    APP_A_WITH_READ_WRITE_PERMS.getPackageName())));
-            fail("Expected to fail with HealthConnectException but didn't");
-        } catch (Exception e) {
-            assertThat(e).isInstanceOf(HealthConnectException.class);
-            assertThat(((HealthConnectException) e).getErrorCode())
-                    .isEqualTo(HealthConnectException.ERROR_SECURITY);
-        }
+        HealthConnectException e =
+                assertThrows(
+                        HealthConnectException.class,
+                        () ->
+                                APP_WITH_WRITE_PERMS_ONLY.readRecords(
+                                        new ReadRecordsRequestUsingFilters.Builder<>(
+                                                        StepsRecord.class)
+                                                .addDataOrigins(
+                                                        getDataOrigin(
+                                                                APP_WITH_WRITE_PERMS_ONLY
+                                                                        .getPackageName()))
+                                                .addDataOrigins(
+                                                        getDataOrigin(
+                                                                APP_A_WITH_READ_WRITE_PERMS
+                                                                        .getPackageName()))
+                                                .build()));
+        assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
     }
 
     @Test
     public void testAppWithWritePermsOnly_readDataByIdForOwnApp_success() throws Exception {
-        Bundle bundle =
-                insertStepsRecordAs(APP_A_WITH_READ_WRITE_PERMS, "01:00 PM", "03:00 PM", 1000);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-        List<Record> writtenRecords = TestUtils.insertRecords(List.of(STEPS_1000, STEPS_2000));
-        List<String> recordIds =
-                writtenRecords.stream()
-                        .map(record -> record.getMetadata().getId())
-                        .collect(Collectors.toList());
+        List<Record> ownRecords = TestUtils.insertRecords(List.of(STEPS_1000, STEPS_2000));
+        List<String> ownRecordIds =
+                ownRecords.stream().map(record -> record.getMetadata().getId()).toList();
 
         List<Record> readRecords =
                 TestUtils.readRecords(
                         new ReadRecordsRequestUsingIds.Builder(StepsRecord.class)
-                                .addId(recordIds.get(0))
-                                .addId(recordIds.get(1))
+                                .addId(ownRecordIds.get(0))
+                                .addId(ownRecordIds.get(1))
                                 .build());
 
         assertThat(
                         readRecords.stream()
                                 .map(record -> record.getMetadata().getClientRecordId())
-                                .collect(Collectors.toList()))
+                                .toList())
                 .containsExactly(STEPS_1000_CLIENT_ID, STEPS_2000_CLIENT_ID);
     }
 
@@ -354,25 +307,18 @@
     @Test
     public void testAppWithWritePermsOnly_readDataByIdForOtherApps_filtersOutOtherAppData()
             throws Exception {
-        Bundle bundle =
-                insertStepsRecordAs(APP_A_WITH_READ_WRITE_PERMS, "01:00 PM", "03:00 PM", 1000);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-        String otherAppRecordId =
-                ((List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS))
-                        .get(0)
-                        .getRecordIds()
-                        .get(0);
-        List<Record> writtenRecords = TestUtils.insertRecords(List.of(STEPS_1000, STEPS_2000));
-        List<String> recordIds =
-                writtenRecords.stream()
-                        .map(record -> record.getMetadata().getId())
-                        .collect(Collectors.toList());
+        StepsRecord otherAppRecord = getStepsRecord(getEmptyMetadata());
+        String otherAppRecordId = APP_A_WITH_READ_WRITE_PERMS.insertRecords(otherAppRecord).get(0);
+
+        List<Record> ownRecords = TestUtils.insertRecords(List.of(STEPS_1000, STEPS_2000));
+        List<String> ownRecordIds =
+                ownRecords.stream().map(record -> record.getMetadata().getId()).toList();
 
         List<Record> readRecords =
                 TestUtils.readRecords(
                         new ReadRecordsRequestUsingIds.Builder(StepsRecord.class)
-                                .addId(recordIds.get(0))
-                                .addId(recordIds.get(1))
+                                .addId(ownRecordIds.get(0))
+                                .addId(ownRecordIds.get(1))
                                 .addId(otherAppRecordId)
                                 .build());
 
@@ -501,144 +447,119 @@
     }
 
     @Test
-    public void testAppWithManageHealthDataPermsOnlyCantInsertRecords() throws Exception {
-        try {
-            insertRecordAs(APP_WITH_DATA_MANAGE_PERMS_ONLY);
-            Assert.fail("Should have thrown Exception while inserting records!");
-        } catch (HealthConnectException e) {
-            assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
-        }
+    public void testAppWithManageHealthDataPermsOnlyCantInsertRecords() {
+        StepsRecord record = getStepsRecord(getEmptyMetadata());
+
+        HealthConnectException e =
+                assertThrows(
+                        HealthConnectException.class,
+                        () -> APP_WITH_DATA_MANAGE_PERMS_ONLY.insertRecords(record));
+
+        assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
     }
 
     @Test
     public void testAppWithManageHealthDataPermsOnlyCantUpdateRecords() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        StepsRecord record = getStepsRecord(getEmptyMetadata());
+        String recordId = APP_WITH_WRITE_PERMS_ONLY.insertRecords(record).get(0);
+        StepsRecord updatedRecord = getStepsRecord(getMetadataForId(recordId));
 
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
+        HealthConnectException e =
+                assertThrows(
+                        HealthConnectException.class,
+                        () -> APP_WITH_DATA_MANAGE_PERMS_ONLY.updateRecords(updatedRecord));
 
-        try {
-            updateRecordsAs(APP_WITH_DATA_MANAGE_PERMS_ONLY, listOfRecordIdsAndClass);
-            Assert.fail("Should have thrown Health Connect Exception!");
-        } catch (HealthConnectException e) {
-            assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
-        }
+        assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
     }
 
     @Test
     public void testTwoAppsCanUseSameClientRecordIdsToInsert() throws Exception {
-        final double clientId = Math.random();
-        Bundle bundle = insertRecordWithGivenClientId(APP_A_WITH_READ_WRITE_PERMS, clientId);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        StepsRecord record = getStepsRecord(getMetadataForClientId("common.client.id"));
 
-        bundle = insertRecordWithGivenClientId(APP_B_WITH_READ_WRITE_PERMS, clientId);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(record);
+        APP_B_WITH_READ_WRITE_PERMS.insertRecords(record);
     }
 
     @Test
     public void testAppCanReadRecordsUsingDataOriginFilters() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(TEST_RECORDS);
 
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
+        List<BasalMetabolicRateRecord> basalMetabolicRateRecords =
+                APP_A_WITH_READ_WRITE_PERMS.readRecords(
+                        new ReadRecordsRequestUsingFilters.Builder<>(BasalMetabolicRateRecord.class)
+                                .build());
+        List<ExerciseSessionRecord> exerciseSessionRecords =
+                APP_A_WITH_READ_WRITE_PERMS.readRecords(
+                        new ReadRecordsRequestUsingFilters.Builder<>(ExerciseSessionRecord.class)
+                                .build());
+        List<HeartRateRecord> heartRateRecords =
+                APP_A_WITH_READ_WRITE_PERMS.readRecords(
+                        new ReadRecordsRequestUsingFilters.Builder<>(HeartRateRecord.class)
+                                .build());
+        List<StepsRecord> stepsRecords =
+                APP_A_WITH_READ_WRITE_PERMS.readRecords(
+                        new ReadRecordsRequestUsingFilters.Builder<>(StepsRecord.class).build());
 
-        int noOfRecordsInsertedByAppA = 0;
-        Set<String> recordClassesToReadSet = new HashSet<>();
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds : listOfRecordIdsAndClass) {
-            noOfRecordsInsertedByAppA += recordTypeAndRecordIds.getRecordIds().size();
-            recordClassesToReadSet.add(recordTypeAndRecordIds.getRecordType());
-        }
-
-        bundle = insertRecordAs(APP_B_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-
-        listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
-
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds : listOfRecordIdsAndClass) {
-            recordClassesToReadSet.add(recordTypeAndRecordIds.getRecordType());
-        }
-
-        ArrayList<String> recordClassesToRead = new ArrayList<>();
-        for (String recordClass : recordClassesToReadSet) {
-            recordClassesToRead.add(recordClass);
-        }
-        bundle =
-                readRecordsUsingDataOriginFiltersAs(
-                        APP_A_WITH_READ_WRITE_PERMS, recordClassesToRead);
-        assertThat(bundle.getInt(READ_RECORDS_SIZE)).isEqualTo(noOfRecordsInsertedByAppA);
+        assertThat(basalMetabolicRateRecords).hasSize(1);
+        assertThat(exerciseSessionRecords).hasSize(1);
+        assertThat(heartRateRecords).hasSize(1);
+        assertThat(stepsRecords).hasSize(1);
     }
 
     @Test
     public void testAppCanReadChangeLogsUsingDataOriginFilters() throws Exception {
-        Bundle bundle =
-                getChangeLogTokenAs(
-                        APP_B_WITH_READ_WRITE_PERMS,
-                        APP_A_WITH_READ_WRITE_PERMS.getPackageName(),
-                        null);
-        String changeLogTokenForAppB = bundle.getString(CHANGE_LOG_TOKEN);
+        String changeLogTokenForAppB =
+                APP_B_WITH_READ_WRITE_PERMS.getChangeLogToken(
+                        new ChangeLogTokenRequest.Builder()
+                                .addDataOriginFilter(
+                                        getDataOrigin(APP_A_WITH_READ_WRITE_PERMS.getPackageName()))
+                                .build());
+        String changeLogTokenForAppA =
+                APP_A_WITH_READ_WRITE_PERMS.getChangeLogToken(
+                        new ChangeLogTokenRequest.Builder()
+                                .addDataOriginFilter(
+                                        getDataOrigin(APP_B_WITH_READ_WRITE_PERMS.getPackageName()))
+                                .build());
 
-        bundle =
-                getChangeLogTokenAs(
-                        APP_A_WITH_READ_WRITE_PERMS,
-                        APP_B_WITH_READ_WRITE_PERMS.getPackageName(),
-                        null);
-        String changeLogTokenForAppA = bundle.getString(CHANGE_LOG_TOKEN);
+        List<Record> recordsA =
+                List.of(
+                        getStepsRecord(getEmptyMetadata()),
+                        getHeartRateRecord(getEmptyMetadata()),
+                        getBasalMetabolicRateRecord(getEmptyMetadata()));
 
-        bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        List<String> recordIdsA = APP_A_WITH_READ_WRITE_PERMS.insertRecords(recordsA);
 
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
+        List<Record> updatedRecordsA =
+                List.of(
+                        getStepsRecord(getMetadataForId(recordIdsA.get(0))),
+                        getHeartRateRecord(getMetadataForId(recordIdsA.get(1))),
+                        getBasalMetabolicRateRecord(getMetadataForId(recordIdsA.get(2))));
+        APP_A_WITH_READ_WRITE_PERMS.updateRecords(updatedRecordsA);
 
-        List<String> listOfRecordIdsInsertedByAppA = new ArrayList<>();
-        int noOfRecordsInsertedByAppA = 0;
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds : listOfRecordIdsAndClass) {
-            noOfRecordsInsertedByAppA += recordTypeAndRecordIds.getRecordIds().size();
-            listOfRecordIdsInsertedByAppA.addAll(recordTypeAndRecordIds.getRecordIds());
-        }
+        List<String> bRecordIds = APP_B_WITH_READ_WRITE_PERMS.insertRecords(TEST_RECORDS);
 
-        updateRecordsAs(APP_A_WITH_READ_WRITE_PERMS, listOfRecordIdsAndClass);
+        APP_B_WITH_READ_WRITE_PERMS.deleteRecords(getRecordIdFilters(bRecordIds, TEST_RECORDS));
 
-        bundle = insertRecordAs(APP_B_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        ChangeLogsResponse response =
+                APP_B_WITH_READ_WRITE_PERMS.getChangeLogs(
+                        new ChangeLogsRequest.Builder(changeLogTokenForAppB).build());
 
-        listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
-
-        int noOfRecordsInsertedByAppB = 0;
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds : listOfRecordIdsAndClass) {
-            noOfRecordsInsertedByAppB += recordTypeAndRecordIds.getRecordIds().size();
-        }
-
-        deleteRecordsAs(APP_B_WITH_READ_WRITE_PERMS, listOfRecordIdsAndClass);
-
-        bundle =
-                readChangeLogsUsingDataOriginFiltersAs(
-                        APP_B_WITH_READ_WRITE_PERMS, changeLogTokenForAppB);
-
-        ChangeLogsResponse response = bundle.getParcelable(CHANGE_LOGS_RESPONSE);
-
-        assertThat(response.getUpsertedRecords()).hasSize(noOfRecordsInsertedByAppA);
+        assertThat(response.getUpsertedRecords()).hasSize(recordsA.size());
         assertThat(
                         response.getUpsertedRecords().stream()
                                 .map(Record::getMetadata)
                                 .map(Metadata::getId)
                                 .toList())
-                .containsExactlyElementsIn(listOfRecordIdsInsertedByAppA);
+                .containsExactlyElementsIn(recordIdsA);
 
         assertThat(response.getDeletedLogs()).isEmpty();
 
-        bundle =
-                readChangeLogsUsingDataOriginFiltersAs(
-                        APP_A_WITH_READ_WRITE_PERMS, changeLogTokenForAppA);
-
-        response = bundle.getParcelable(CHANGE_LOGS_RESPONSE);
+        response =
+                APP_A_WITH_READ_WRITE_PERMS.getChangeLogs(
+                        new ChangeLogsRequest.Builder(changeLogTokenForAppA).build());
 
         assertThat(response.getUpsertedRecords()).isEmpty();
-        assertThat(response.getDeletedLogs()).hasSize(noOfRecordsInsertedByAppB);
+        assertThat(response.getDeletedLogs()).hasSize(TEST_RECORDS.size());
     }
 
     @Test
@@ -650,8 +571,8 @@
                 fetchDataOriginsPriorityOrder(HealthDataCategory.ACTIVITY)
                         .getDataOriginsPriorityOrder()
                         .stream()
-                        .map(dataOrigin -> dataOrigin.getPackageName())
-                        .collect(Collectors.toList());
+                        .map(DataOrigin::getPackageName)
+                        .toList();
 
         List<String> healthPerms =
                 getGrantedHealthPermissions(APP_A_WITH_READ_WRITE_PERMS.getPackageName());
@@ -667,8 +588,8 @@
                 fetchDataOriginsPriorityOrder(HealthDataCategory.ACTIVITY)
                         .getDataOriginsPriorityOrder()
                         .stream()
-                        .map(dataOrigin -> dataOrigin.getPackageName())
-                        .collect(Collectors.toList());
+                        .map(DataOrigin::getPackageName)
+                        .toList();
 
         assertThat(newPriorityList).hasSize(oldPriorityList.size() + 1);
         assertThat(newPriorityList).contains(APP_A_WITH_READ_WRITE_PERMS.getPackageName());
@@ -808,79 +729,35 @@
 
     @Test
     public void testAppWithManageHealthDataPermsCanReadAnotherAppEntry() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
-
-        ArrayList<String> recordClassesToRead = new ArrayList<>();
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds : listOfRecordIdsAndClass) {
-            recordClassesToRead.add(recordTypeAndRecordIds.getRecordType());
-        }
-
+        StepsRecord record = getStepsRecord(getEmptyMetadata());
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(record);
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         uiAutomation.adoptShellPermissionIdentity(MANAGE_HEALTH_DATA);
-        int recordsSize = 0;
-        try {
-            for (String recordClass : recordClassesToRead) {
-                List<? extends Record> recordsRead =
-                        readRecords(
-                                new ReadRecordsRequestUsingFilters.Builder<>(
-                                                (Class<? extends Record>)
-                                                        Class.forName(recordClass))
-                                        .build(),
-                                ApplicationProvider.getApplicationContext());
 
-                recordsSize += recordsRead.size();
-            }
-        } catch (Exception e) {
-            Assert.fail(
-                    "App with MANAGE_HEALTH_DATA  permission should have read entries of another"
-                            + " app!");
-        }
-        assertThat(recordsSize).isNotEqualTo(0);
+        List<StepsRecord> recordsRead =
+                readRecords(
+                        new ReadRecordsRequestUsingFilters.Builder<>(StepsRecord.class).build(),
+                        ApplicationProvider.getApplicationContext());
+
+        assertThat(recordsRead).hasSize(1);
     }
 
     @Test
     public void testAppWithManageHealthDataPermsCanDeleteAnotherAppEntry() throws Exception {
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-
-        List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass =
-                (List<TestUtils.RecordTypeAndRecordIds>) bundle.getSerializable(RECORD_IDS);
-
-        List<RecordIdFilter> recordIdFilters = new ArrayList<>();
-        for (TestUtils.RecordTypeAndRecordIds recordTypeAndRecordIds : listOfRecordIdsAndClass) {
-            for (String recordId : recordTypeAndRecordIds.getRecordIds()) {
-                recordIdFilters.add(
-                        RecordIdFilter.fromId(
-                                (Class<? extends Record>)
-                                        Class.forName(recordTypeAndRecordIds.getRecordType()),
-                                recordId));
-            }
-        }
-
+        StepsRecord record = getStepsRecord(getEmptyMetadata());
+        String recordId = APP_A_WITH_READ_WRITE_PERMS.insertRecords(record).get(0);
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         uiAutomation.adoptShellPermissionIdentity(MANAGE_HEALTH_DATA);
-        try {
-            verifyDeleteRecords(recordIdFilters, ApplicationProvider.getApplicationContext());
-        } catch (Exception e) {
-            Assert.fail(
-                    "App with MANAGE_HEALTH_DATA  permission should have deleted data from other"
-                            + " app!");
-        }
+
+        verifyDeleteRecords(List.of(RecordIdFilter.fromId(StepsRecord.class, recordId)));
     }
 
     @Test
     public void testToVerifyGetContributorApplicationsInfo() throws Exception {
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
 
-        Bundle bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-
-        bundle = insertRecordAs(APP_B_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(TEST_RECORDS);
+        APP_B_WITH_READ_WRITE_PERMS.insertRecords(TEST_RECORDS);
 
         List<String> pkgNameList =
                 List.of(
@@ -893,9 +770,7 @@
         // finishes (or we run out of retries).
         for (int i = 1; i <= ASYNC_RETRIES; i++) {
             List<String> appInfoList =
-                    getApplicationInfo().stream()
-                            .map(appInfo -> appInfo.getPackageName())
-                            .collect(Collectors.toList());
+                    getApplicationInfo().stream().map(AppInfo::getPackageName).toList();
 
             try {
                 assertThat(appInfoList).containsAtLeastElementsIn(pkgNameList);
@@ -915,39 +790,51 @@
             throws Exception {
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
 
-        revokeAndThenGrantHealthPermissions(APP_A_WITH_READ_WRITE_PERMS);
-        revokeAndThenGrantHealthPermissions(APP_B_WITH_READ_WRITE_PERMS);
+        revokeAndThenGrantHealthPermissions(APP_A_WITH_READ_WRITE_PERMS.getPackageName());
+        revokeAndThenGrantHealthPermissions(APP_B_WITH_READ_WRITE_PERMS.getPackageName());
 
         List<DataOrigin> dataOriginPrioOrder =
-                getDataOriginPriorityOrder(
-                        APP_A_WITH_READ_WRITE_PERMS, APP_B_WITH_READ_WRITE_PERMS);
+                getDataOrigins(
+                        APP_A_WITH_READ_WRITE_PERMS.getPackageName(),
+                        APP_B_WITH_READ_WRITE_PERMS.getPackageName());
 
         uiAutomation.adoptShellPermissionIdentity(MANAGE_HEALTH_DATA);
         List<String> priorityList =
                 fetchDataOriginsPriorityOrder(HealthDataCategory.ACTIVITY)
                         .getDataOriginsPriorityOrder()
                         .stream()
-                        .map(dataOrigin -> dataOrigin.getPackageName())
-                        .collect(Collectors.toList());
+                        .map(DataOrigin::getPackageName)
+                        .toList();
 
         assertThat(
                         priorityList.equals(
                                 dataOriginPrioOrder.stream()
-                                        .map(dataOrigin -> dataOrigin.getPackageName())
-                                        .collect(Collectors.toList())))
+                                        .map(DataOrigin::getPackageName)
+                                        .toList()))
                 .isTrue();
 
-        Bundle bundle =
-                insertStepsRecordAs(APP_A_WITH_READ_WRITE_PERMS, "01:00 PM", "03:00 PM", 1000);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-        bundle = insertStepsRecordAs(APP_B_WITH_READ_WRITE_PERMS, "02:00 PM", "04:00 PM", 2000);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        StepsRecord stepsRecordA =
+                new StepsRecord.Builder(
+                                getEmptyMetadata(),
+                                yesterdayAt("13:00"),
+                                yesterdayAt("15:00"),
+                                1000)
+                        .build();
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(stepsRecordA);
+        StepsRecord stepsRecordB =
+                new StepsRecord.Builder(
+                                getEmptyMetadata(),
+                                yesterdayAt("14:00"),
+                                yesterdayAt("16:00"),
+                                2000)
+                        .build();
+        APP_B_WITH_READ_WRITE_PERMS.insertRecords(stepsRecordB);
 
         AggregateRecordsRequest<Long> aggregateRecordsRequest =
                 new AggregateRecordsRequest.Builder<Long>(
                                 new TimeInstantRangeFilter.Builder()
-                                        .setStartTime(getInstantTime("01:00 PM"))
-                                        .setEndTime(getInstantTime("04:00 PM"))
+                                        .setStartTime(yesterdayAt("13:00"))
+                                        .setEndTime(yesterdayAt("16:00"))
                                         .build())
                         .addAggregationType(STEPS_COUNT_TOTAL)
                         .build();
@@ -960,8 +847,9 @@
         assertThat(oldResponse.get(STEPS_COUNT_TOTAL)).isEqualTo(2000);
 
         dataOriginPrioOrder =
-                getDataOriginPriorityOrder(
-                        APP_B_WITH_READ_WRITE_PERMS, APP_A_WITH_READ_WRITE_PERMS);
+                getDataOrigins(
+                        APP_B_WITH_READ_WRITE_PERMS.getPackageName(),
+                        APP_A_WITH_READ_WRITE_PERMS.getPackageName());
 
         uiAutomation.adoptShellPermissionIdentity(MANAGE_HEALTH_DATA);
         updateDataOriginPriorityOrder(
@@ -993,46 +881,59 @@
             throws Exception {
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
 
-        revokeAndThenGrantHealthPermissions(APP_A_WITH_READ_WRITE_PERMS);
-        revokeAndThenGrantHealthPermissions(APP_B_WITH_READ_WRITE_PERMS);
+        revokeAndThenGrantHealthPermissions(APP_A_WITH_READ_WRITE_PERMS.getPackageName());
+        revokeAndThenGrantHealthPermissions(APP_B_WITH_READ_WRITE_PERMS.getPackageName());
 
         List<DataOrigin> dataOriginPrioOrder =
-                getDataOriginPriorityOrder(
-                        APP_A_WITH_READ_WRITE_PERMS, APP_B_WITH_READ_WRITE_PERMS);
+                getDataOrigins(
+                        APP_A_WITH_READ_WRITE_PERMS.getPackageName(),
+                        APP_B_WITH_READ_WRITE_PERMS.getPackageName());
 
         uiAutomation.adoptShellPermissionIdentity(MANAGE_HEALTH_DATA);
         List<String> priorityList =
                 fetchDataOriginsPriorityOrder(HealthDataCategory.ACTIVITY)
                         .getDataOriginsPriorityOrder()
                         .stream()
-                        .map(dataOrigin -> dataOrigin.getPackageName())
-                        .collect(Collectors.toList());
+                        .map(DataOrigin::getPackageName)
+                        .toList();
 
         assertThat(
                         priorityList.equals(
                                 dataOriginPrioOrder.stream()
-                                        .map(dataOrigin -> dataOrigin.getPackageName())
-                                        .collect(Collectors.toList())))
+                                        .map(DataOrigin::getPackageName)
+                                        .toList()))
                 .isTrue();
 
-        Bundle bundle =
-                insertExerciseSessionAs(
-                        APP_A_WITH_READ_WRITE_PERMS,
-                        "01:00 PM",
-                        "03:00 PM",
-                        "02:00 PM",
-                        "03:00 PM");
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
-        bundle =
-                insertExerciseSessionAs(
-                        APP_B_WITH_READ_WRITE_PERMS, "02:00 PM", "03:00 PM", null, null);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        ExerciseSessionRecord sessionRecordA =
+                new ExerciseSessionRecord.Builder(
+                                getEmptyMetadata(),
+                                yesterdayAt("13:00"),
+                                yesterdayAt("15:00"),
+                                EXERCISE_SESSION_TYPE_RUNNING)
+                        .setSegments(
+                                List.of(
+                                        new ExerciseSegment.Builder(
+                                                        yesterdayAt("14:00"),
+                                                        yesterdayAt("15:00"),
+                                                        EXERCISE_SEGMENT_TYPE_PAUSE)
+                                                .build()))
+                        .build();
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(sessionRecordA);
+
+        ExerciseSessionRecord sessionRecordB =
+                new ExerciseSessionRecord.Builder(
+                                getEmptyMetadata(),
+                                yesterdayAt("14:00"),
+                                yesterdayAt("15:00"),
+                                EXERCISE_SESSION_TYPE_RUNNING)
+                        .build();
+        APP_B_WITH_READ_WRITE_PERMS.insertRecords(sessionRecordB);
 
         AggregateRecordsRequest<Long> aggregateRecordsRequest =
                 new AggregateRecordsRequest.Builder<Long>(
                                 new TimeInstantRangeFilter.Builder()
-                                        .setStartTime(getInstantTime("01:00 PM"))
-                                        .setEndTime(getInstantTime("03:00 PM"))
+                                        .setStartTime(yesterdayAt("13:00"))
+                                        .setEndTime(yesterdayAt("15:00"))
                                         .build())
                         .addAggregationType(EXERCISE_DURATION_TOTAL)
                         .build();
@@ -1044,14 +945,14 @@
         assertThat(response.get(EXERCISE_DURATION_TOTAL)).isNotNull();
         assertThat(response.get(EXERCISE_DURATION_TOTAL))
                 .isEqualTo(
-                        (getInstantTime("03:00 PM").toEpochMilli()
-                                        - getInstantTime("01:00 PM").toEpochMilli())
-                                - (getInstantTime("03:00 PM").toEpochMilli()
-                                        - getInstantTime("02:00 PM").toEpochMilli()));
+                        Duration.between(yesterdayAt("13:00"), yesterdayAt("15:00"))
+                                .minus(Duration.between(yesterdayAt("14:00"), yesterdayAt("15:00")))
+                                .toMillis());
 
         dataOriginPrioOrder =
-                getDataOriginPriorityOrder(
-                        APP_B_WITH_READ_WRITE_PERMS, APP_A_WITH_READ_WRITE_PERMS);
+                getDataOrigins(
+                        APP_B_WITH_READ_WRITE_PERMS.getPackageName(),
+                        APP_A_WITH_READ_WRITE_PERMS.getPackageName());
 
         uiAutomation.adoptShellPermissionIdentity(MANAGE_HEALTH_DATA);
         updateDataOriginPriorityOrder(
@@ -1063,22 +964,20 @@
                 fetchDataOriginsPriorityOrder(HealthDataCategory.ACTIVITY)
                         .getDataOriginsPriorityOrder()
                         .stream()
-                        .map(dataOrigin -> dataOrigin.getPackageName())
-                        .collect(Collectors.toList());
+                        .map(DataOrigin::getPackageName)
+                        .toList();
 
         assertThat(
                         priorityList.equals(
                                 dataOriginPrioOrder.stream()
-                                        .map(dataOrigin -> dataOrigin.getPackageName())
-                                        .collect(Collectors.toList())))
+                                        .map(DataOrigin::getPackageName)
+                                        .toList()))
                 .isTrue();
 
         AggregateRecordsResponse<Long> newResponse = getAggregateResponse(aggregateRecordsRequest);
         assertThat(newResponse.get(EXERCISE_DURATION_TOTAL)).isNotNull();
         assertThat(newResponse.get(EXERCISE_DURATION_TOTAL))
-                .isEqualTo(
-                        getInstantTime("03:00 PM").toEpochMilli()
-                                - getInstantTime("01:00 PM").toEpochMilli());
+                .isEqualTo(Duration.between(yesterdayAt("13:00"), yesterdayAt("15:00")).toMillis());
     }
 
     @Test
@@ -1087,41 +986,45 @@
         recordClassesToRead.add(HeartRateRecord.class.getName());
         recordClassesToRead.add(StepsRecord.class.getName());
 
-        Bundle bundle =
-                getChangeLogTokenAs(
-                        APP_B_WITH_READ_WRITE_PERMS,
-                        APP_A_WITH_READ_WRITE_PERMS.getPackageName(),
-                        recordClassesToRead);
-        String changeLogTokenForAppB = bundle.getString(CHANGE_LOG_TOKEN);
+        String changeLogTokenForAppB =
+                APP_B_WITH_READ_WRITE_PERMS.getChangeLogToken(
+                        new ChangeLogTokenRequest.Builder()
+                                .addDataOriginFilter(
+                                        getDataOrigin(APP_A_WITH_READ_WRITE_PERMS.getPackageName()))
+                                .addRecordType(HeartRateRecord.class)
+                                .addRecordType(StepsRecord.class)
+                                .build());
 
-        bundle = insertRecordAs(APP_A_WITH_READ_WRITE_PERMS);
-        assertThat(bundle.getBoolean(SUCCESS)).isTrue();
+        APP_A_WITH_READ_WRITE_PERMS.insertRecords(TEST_RECORDS);
 
         List<String> healthPerms =
                 getGrantedHealthPermissions(APP_B_WITH_READ_WRITE_PERMS.getPackageName());
 
         revokeHealthPermissions(APP_B_WITH_READ_WRITE_PERMS.getPackageName());
 
-        try {
-            readChangeLogsUsingDataOriginFiltersAs(
-                    APP_B_WITH_READ_WRITE_PERMS, changeLogTokenForAppB);
-            Assert.fail(
-                    "Should have thrown exception in reading changeLogs without read permissions!");
-        } catch (HealthConnectException e) {
-            assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
-        }
+        HealthConnectException e =
+                assertThrows(
+                        HealthConnectException.class,
+                        () ->
+                                APP_B_WITH_READ_WRITE_PERMS.getChangeLogs(
+                                        new ChangeLogsRequest.Builder(changeLogTokenForAppB)
+                                                .build()));
+        assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
 
-        try {
-            getChangeLogTokenAs(
-                    APP_B_WITH_READ_WRITE_PERMS,
-                    APP_A_WITH_READ_WRITE_PERMS.getPackageName(),
-                    recordClassesToRead);
-            Assert.fail(
-                    "Should have thrown exception in getting change log token without read "
-                            + "permission!");
-        } catch (HealthConnectException e) {
-            assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
-        }
+        e =
+                assertThrows(
+                        HealthConnectException.class,
+                        () ->
+                                APP_B_WITH_READ_WRITE_PERMS.getChangeLogToken(
+                                        new ChangeLogTokenRequest.Builder()
+                                                .addRecordType(HeartRateRecord.class)
+                                                .addRecordType(StepsRecord.class)
+                                                .addDataOriginFilter(
+                                                        getDataOrigin(
+                                                                APP_A_WITH_READ_WRITE_PERMS
+                                                                        .getPackageName()))
+                                                .build()));
+        assertThat(e.getErrorCode()).isEqualTo(HealthConnectException.ERROR_SECURITY);
 
         for (String perm : healthPerms) {
             grantPermission(APP_B_WITH_READ_WRITE_PERMS.getPackageName(), perm);
@@ -1131,17 +1034,42 @@
     private static StepsRecord getStepsRecord(
             int stepCount, Instant startTime, int durationInHours, String clientId) {
         return new StepsRecord.Builder(
-                        new Metadata.Builder()
-                                .setDataOrigin(
-                                        new DataOrigin.Builder()
-                                                .setPackageName(
-                                                        APP_WITH_WRITE_PERMS_ONLY.getPackageName())
-                                                .build())
-                                .setClientRecordId(clientId)
-                                .build(),
+                        getMetadataForClientId(
+                                clientId,
+                                getDataOrigin(APP_WITH_WRITE_PERMS_ONLY.getPackageName())),
                         startTime,
                         startTime.plus(durationInHours, ChronoUnit.HOURS),
                         stepCount)
                 .build();
     }
+
+    private static StepsRecord getStepsRecord(Metadata metadata) {
+        Instant startTime = NOW.minus(ofMinutes(10));
+        Instant endTime = NOW.minus(ofMinutes(5));
+        return new StepsRecord.Builder(metadata, startTime, endTime, 155).build();
+    }
+
+    private static HeartRateRecord getHeartRateRecord(Metadata metadata) {
+        Instant startTime = NOW.minus(ofMinutes(10));
+        Instant endTime = NOW.minus(ofMinutes(5));
+        return new HeartRateRecord.Builder(
+                        metadata,
+                        startTime,
+                        endTime,
+                        List.of(new HeartRateRecord.HeartRateSample(75, startTime.plusSeconds(5))))
+                .build();
+    }
+
+    private static BasalMetabolicRateRecord getBasalMetabolicRateRecord(Metadata metadata) {
+        Instant time = NOW.minus(ofMinutes(10));
+        return new BasalMetabolicRateRecord.Builder(metadata, time, Power.fromWatts(10)).build();
+    }
+
+    private static ExerciseSessionRecord getExerciseSessionRecord(Metadata metadata) {
+        Instant startTime = NOW.minus(ofMinutes(10));
+        Instant endTime = NOW.minus(ofMinutes(5));
+        return new ExerciseSessionRecord.Builder(
+                        metadata, startTime, endTime, EXERCISE_SESSION_TYPE_RUNNING)
+                .build();
+    }
 }
diff --git a/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/dailyjob/DailyDeleteAccessLogTest.java b/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/dailyjob/DailyDeleteAccessLogTest.java
index 7d29f05..d5188c7 100644
--- a/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/dailyjob/DailyDeleteAccessLogTest.java
+++ b/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/dailyjob/DailyDeleteAccessLogTest.java
@@ -51,6 +51,9 @@
 
     @Override
     protected void tearDown() throws Exception {
+        if (!isHardwareSupported(getDevice())) {
+            return;
+        }
         clearData(getDevice());
         resetTime(getDevice(), mTestStartTime, mDeviceStartTime);
         super.tearDown();
diff --git a/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/logging/HealthConnectDailyLogsStatsTests.java b/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/logging/HealthConnectDailyLogsStatsTests.java
index eb2a5e7..6d5906f 100644
--- a/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/logging/HealthConnectDailyLogsStatsTests.java
+++ b/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/logging/HealthConnectDailyLogsStatsTests.java
@@ -77,6 +77,9 @@
 
     @Override
     protected void tearDown() throws Exception {
+        if (!isHardwareSupported(getDevice())) {
+            return;
+        }
         // TODO(b/313055175): Do not disable rate limiting once b/300238889 is resolved.
         HostSideTestUtil.restoreRateLimitingFeatureFlag(getDevice());
         ConfigUtils.removeConfig(getDevice());
diff --git a/tests/cts/hostsidetests/healthconnect/libs/HealthConnectTestLib/src/android/healthconnect/cts/lib/BundleHelper.java b/tests/cts/hostsidetests/healthconnect/libs/HealthConnectTestLib/src/android/healthconnect/cts/lib/BundleHelper.java
new file mode 100644
index 0000000..4fa2733
--- /dev/null
+++ b/tests/cts/hostsidetests/healthconnect/libs/HealthConnectTestLib/src/android/healthconnect/cts/lib/BundleHelper.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.healthconnect.cts.lib;
+
+import android.health.connect.ReadRecordsRequestUsingFilters;
+import android.health.connect.RecordIdFilter;
+import android.health.connect.TimeInstantRangeFilter;
+import android.health.connect.changelog.ChangeLogTokenRequest;
+import android.health.connect.changelog.ChangeLogsRequest;
+import android.health.connect.changelog.ChangeLogsResponse;
+import android.health.connect.datatypes.BasalMetabolicRateRecord;
+import android.health.connect.datatypes.DataOrigin;
+import android.health.connect.datatypes.ExerciseRoute;
+import android.health.connect.datatypes.ExerciseSegment;
+import android.health.connect.datatypes.ExerciseSessionRecord;
+import android.health.connect.datatypes.HeartRateRecord;
+import android.health.connect.datatypes.InstantRecord;
+import android.health.connect.datatypes.IntervalRecord;
+import android.health.connect.datatypes.Metadata;
+import android.health.connect.datatypes.Record;
+import android.health.connect.datatypes.StepsRecord;
+import android.health.connect.datatypes.units.Power;
+import android.os.Bundle;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+
+/** Converters from/to bundles for HC request, response, and record types. */
+public final class BundleHelper {
+    private static final String PREFIX = "android.healthconnect.cts.";
+    public static final String QUERY_TYPE = PREFIX + "QUERY_TYPE";
+    public static final String INSERT_RECORDS_QUERY = PREFIX + "INSERT_RECORDS_QUERY";
+    public static final String READ_RECORDS_QUERY = PREFIX + "READ_RECORDS_QUERY";
+    public static final String READ_CHANGE_LOGS_QUERY = PREFIX + "READ_CHANGE_LOGS_QUERY";
+    public static final String DELETE_RECORDS_QUERY = PREFIX + "DELETE_RECORDS_QUERY";
+    public static final String UPDATE_RECORDS_QUERY = PREFIX + "UPDATE_RECORDS_QUERY";
+    public static final String GET_CHANGE_LOG_TOKEN_QUERY = PREFIX + "GET_CHANGE_LOG_TOKEN_QUERY";
+    public static final String INTENT_EXCEPTION = PREFIX + "INTENT_EXCEPTION";
+
+    private static final String CHANGE_LOGS_RESPONSE = PREFIX + "CHANGE_LOGS_RESPONSE";
+    private static final String CHANGE_LOG_TOKEN = PREFIX + "CHANGE_LOG_TOKEN";
+    private static final String RECORD_CLASS_NAME = PREFIX + "RECORD_CLASS_NAME";
+    private static final String START_TIME_MILLIS = PREFIX + "START_TIME_MILLIS";
+    private static final String END_TIME_MILLIS = PREFIX + "END_TIME_MILLIS";
+    private static final String EXERCISE_SESSION_TYPE = PREFIX + "EXERCISE_SESSION_TYPE";
+    private static final String RECORD_LIST = PREFIX + "RECORD_LIST";
+    private static final String PACKAGE_NAME = PREFIX + "PACKAGE_NAME";
+    private static final String CLIENT_ID = PREFIX + "CLIENT_ID";
+    private static final String RECORD_ID = PREFIX + "RECORD_ID";
+    private static final String METADATA = PREFIX + "METADATA";
+    private static final String VALUES = PREFIX + "VALUES";
+    private static final String COUNT = PREFIX + "COUNT";
+    private static final String SAMPLE_TIMES = PREFIX + "SAMPLE_TIMES";
+    private static final String SAMPLE_VALUES = PREFIX + "SAMPLE_VALUES";
+    private static final String EXERCISE_ROUTE_TIMESTAMPS = PREFIX + "EXERCISE_ROUTE_TIMESTAMPS";
+    private static final String EXERCISE_ROUTE_LATITUDES = PREFIX + "EXERCISE_ROUTE_LATITUDES";
+    private static final String EXERCISE_ROUTE_LONGITUDES = PREFIX + "EXERCISE_ROUTE_LONGITUDES";
+    private static final String POWER_WATTS = PREFIX + "POWER_WATTS";
+    private static final String TIME_INSTANT_RANGE_FILTER = PREFIX + "TIME_INSTANT_RANGE_FILTER";
+    private static final String CHANGE_LOGS_REQUEST = PREFIX + "CHANGE_LOGS_REQUEST";
+    private static final String CHANGE_LOG_TOKEN_REQUEST = PREFIX + "CHANGE_LOG_TOKEN_REQUEST";
+    private static final String EXERCISE_SEGMENT_START_TIMES =
+            PREFIX + "EXERCISE_SEGMENT_START_TIMES";
+    private static final String EXERCISE_SEGMENT_END_TIMES = PREFIX + "EXERCISE_SEGMENT_END_TIMES";
+    private static final String EXERCISE_SEGMENT_TYPES = PREFIX + "EXERCISE_SEGMENT_TYPES";
+
+    /** Converts an insert records request to a bundle. */
+    public static Bundle fromInsertRecordsRequest(List<Record> records) {
+        Bundle bundle = new Bundle();
+        bundle.putString(QUERY_TYPE, INSERT_RECORDS_QUERY);
+        bundle.putParcelableArrayList(RECORD_LIST, new ArrayList<>(fromRecordList(records)));
+        return bundle;
+    }
+
+    /** Converts a bundle to an insert records request. */
+    public static List<? extends Record> toInsertRecordsRequest(Bundle bundle) {
+        return toRecordList(bundle.getParcelableArrayList(RECORD_LIST, Bundle.class));
+    }
+
+    /** Converts an update records request to a bundle. */
+    public static Bundle fromUpdateRecordsRequest(List<Record> records) {
+        Bundle bundle = new Bundle();
+        bundle.putString(QUERY_TYPE, UPDATE_RECORDS_QUERY);
+        bundle.putParcelableArrayList(RECORD_LIST, new ArrayList<>(fromRecordList(records)));
+        return bundle;
+    }
+
+    /** Converts a bundle to an update records request. */
+    public static List<? extends Record> toUpdateRecordsRequest(Bundle bundle) {
+        return toRecordList(bundle.getParcelableArrayList(RECORD_LIST, Bundle.class));
+    }
+
+    /** Converts an insert records response to a bundle. */
+    public static Bundle fromInsertRecordsResponse(List<String> recordIds) {
+        Bundle bundle = new Bundle();
+        bundle.putStringArrayList(RECORD_ID, new ArrayList<>(recordIds));
+        return bundle;
+    }
+
+    /** Converts a bundle to an insert records response. */
+    public static List<String> toInsertRecordsResponse(Bundle bundle) {
+        return bundle.getStringArrayList(RECORD_ID);
+    }
+
+    /** Converts a ReadRecordsRequestUsingFilters to a bundle. */
+    public static <T extends Record> Bundle fromReadRecordsRequestUsingFilters(
+            ReadRecordsRequestUsingFilters<T> request) {
+        Bundle bundle = new Bundle();
+        bundle.putString(QUERY_TYPE, READ_RECORDS_QUERY);
+        bundle.putString(RECORD_CLASS_NAME, request.getRecordType().getName());
+        bundle.putStringArrayList(
+                PACKAGE_NAME,
+                new ArrayList<>(
+                        request.getDataOrigins().stream()
+                                .map(DataOrigin::getPackageName)
+                                .toList()));
+
+        if (request.getTimeRangeFilter() instanceof TimeInstantRangeFilter filter) {
+            bundle.putBoolean(TIME_INSTANT_RANGE_FILTER, true);
+
+            Long startTime = transformOrNull(filter.getStartTime(), Instant::toEpochMilli);
+            Long endTime = transformOrNull(filter.getEndTime(), Instant::toEpochMilli);
+
+            bundle.putSerializable(START_TIME_MILLIS, startTime);
+            bundle.putSerializable(END_TIME_MILLIS, endTime);
+        } else if (request.getTimeRangeFilter() != null) {
+            throw new IllegalArgumentException("Unsupported time range filter");
+        }
+
+        return bundle;
+    }
+
+    /** Converts a bundle to a ReadRecordsRequestUsingFilters. */
+    public static ReadRecordsRequestUsingFilters<? extends Record> toReadRecordsRequestUsingFilters(
+            Bundle bundle) {
+        String recordClassName = bundle.getString(RECORD_CLASS_NAME);
+
+        Class<? extends Record> recordClass = recordClassForName(recordClassName);
+
+        ReadRecordsRequestUsingFilters.Builder<? extends Record> request =
+                new ReadRecordsRequestUsingFilters.Builder<>(recordClass);
+
+        if (bundle.getBoolean(TIME_INSTANT_RANGE_FILTER)) {
+            Long startTimeMillis = bundle.getSerializable(START_TIME_MILLIS, Long.class);
+            Long endTimeMillis = bundle.getSerializable(END_TIME_MILLIS, Long.class);
+
+            Instant startTime = transformOrNull(startTimeMillis, Instant::ofEpochMilli);
+            Instant endTime = transformOrNull(endTimeMillis, Instant::ofEpochMilli);
+
+            TimeInstantRangeFilter timeInstantRangeFilter =
+                    new TimeInstantRangeFilter.Builder()
+                            .setStartTime(startTime)
+                            .setEndTime(endTime)
+                            .build();
+
+            request.setTimeRangeFilter(timeInstantRangeFilter);
+        }
+        List<String> packageNames = bundle.getStringArrayList(PACKAGE_NAME);
+
+        if (packageNames != null) {
+            for (String packageName : packageNames) {
+                request.addDataOrigins(
+                        new DataOrigin.Builder().setPackageName(packageName).build());
+            }
+        }
+
+        return request.build();
+    }
+
+    /** Converts a read records response to a bundle. */
+    public static Bundle fromReadRecordsResponse(List<? extends Record> records) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelableArrayList(RECORD_LIST, new ArrayList<>(fromRecordList(records)));
+        return bundle;
+    }
+
+    /** Converts a bundle to a read records response. */
+    public static <T extends Record> List<T> toReadRecordsResponse(Bundle bundle) {
+        return (List<T>) toRecordList(bundle.getParcelableArrayList(RECORD_LIST, Bundle.class));
+    }
+
+    /** Converts a delete records request to a bundle. */
+    public static Bundle fromDeleteRecordsByIdsRequest(List<RecordIdFilter> recordIdFilters) {
+        Bundle bundle = new Bundle();
+        bundle.putString(QUERY_TYPE, DELETE_RECORDS_QUERY);
+
+        List<String> recordClassNames =
+                recordIdFilters.stream()
+                        .map(RecordIdFilter::getRecordType)
+                        .map(Class::getName)
+                        .toList();
+        List<String> recordIds = recordIdFilters.stream().map(RecordIdFilter::getId).toList();
+
+        bundle.putStringArrayList(RECORD_CLASS_NAME, new ArrayList<>(recordClassNames));
+        bundle.putStringArrayList(RECORD_ID, new ArrayList<>(recordIds));
+
+        return bundle;
+    }
+
+    /** Converts a bundle to a delete records request. */
+    public static List<RecordIdFilter> toDeleteRecordsByIdsRequest(Bundle bundle) {
+        List<String> recordClassNames = bundle.getStringArrayList(RECORD_CLASS_NAME);
+        List<String> recordIds = bundle.getStringArrayList(RECORD_ID);
+
+        return IntStream.range(0, recordClassNames.size())
+                .mapToObj(
+                        i -> {
+                            String recordClassName = recordClassNames.get(i);
+                            Class<? extends Record> recordClass =
+                                    recordClassForName(recordClassName);
+                            String recordId = recordIds.get(i);
+                            return RecordIdFilter.fromId(recordClass, recordId);
+                        })
+                .toList();
+    }
+
+    /** Converts a ChangeLogTokenRequest to a bundle. */
+    public static Bundle fromChangeLogTokenRequest(ChangeLogTokenRequest request) {
+        Bundle bundle = new Bundle();
+        bundle.putString(QUERY_TYPE, GET_CHANGE_LOG_TOKEN_QUERY);
+        bundle.putParcelable(CHANGE_LOG_TOKEN_REQUEST, request);
+        return bundle;
+    }
+
+    /** Converts a bundle to a ChangeLogTokenRequest. */
+    public static ChangeLogTokenRequest toChangeLogTokenRequest(Bundle bundle) {
+        return bundle.getParcelable(CHANGE_LOG_TOKEN_REQUEST, ChangeLogTokenRequest.class);
+    }
+
+    /** Converts a changelog token response to a bundle. */
+    public static Bundle fromChangeLogTokenResponse(String token) {
+        Bundle bundle = new Bundle();
+        bundle.putString(CHANGE_LOG_TOKEN, token);
+        return bundle;
+    }
+
+    /** Converts a bundle to a change log token response. */
+    public static String toChangeLogTokenResponse(Bundle bundle) {
+        return bundle.getString(CHANGE_LOG_TOKEN);
+    }
+
+    /** Converts a ChangeLogsRequest to a bundle. */
+    public static Bundle fromChangeLogsRequest(ChangeLogsRequest request) {
+        Bundle bundle = new Bundle();
+        bundle.putString(QUERY_TYPE, READ_CHANGE_LOGS_QUERY);
+        bundle.putParcelable(CHANGE_LOGS_REQUEST, request);
+        return bundle;
+    }
+
+    /** Converts a bundle to a ChangeLogsRequest. */
+    public static ChangeLogsRequest toChangeLogsRequest(Bundle bundle) {
+        return bundle.getParcelable(CHANGE_LOGS_REQUEST, ChangeLogsRequest.class);
+    }
+
+    /** Converts a ChangeLogsResponse to a bundle. */
+    public static Bundle fromChangeLogsResponse(ChangeLogsResponse response) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(CHANGE_LOGS_RESPONSE, response);
+        return bundle;
+    }
+
+    /** Converts a bundle to a ChangeLogsResponse. */
+    public static ChangeLogsResponse toChangeLogsResponse(Bundle bundle) {
+        return bundle.getParcelable(CHANGE_LOGS_RESPONSE, ChangeLogsResponse.class);
+    }
+
+    private static List<Bundle> fromRecordList(List<? extends Record> records) {
+        return records.stream().map(BundleHelper::fromRecord).toList();
+    }
+
+    private static List<? extends Record> toRecordList(List<Bundle> bundles) {
+        return bundles.stream().map(BundleHelper::toRecord).toList();
+    }
+
+    private static Bundle fromRecord(Record record) {
+        Bundle bundle = new Bundle();
+        bundle.putString(RECORD_CLASS_NAME, record.getClass().getName());
+        bundle.putBundle(METADATA, fromMetadata(record.getMetadata()));
+
+        if (record instanceof IntervalRecord intervalRecord) {
+            bundle.putLong(START_TIME_MILLIS, intervalRecord.getStartTime().toEpochMilli());
+            bundle.putLong(END_TIME_MILLIS, intervalRecord.getEndTime().toEpochMilli());
+        } else if (record instanceof InstantRecord instantRecord) {
+            bundle.putLong(START_TIME_MILLIS, instantRecord.getTime().toEpochMilli());
+        } else {
+            throw new IllegalArgumentException("Unsupported record type: ");
+        }
+
+        Bundle values;
+
+        if (record instanceof BasalMetabolicRateRecord basalMetabolicRateRecord) {
+            values = getBasalMetabolicRateRecordValues(basalMetabolicRateRecord);
+        } else if (record instanceof ExerciseSessionRecord exerciseSessionRecord) {
+            values = getExerciseSessionRecordValues(exerciseSessionRecord);
+        } else if (record instanceof StepsRecord stepsRecord) {
+            values = getStepsRecordValues(stepsRecord);
+        } else if (record instanceof HeartRateRecord heartRateRecord) {
+            values = getHeartRateRecordValues(heartRateRecord);
+        } else {
+            throw new IllegalArgumentException(
+                    "Unsupported record type: " + record.getClass().getName());
+        }
+
+        bundle.putBundle(VALUES, values);
+
+        Record decodedRecord = toRecord(bundle);
+        if (!record.equals(decodedRecord)) {
+            throw new IllegalArgumentException(
+                    "Some fields are incorrectly encoded in " + record.getClass().getSimpleName());
+        }
+
+        return bundle;
+    }
+
+    private static Record toRecord(Bundle bundle) {
+        Metadata metadata = toMetadata(bundle.getBundle(METADATA));
+
+        String recordClassName = bundle.getString(RECORD_CLASS_NAME);
+
+        Instant startTime = Instant.ofEpochMilli(bundle.getLong(START_TIME_MILLIS));
+        Instant endTime = Instant.ofEpochMilli(bundle.getLong(END_TIME_MILLIS));
+
+        Bundle values = bundle.getBundle(VALUES);
+
+        if (Objects.equals(recordClassName, BasalMetabolicRateRecord.class.getName())) {
+            return createBasalMetabolicRateRecord(metadata, startTime, values);
+        } else if (Objects.equals(recordClassName, ExerciseSessionRecord.class.getName())) {
+            return createExerciseSessionRecord(metadata, startTime, endTime, values);
+        } else if (Objects.equals(recordClassName, HeartRateRecord.class.getName())) {
+            return createHeartRateRecord(metadata, startTime, endTime, values);
+        } else if (Objects.equals(recordClassName, StepsRecord.class.getName())) {
+            return createStepsRecord(metadata, startTime, endTime, values);
+        }
+
+        throw new IllegalArgumentException("Unsupported record type: " + recordClassName);
+    }
+
+    private static Bundle getBasalMetabolicRateRecordValues(BasalMetabolicRateRecord record) {
+        Bundle values = new Bundle();
+        values.putDouble(POWER_WATTS, record.getBasalMetabolicRate().getInWatts());
+        return values;
+    }
+
+    private static BasalMetabolicRateRecord createBasalMetabolicRateRecord(
+            Metadata metadata, Instant time, Bundle values) {
+        double powerWatts = values.getDouble(POWER_WATTS);
+
+        return new BasalMetabolicRateRecord.Builder(metadata, time, Power.fromWatts(powerWatts))
+                .build();
+    }
+
+    private static Bundle getExerciseSessionRecordValues(ExerciseSessionRecord record) {
+        Bundle values = new Bundle();
+
+        values.putInt(EXERCISE_SESSION_TYPE, record.getExerciseType());
+
+        ExerciseRoute route = record.getRoute();
+
+        if (route != null) {
+            long[] timestamps =
+                    route.getRouteLocations().stream()
+                            .map(ExerciseRoute.Location::getTime)
+                            .mapToLong(Instant::toEpochMilli)
+                            .toArray();
+            double[] latitudes =
+                    route.getRouteLocations().stream()
+                            .mapToDouble(ExerciseRoute.Location::getLatitude)
+                            .toArray();
+            double[] longitudes =
+                    route.getRouteLocations().stream()
+                            .mapToDouble(ExerciseRoute.Location::getLongitude)
+                            .toArray();
+            values.putLongArray(EXERCISE_ROUTE_TIMESTAMPS, timestamps);
+            values.putDoubleArray(EXERCISE_ROUTE_LATITUDES, latitudes);
+            values.putDoubleArray(EXERCISE_ROUTE_LONGITUDES, longitudes);
+        }
+
+        long[] segmentStartTimes =
+                record.getSegments().stream()
+                        .map(ExerciseSegment::getStartTime)
+                        .mapToLong(Instant::toEpochMilli)
+                        .toArray();
+        long[] segmentEndTimes =
+                record.getSegments().stream()
+                        .map(ExerciseSegment::getEndTime)
+                        .mapToLong(Instant::toEpochMilli)
+                        .toArray();
+        int[] segmentTypes =
+                record.getSegments().stream().mapToInt(ExerciseSegment::getSegmentType).toArray();
+
+        values.putLongArray(EXERCISE_SEGMENT_START_TIMES, segmentStartTimes);
+        values.putLongArray(EXERCISE_SEGMENT_END_TIMES, segmentEndTimes);
+        values.putIntArray(EXERCISE_SEGMENT_TYPES, segmentTypes);
+
+        return values;
+    }
+
+    private static ExerciseSessionRecord createExerciseSessionRecord(
+            Metadata metadata, Instant startTime, Instant endTime, Bundle values) {
+        int exerciseType = values.getInt(EXERCISE_SESSION_TYPE);
+
+        ExerciseSessionRecord.Builder record =
+                new ExerciseSessionRecord.Builder(metadata, startTime, endTime, exerciseType);
+
+        long[] routeTimestamps = values.getLongArray(EXERCISE_ROUTE_TIMESTAMPS);
+
+        int locationCount = routeTimestamps == null ? 0 : routeTimestamps.length;
+
+        if (locationCount > 0) {
+            double[] latitudes = values.getDoubleArray(EXERCISE_ROUTE_LATITUDES);
+            double[] longitudes = values.getDoubleArray(EXERCISE_ROUTE_LONGITUDES);
+            List<ExerciseRoute.Location> locations =
+                    IntStream.range(0, locationCount)
+                            .mapToObj(
+                                    i -> {
+                                        Instant time = Instant.ofEpochMilli(routeTimestamps[i]);
+                                        double latitude = latitudes[i];
+                                        double longitude = longitudes[i];
+
+                                        return new ExerciseRoute.Location.Builder(
+                                                        time, latitude, longitude)
+                                                .build();
+                                    })
+                            .toList();
+
+            record.setRoute(new ExerciseRoute(locations));
+        }
+
+        long[] segmentStartTimes = values.getLongArray(EXERCISE_SEGMENT_START_TIMES);
+        long[] segmentEndTimes = values.getLongArray(EXERCISE_SEGMENT_END_TIMES);
+        int[] segmentTypes = values.getIntArray(EXERCISE_SEGMENT_TYPES);
+
+        List<ExerciseSegment> segments =
+                IntStream.range(0, segmentStartTimes.length)
+                        .mapToObj(
+                                i -> {
+                                    Instant segmentStartTime =
+                                            Instant.ofEpochMilli(segmentStartTimes[i]);
+                                    Instant segmentEndTime =
+                                            Instant.ofEpochMilli(segmentEndTimes[i]);
+                                    return new ExerciseSegment.Builder(
+                                                    segmentStartTime,
+                                                    segmentEndTime,
+                                                    segmentTypes[i])
+                                            .build();
+                                })
+                        .toList();
+
+        record.setSegments(segments);
+
+        return record.build();
+    }
+
+    private static Bundle getHeartRateRecordValues(HeartRateRecord record) {
+        Bundle values = new Bundle();
+        long[] times =
+                record.getSamples().stream()
+                        .map(HeartRateRecord.HeartRateSample::getTime)
+                        .mapToLong(Instant::toEpochMilli)
+                        .toArray();
+        long[] bpms =
+                record.getSamples().stream()
+                        .mapToLong(HeartRateRecord.HeartRateSample::getBeatsPerMinute)
+                        .toArray();
+
+        values.putLongArray(SAMPLE_TIMES, times);
+        values.putLongArray(SAMPLE_VALUES, bpms);
+        return values;
+    }
+
+    private static HeartRateRecord createHeartRateRecord(
+            Metadata metadata, Instant startTime, Instant endTime, Bundle values) {
+
+        long[] times = values.getLongArray(SAMPLE_TIMES);
+        long[] bpms = values.getLongArray(SAMPLE_VALUES);
+
+        List<HeartRateRecord.HeartRateSample> samples =
+                IntStream.range(0, times.length)
+                        .mapToObj(
+                                i ->
+                                        new HeartRateRecord.HeartRateSample(
+                                                bpms[i], Instant.ofEpochMilli(times[i])))
+                        .toList();
+
+        return new HeartRateRecord.Builder(metadata, startTime, endTime, samples).build();
+    }
+
+    private static Bundle getStepsRecordValues(StepsRecord record) {
+        Bundle values = new Bundle();
+        values.putLong(COUNT, record.getCount());
+        return values;
+    }
+
+    private static StepsRecord createStepsRecord(
+            Metadata metadata, Instant startTime, Instant endTime, Bundle values) {
+        long count = values.getLong(COUNT);
+
+        return new StepsRecord.Builder(metadata, startTime, endTime, count).build();
+    }
+
+    private static Bundle fromMetadata(Metadata metadata) {
+        Bundle bundle = new Bundle();
+        bundle.putString(RECORD_ID, metadata.getId());
+        bundle.putString(PACKAGE_NAME, metadata.getDataOrigin().getPackageName());
+        bundle.putString(CLIENT_ID, metadata.getClientRecordId());
+        return bundle;
+    }
+
+    private static Metadata toMetadata(Bundle bundle) {
+        Metadata.Builder metadata = new Metadata.Builder();
+
+        ifNotNull(bundle.getString(RECORD_ID), metadata::setId);
+        ifNotNull(
+                bundle.getString(PACKAGE_NAME),
+                packageName ->
+                        metadata.setDataOrigin(
+                                new DataOrigin.Builder().setPackageName(packageName).build()));
+        metadata.setClientRecordId(bundle.getString(CLIENT_ID));
+
+        return metadata.build();
+    }
+
+    private static <T> void ifNotNull(T obj, Consumer<T> consumer) {
+        if (obj == null) {
+            return;
+        }
+        consumer.accept(obj);
+    }
+
+    private static <T, R> R transformOrNull(T obj, Function<T, R> transform) {
+        if (obj == null) {
+            return null;
+        }
+        return transform.apply(obj);
+    }
+
+    private static Class<? extends Record> recordClassForName(String className) {
+        try {
+            return (Class<? extends Record>) Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private BundleHelper() {}
+}
diff --git a/tests/cts/hostsidetests/healthconnect/libs/HealthConnectTestLib/src/android/healthconnect/cts/lib/MultiAppTestUtils.java b/tests/cts/hostsidetests/healthconnect/libs/HealthConnectTestLib/src/android/healthconnect/cts/lib/MultiAppTestUtils.java
deleted file mode 100644
index fd0b3a2..0000000
--- a/tests/cts/hostsidetests/healthconnect/libs/HealthConnectTestLib/src/android/healthconnect/cts/lib/MultiAppTestUtils.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.healthconnect.cts.lib;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.health.connect.datatypes.DataOrigin;
-import android.healthconnect.cts.utils.TestUtils;
-import android.os.Bundle;
-
-import com.android.cts.install.lib.TestApp;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-public class MultiAppTestUtils {
-    static final String TAG = "HealthConnectTest";
-    public static final String QUERY_TYPE = "android.healthconnect.cts.queryType";
-    public static final String INTENT_EXTRA_CALLING_PKG = "android.healthconnect.cts.calling_pkg";
-    public static final String APP_PKG_NAME_USED_IN_DATA_ORIGIN =
-            "android.healthconnect.cts.pkg.usedInDataOrigin";
-    public static final String INSERT_RECORD_QUERY = "android.healthconnect.cts.insertRecord";
-    public static final String READ_RECORDS_QUERY = "android.healthconnect.cts.readRecords";
-    public static final String READ_RECORDS_SIZE = "android.healthconnect.cts.readRecordsNumber";
-    public static final String READ_USING_DATA_ORIGIN_FILTERS =
-            "android.healthconnect.cts.readUsingDataOriginFilters";
-
-    public static final String DATA_ORIGIN_FILTER_PACKAGE_NAMES =
-            "android.healthconnect.cts.dataOriginFilterPackageNames";
-    public static final String READ_RECORD_CLASS_NAME =
-            "android.healthconnect.cts.readRecordsClass";
-    public static final String READ_CHANGE_LOGS_QUERY = "android.healthconnect.cts.readChangeLogs";
-    public static final String CHANGE_LOGS_RESPONSE =
-            "android.healthconnect.cts.changeLogsResponse";
-    public static final String CHANGE_LOG_TOKEN = "android.healthconnect.cts.changeLogToken";
-    public static final String SUCCESS = "android.healthconnect.cts.success";
-    public static final String CLIENT_ID = "android.healthconnect.cts.clientId";
-    public static final String RECORD_IDS = "android.healthconnect.cts.records";
-    public static final String DELETE_RECORDS_QUERY = "android.healthconnect.cts.deleteRecords";
-    public static final String UPDATE_RECORDS_QUERY = "android.healthconnect.cts.updateRecords";
-    public static final String UPDATE_EXERCISE_ROUTE = "android.healthconnect.cts.updateRoute";
-
-    public static final String UPSERT_EXERCISE_ROUTE = "android.healthconnect.cts.upsertRoute";
-    public static final String GET_CHANGE_LOG_TOKEN_QUERY =
-            "android.healthconnect.cts.getChangeLogToken";
-    public static final String RECORD_TYPE = "android.healthconnect.cts.recordType";
-    public static final String STEPS_RECORD = "android.healthconnect.cts.stepsRecord";
-    public static final String EXERCISE_SESSION = "android.healthconnect.cts.exerciseSession";
-    public static final String START_TIME = "android.healthconnect.cts.startTime";
-    public static final String END_TIME = "android.healthconnect.cts.endTime";
-    public static final String STEPS_COUNT = "android.healthconnect.cts.stepsCount";
-    public static final String PAUSE_START = "android.healthconnect.cts.pauseStart";
-    public static final String PAUSE_END = "android.healthconnect.cts.pauseEnd";
-    public static final String INTENT_EXCEPTION = "android.healthconnect.cts.exception";
-    private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20);
-
-    public static Bundle insertRecordAs(TestApp testApp) throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY);
-
-        return getFromTestApp(testApp, bundle);
-    }
-
-    public static Bundle deleteRecordsAs(
-            TestApp testApp, List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass)
-            throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, DELETE_RECORDS_QUERY);
-        bundle.putSerializable(RECORD_IDS, (Serializable) listOfRecordIdsAndClass);
-
-        return getFromTestApp(testApp, bundle);
-    }
-
-    public static Bundle updateRecordsAs(
-            TestApp testAppToUpdateData,
-            List<TestUtils.RecordTypeAndRecordIds> listOfRecordIdsAndClass)
-            throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, UPDATE_RECORDS_QUERY);
-        bundle.putSerializable(RECORD_IDS, (Serializable) listOfRecordIdsAndClass);
-
-        return getFromTestApp(testAppToUpdateData, bundle);
-    }
-
-    public static Bundle updateRouteAs(TestApp testAppToUpdateData) throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, UPDATE_EXERCISE_ROUTE);
-        return getFromTestApp(testAppToUpdateData, bundle);
-    }
-
-    public static Bundle insertSessionNoRouteAs(TestApp testAppToUpdateData) throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, UPSERT_EXERCISE_ROUTE);
-        return getFromTestApp(testAppToUpdateData, bundle);
-    }
-
-    public static Bundle insertRecordWithAnotherAppPackageName(
-            TestApp testAppToInsertData, TestApp testAppPkgNameUsed) throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY);
-        bundle.putString(APP_PKG_NAME_USED_IN_DATA_ORIGIN, testAppPkgNameUsed.getPackageName());
-
-        return getFromTestApp(testAppToInsertData, bundle);
-    }
-
-    public static Bundle readRecordsAs(TestApp testApp, ArrayList<String> recordClassesToRead)
-            throws Exception {
-        return readRecordsAs(
-                testApp, recordClassesToRead, /* dataOriginFilterPackageNames= */ Optional.empty());
-    }
-
-    public static Bundle readRecordsAs(
-            TestApp testApp,
-            ArrayList<String> recordClassesToRead,
-            Optional<List<String>> dataOriginFilterPackageNames)
-            throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, READ_RECORDS_QUERY);
-        bundle.putStringArrayList(READ_RECORD_CLASS_NAME, recordClassesToRead);
-        if (!dataOriginFilterPackageNames.isEmpty()) {
-            ArrayList<String> dataOrigins = new ArrayList<>();
-            dataOrigins.addAll(dataOriginFilterPackageNames.get());
-            bundle.putBoolean(READ_USING_DATA_ORIGIN_FILTERS, true);
-            bundle.putStringArrayList(DATA_ORIGIN_FILTER_PACKAGE_NAMES, dataOrigins);
-        }
-        return getFromTestApp(testApp, bundle);
-    }
-
-    public static Bundle insertRecordWithGivenClientId(TestApp testApp, double clientId)
-            throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY);
-        bundle.putDouble(CLIENT_ID, clientId);
-
-        return getFromTestApp(testApp, bundle);
-    }
-
-    public static Bundle readRecordsUsingDataOriginFiltersAs(
-            TestApp testApp, ArrayList<String> recordClassesToRead) throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, READ_RECORDS_QUERY);
-        bundle.putStringArrayList(READ_RECORD_CLASS_NAME, recordClassesToRead);
-        bundle.putBoolean(READ_USING_DATA_ORIGIN_FILTERS, true);
-
-        return getFromTestApp(testApp, bundle);
-    }
-
-    public static Bundle readChangeLogsUsingDataOriginFiltersAs(
-            TestApp testApp, String changeLogToken) throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, READ_CHANGE_LOGS_QUERY);
-        bundle.putString(CHANGE_LOG_TOKEN, changeLogToken);
-        bundle.putBoolean(READ_USING_DATA_ORIGIN_FILTERS, true);
-
-        return getFromTestApp(testApp, bundle);
-    }
-
-    public static Bundle getChangeLogTokenAs(
-            TestApp testApp, String pkgName, ArrayList<String> recordClassesToRead)
-            throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, GET_CHANGE_LOG_TOKEN_QUERY);
-        bundle.putString(APP_PKG_NAME_USED_IN_DATA_ORIGIN, pkgName);
-
-        if (recordClassesToRead != null) {
-            bundle.putStringArrayList(READ_RECORD_CLASS_NAME, recordClassesToRead);
-        }
-        return getFromTestApp(testApp, bundle);
-    }
-
-    public static Bundle insertStepsRecordAs(
-            TestApp testApp, String startTime, String endTime, int stepsCount) throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY);
-        bundle.putString(RECORD_TYPE, STEPS_RECORD);
-        bundle.putString(START_TIME, startTime);
-        bundle.putString(END_TIME, endTime);
-        bundle.putInt(STEPS_COUNT, stepsCount);
-
-        return getFromTestApp(testApp, bundle);
-    }
-
-    public static Bundle insertExerciseSessionAs(
-            TestApp testApp,
-            String sessionStartTime,
-            String sessionEndTime,
-            String pauseStart,
-            String pauseEnd)
-            throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY);
-        bundle.putString(RECORD_TYPE, EXERCISE_SESSION);
-        bundle.putString(START_TIME, sessionStartTime);
-        bundle.putString(END_TIME, sessionEndTime);
-        bundle.putString(PAUSE_START, pauseStart);
-        bundle.putString(PAUSE_END, pauseEnd);
-
-        return getFromTestApp(testApp, bundle);
-    }
-
-    private static Bundle getFromTestApp(TestApp testApp, Bundle bundleToCreateIntent)
-            throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        AtomicReference<Bundle> response = new AtomicReference<>();
-        AtomicReference<Exception> exceptionAtomicReference = new AtomicReference<>();
-        BroadcastReceiver broadcastReceiver =
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        if (intent.hasExtra(INTENT_EXCEPTION)) {
-                            exceptionAtomicReference.set(
-                                    (Exception) (intent.getSerializableExtra(INTENT_EXCEPTION)));
-                        } else {
-                            response.set(intent.getExtras());
-                        }
-                        latch.countDown();
-                    }
-                };
-
-        launchTestApp(testApp, bundleToCreateIntent, broadcastReceiver, latch);
-        if (exceptionAtomicReference.get() != null) {
-            throw exceptionAtomicReference.get();
-        }
-        return response.get();
-    }
-
-    private static void launchTestApp(
-            TestApp testApp,
-            Bundle bundleToCreateIntent,
-            BroadcastReceiver broadcastReceiver,
-            CountDownLatch latch)
-            throws Exception {
-
-        // Register broadcast receiver
-        final IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(bundleToCreateIntent.getString(QUERY_TYPE));
-        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
-        getContext().registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED);
-
-        // Launch the test app.
-        final Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.setPackage(testApp.getPackageName());
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        intent.putExtra(INTENT_EXTRA_CALLING_PKG, getContext().getPackageName());
-        intent.putExtras(bundleToCreateIntent);
-        intent.addCategory(Intent.CATEGORY_LAUNCHER);
-        intent.putExtras(bundleToCreateIntent);
-
-        Thread.sleep(500);
-        getContext().startActivity(intent);
-        if (!latch.await(POLLING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
-            final String errorMessage =
-                    "Timed out while waiting to receive "
-                            + bundleToCreateIntent.getString(QUERY_TYPE)
-                            + " intent from "
-                            + testApp.getPackageName();
-            throw new TimeoutException(errorMessage);
-        }
-        getContext().unregisterReceiver(broadcastReceiver);
-    }
-
-    public static List<DataOrigin> getDataOriginPriorityOrder(TestApp testAppA, TestApp testAppB) {
-        return List.of(
-                new DataOrigin.Builder().setPackageName(testAppA.getPackageName()).build(),
-                new DataOrigin.Builder().setPackageName(testAppB.getPackageName()).build());
-    }
-}
diff --git a/tests/cts/hostsidetests/healthconnect/libs/HealthConnectTestLib/src/android/healthconnect/cts/lib/TestAppProxy.java b/tests/cts/hostsidetests/healthconnect/libs/HealthConnectTestLib/src/android/healthconnect/cts/lib/TestAppProxy.java
new file mode 100644
index 0000000..63fb9dc
--- /dev/null
+++ b/tests/cts/hostsidetests/healthconnect/libs/HealthConnectTestLib/src/android/healthconnect/cts/lib/TestAppProxy.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.healthconnect.cts.lib;
+
+import static android.healthconnect.cts.lib.BundleHelper.INTENT_EXCEPTION;
+import static android.healthconnect.cts.lib.BundleHelper.QUERY_TYPE;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.health.connect.ReadRecordsRequestUsingFilters;
+import android.health.connect.RecordIdFilter;
+import android.health.connect.changelog.ChangeLogTokenRequest;
+import android.health.connect.changelog.ChangeLogsRequest;
+import android.health.connect.changelog.ChangeLogsResponse;
+import android.health.connect.datatypes.Record;
+import android.os.Bundle;
+
+import com.android.cts.install.lib.TestApp;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/** Performs API calls to HC on behalf of test apps. */
+public class TestAppProxy {
+    private static final String TEST_APP_RECEIVER_CLASS_NAME =
+            "android.healthconnect.cts.testhelper.TestAppReceiver";
+    private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20);
+
+    private final String mPackageName;
+    private final boolean mInBackground;
+
+    private TestAppProxy(String packageName, boolean inBackground) {
+        mPackageName = packageName;
+        mInBackground = inBackground;
+    }
+
+    /** Create a new {@link TestAppProxy} for given test app. */
+    public static TestAppProxy forApp(TestApp testApp) {
+        return forPackageName(testApp.getPackageName());
+    }
+
+    /** Create a new {@link TestAppProxy} for given package name. */
+    public static TestAppProxy forPackageName(String packageName) {
+        return new TestAppProxy(packageName, false);
+    }
+
+    /**
+     * Create a new {@link TestAppProxy} for given package name which performs calls in the
+     * background.
+     */
+    public static TestAppProxy forPackageNameInBackground(String packageName) {
+        return new TestAppProxy(packageName, true);
+    }
+
+    /** Returns the package name of the app. */
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /** Inserts records to HC on behalf of the app. */
+    public List<String> insertRecords(Record... records) throws Exception {
+        return insertRecords(Arrays.asList(records));
+    }
+
+    /** Inserts records to HC on behalf of the app. */
+    public List<String> insertRecords(List<Record> records) throws Exception {
+        Bundle requestBundle = BundleHelper.fromInsertRecordsRequest(records);
+        Bundle responseBundle = getFromTestApp(requestBundle);
+        return BundleHelper.toInsertRecordsResponse(responseBundle);
+    }
+
+    /** Deletes records from HC on behalf of the app. */
+    public void deleteRecords(RecordIdFilter... recordIdFilters) throws Exception {
+        deleteRecords(Arrays.asList(recordIdFilters));
+    }
+
+    /** Deletes records from HC on behalf of the app. */
+    public void deleteRecords(List<RecordIdFilter> recordIdFilters) throws Exception {
+        Bundle requestBundle = BundleHelper.fromDeleteRecordsByIdsRequest(recordIdFilters);
+        getFromTestApp(requestBundle);
+    }
+
+    /** Updates records in HC on behalf of the app. */
+    public void updateRecords(Record... records) throws Exception {
+        updateRecords(Arrays.asList(records));
+    }
+
+    /** Updates records in HC on behalf of the app. */
+    public void updateRecords(List<Record> records) throws Exception {
+        Bundle requestBundle = BundleHelper.fromUpdateRecordsRequest(records);
+        getFromTestApp(requestBundle);
+    }
+
+    /** Read records from HC on behalf of the app. */
+    public <T extends Record> List<T> readRecords(ReadRecordsRequestUsingFilters<T> request)
+            throws Exception {
+        Bundle requestBundle = BundleHelper.fromReadRecordsRequestUsingFilters(request);
+        Bundle responseBundle = getFromTestApp(requestBundle);
+        return BundleHelper.toReadRecordsResponse(responseBundle);
+    }
+
+    /** Gets changelogs from HC on behalf of the app. */
+    public ChangeLogsResponse getChangeLogs(ChangeLogsRequest request) throws Exception {
+        Bundle requestBundle = BundleHelper.fromChangeLogsRequest(request);
+        Bundle responseBundle = getFromTestApp(requestBundle);
+        return BundleHelper.toChangeLogsResponse(responseBundle);
+    }
+
+    /** Gets a change log token from HC on behalf of the app. */
+    public String getChangeLogToken(ChangeLogTokenRequest request) throws Exception {
+        Bundle requestBundle = BundleHelper.fromChangeLogTokenRequest(request);
+        Bundle responseBundle = getFromTestApp(requestBundle);
+        return BundleHelper.toChangeLogTokenResponse(responseBundle);
+    }
+
+    private Bundle getFromTestApp(Bundle bundleToCreateIntent) throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        AtomicReference<Bundle> response = new AtomicReference<>();
+        AtomicReference<Exception> exceptionAtomicReference = new AtomicReference<>();
+        BroadcastReceiver broadcastReceiver =
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (intent.hasExtra(INTENT_EXCEPTION)) {
+                            exceptionAtomicReference.set(
+                                    (Exception) (intent.getSerializableExtra(INTENT_EXCEPTION)));
+                        } else {
+                            response.set(intent.getExtras());
+                        }
+                        latch.countDown();
+                    }
+                };
+
+        launchTestApp(bundleToCreateIntent, broadcastReceiver, latch);
+        if (exceptionAtomicReference.get() != null) {
+            throw exceptionAtomicReference.get();
+        }
+        return response.get();
+    }
+
+    private void launchTestApp(
+            Bundle bundleToCreateIntent, BroadcastReceiver broadcastReceiver, CountDownLatch latch)
+            throws Exception {
+
+        // Register broadcast receiver
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(bundleToCreateIntent.getString(QUERY_TYPE));
+        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+        getContext().registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED);
+
+        // Launch the test app.
+        Intent intent;
+
+        if (mInBackground) {
+            intent = new Intent().setClassName(mPackageName, TEST_APP_RECEIVER_CLASS_NAME);
+        } else {
+            intent = new Intent(Intent.ACTION_MAIN);
+            intent.setPackage(mPackageName);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        }
+
+        intent.putExtras(bundleToCreateIntent);
+
+        Thread.sleep(500);
+
+        if (mInBackground) {
+            getContext().sendBroadcast(intent);
+        } else {
+            getContext().startActivity(intent);
+        }
+
+        if (!latch.await(POLLING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
+            final String errorMessage =
+                    "Timed out while waiting to receive "
+                            + bundleToCreateIntent.getString(QUERY_TYPE)
+                            + " intent from "
+                            + mPackageName;
+            throw new TimeoutException(errorMessage);
+        }
+        getContext().unregisterReceiver(broadcastReceiver);
+    }
+}
diff --git a/tests/cts/src/android/healthconnect/cts/ActiveCaloriesBurnedRecordTest.java b/tests/cts/src/android/healthconnect/cts/ActiveCaloriesBurnedRecordTest.java
index eecd084..06bfb59 100644
--- a/tests/cts/src/android/healthconnect/cts/ActiveCaloriesBurnedRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/ActiveCaloriesBurnedRecordTest.java
@@ -40,6 +40,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Energy;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -49,6 +50,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -68,6 +70,11 @@
     private static final String TAG = "ActiveCaloriesBurnedRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/BasalBodyTemperatureRecordTest.java b/tests/cts/src/android/healthconnect/cts/BasalBodyTemperatureRecordTest.java
index 2aefe73..123671d 100644
--- a/tests/cts/src/android/healthconnect/cts/BasalBodyTemperatureRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/BasalBodyTemperatureRecordTest.java
@@ -35,6 +35,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Temperature;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -43,6 +44,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +61,11 @@
 public class BasalBodyTemperatureRecordTest {
     private static final String TAG = "BasalBodyTemperatureRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/BasalMetabolicRateRecordTest.java b/tests/cts/src/android/healthconnect/cts/BasalMetabolicRateRecordTest.java
index eb97dc2..274ef1c 100644
--- a/tests/cts/src/android/healthconnect/cts/BasalMetabolicRateRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/BasalMetabolicRateRecordTest.java
@@ -43,6 +43,7 @@
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Energy;
 import android.health.connect.datatypes.units.Power;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -52,6 +53,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -73,6 +75,11 @@
     private static final String TAG = "BasalMetabolicRateRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
@@ -446,7 +453,6 @@
     public void testAggregation_BasalCaloriesBurntTotal_groupByDuration_lbmDerived()
             throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         Instant now = Instant.now();
         List<Record> records =
                 List.of(
@@ -483,7 +489,6 @@
     @Test
     public void testAggregation_BasalCaloriesBurntTotal_profile_group() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         Instant now = Instant.now();
         List<Record> records =
                 List.of(
@@ -743,7 +748,6 @@
     public void testAggregation_BasalCaloriesBurntTotal_groupByDuration_profileDerived()
             throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         Instant now = Instant.now();
         List<Record> records =
                 List.of(
@@ -781,7 +785,6 @@
     @Test
     public void testAggregation_BasalCaloriesBurntTotal() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         List<Record> records =
                 Arrays.asList(
                         getBasalMetabolicRateRecord(30.0, Instant.now().minus(3, ChronoUnit.DAYS)),
@@ -831,7 +834,6 @@
     @Test
     public void testAggregation_BasalCaloriesBurntTotal_groupDuration() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         Instant now = Instant.now();
         List<Record> records =
                 List.of(
@@ -878,7 +880,6 @@
     public void testAggregation_BasalCaloriesBurntTotal_groupDurationLocalFilter()
             throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         Instant now = Instant.now();
         ZoneOffset offset = ZoneOffset.MIN;
         LocalDateTime nowLocal = LocalDateTime.ofInstant(now, offset);
@@ -920,7 +921,6 @@
     @Test
     public void testAggregation_BasalCaloriesBurntTotal_group() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         Instant now = Instant.now();
         List<Record> records =
                 List.of(
@@ -954,7 +954,6 @@
     @Test
     public void testAggregation_BasalCaloriesBurntTotal_profile() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         List<Record> records =
                 List.of(
                         HeightRecordTest.getBaseHeightRecord(
@@ -1188,6 +1187,7 @@
 
     @Test
     public void testAggregate_withDifferentTimeZone() throws Exception {
+        TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
         Instant instant = Instant.now().minus(1, ChronoUnit.DAYS);
         List<Record> records =
                 List.of(
diff --git a/tests/cts/src/android/healthconnect/cts/BloodGlucoseRecordTest.java b/tests/cts/src/android/healthconnect/cts/BloodGlucoseRecordTest.java
index d4e58e0..c4f117b 100644
--- a/tests/cts/src/android/healthconnect/cts/BloodGlucoseRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/BloodGlucoseRecordTest.java
@@ -35,6 +35,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.BloodGlucose;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -43,6 +44,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +61,11 @@
 public class BloodGlucoseRecordTest {
     private static final String TAG = "BloodGlucoseRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/BloodPressureRecordTest.java b/tests/cts/src/android/healthconnect/cts/BloodPressureRecordTest.java
index 72420cc..7d8243c 100644
--- a/tests/cts/src/android/healthconnect/cts/BloodPressureRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/BloodPressureRecordTest.java
@@ -45,6 +45,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Pressure;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -54,6 +55,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -73,6 +75,11 @@
     private static final String TAG = "BloodPressureRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/BodyFatRecordTest.java b/tests/cts/src/android/healthconnect/cts/BodyFatRecordTest.java
index 978f13c..59178b9 100644
--- a/tests/cts/src/android/healthconnect/cts/BodyFatRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/BodyFatRecordTest.java
@@ -35,6 +35,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Percentage;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -43,6 +44,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +61,11 @@
 public class BodyFatRecordTest {
     private static final String TAG = "BodyFatRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/BodyTemperatureRecordTest.java b/tests/cts/src/android/healthconnect/cts/BodyTemperatureRecordTest.java
index 5089e97..fefbcf3 100644
--- a/tests/cts/src/android/healthconnect/cts/BodyTemperatureRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/BodyTemperatureRecordTest.java
@@ -35,6 +35,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Temperature;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -43,6 +44,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +61,11 @@
 public class BodyTemperatureRecordTest {
     private static final String TAG = "BodyTemperatureRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/BodyWaterMassRecordTest.java b/tests/cts/src/android/healthconnect/cts/BodyWaterMassRecordTest.java
index 12d3a6e..b7801fa 100644
--- a/tests/cts/src/android/healthconnect/cts/BodyWaterMassRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/BodyWaterMassRecordTest.java
@@ -35,6 +35,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Mass;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -43,6 +44,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -60,6 +62,11 @@
     private static final String TAG = "BodyWaterMassRecordTest";
     private static final Instant TIME = Instant.ofEpochMilli((long) 1e9);
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/BoneMassRecordTest.java b/tests/cts/src/android/healthconnect/cts/BoneMassRecordTest.java
index 50d9e47..e273ac4 100644
--- a/tests/cts/src/android/healthconnect/cts/BoneMassRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/BoneMassRecordTest.java
@@ -35,6 +35,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Mass;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -43,6 +44,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +61,11 @@
 public class BoneMassRecordTest {
     private static final String TAG = "BoneMassRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/CervicalMucusRecordTest.java b/tests/cts/src/android/healthconnect/cts/CervicalMucusRecordTest.java
index 408a6e8..4f48c7d 100644
--- a/tests/cts/src/android/healthconnect/cts/CervicalMucusRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/CervicalMucusRecordTest.java
@@ -34,6 +34,7 @@
 import android.health.connect.datatypes.Device;
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -42,6 +43,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,6 +60,11 @@
 public class CervicalMucusRecordTest {
     private static final String TAG = "CervicalMucusRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/CyclingPedalingCadenceRecordTest.java b/tests/cts/src/android/healthconnect/cts/CyclingPedalingCadenceRecordTest.java
index ff7a69a..534d6ef 100644
--- a/tests/cts/src/android/healthconnect/cts/CyclingPedalingCadenceRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/CyclingPedalingCadenceRecordTest.java
@@ -42,6 +42,7 @@
 import android.health.connect.datatypes.Device;
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -51,6 +52,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -71,6 +73,11 @@
     private static final String TAG = "CyclingPedalingCadenceRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/DataMigrationTest.java b/tests/cts/src/android/healthconnect/cts/DataMigrationTest.java
index 2bdb49e..c0f6fd8 100644
--- a/tests/cts/src/android/healthconnect/cts/DataMigrationTest.java
+++ b/tests/cts/src/android/healthconnect/cts/DataMigrationTest.java
@@ -75,6 +75,7 @@
 import android.health.connect.migration.PermissionMigrationPayload;
 import android.health.connect.migration.PriorityMigrationPayload;
 import android.health.connect.migration.RecordMigrationPayload;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.os.Build;
 import android.os.OutcomeReceiver;
@@ -207,6 +208,11 @@
         }
     }
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() {
         mTargetContext = InstrumentationRegistry.getTargetContext();
diff --git a/tests/cts/src/android/healthconnect/cts/DistanceRecordTest.java b/tests/cts/src/android/healthconnect/cts/DistanceRecordTest.java
index 547be9f..85412c2 100644
--- a/tests/cts/src/android/healthconnect/cts/DistanceRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/DistanceRecordTest.java
@@ -40,6 +40,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Length;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -50,6 +51,7 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -69,6 +71,11 @@
     private static final String TAG = "DistanceRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
@@ -87,6 +94,9 @@
 
     @BeforeClass
     public static void setup() throws InterruptedException {
+        if (!TestUtils.isHardwareSupported()) {
+            return;
+        }
         TestUtils.verifyDeleteRecords(
                 DistanceRecord.class,
                 new TimeInstantRangeFilter.Builder()
diff --git a/tests/cts/src/android/healthconnect/cts/ElevationGainedRecordTest.java b/tests/cts/src/android/healthconnect/cts/ElevationGainedRecordTest.java
index 7b678e3..9f7ea27 100644
--- a/tests/cts/src/android/healthconnect/cts/ElevationGainedRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/ElevationGainedRecordTest.java
@@ -40,6 +40,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Length;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -49,6 +50,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -68,6 +70,11 @@
     private static final String TAG = "ElevationGainedRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/ExerciseDurationAggregationTest.java b/tests/cts/src/android/healthconnect/cts/ExerciseDurationAggregationTest.java
index 016a1c2..021e6f4 100644
--- a/tests/cts/src/android/healthconnect/cts/ExerciseDurationAggregationTest.java
+++ b/tests/cts/src/android/healthconnect/cts/ExerciseDurationAggregationTest.java
@@ -32,10 +32,12 @@
 import android.health.connect.datatypes.ExerciseSegmentType;
 import android.health.connect.datatypes.ExerciseSessionRecord;
 import android.health.connect.datatypes.ExerciseSessionType;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.time.Duration;
@@ -70,6 +72,11 @@
 
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
@@ -112,7 +119,6 @@
     public void testSimpleAggregation_oneSessionStartEarlierThanWindow_returnsOverlapDuration()
             throws InterruptedException {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY);
-
         ExerciseSessionRecord session =
                 new ExerciseSessionRecord.Builder(
                                 TestUtils.generateMetadata(),
@@ -200,7 +206,6 @@
     public void testAggregationByDuration_oneSession_returnsSplitDurationIntoGroups()
             throws InterruptedException {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY);
-
         Instant endTime = SESSION_START_TIME.plus(10, ChronoUnit.HOURS);
         ExerciseSessionRecord session =
                 new ExerciseSessionRecord.Builder(
@@ -233,7 +238,6 @@
     public void testAggregation_oneSessionLocalTimeFilter_findsSessionWithMinOffset()
             throws InterruptedException {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY);
-
         Instant endTime = Instant.now();
         LocalDateTime endTimeLocal = LocalDateTime.ofInstant(endTime, ZoneOffset.UTC);
 
@@ -266,7 +270,6 @@
     public void testAggregation_oneSessionLocalTimeFilterExcludeSegment_substractsExcludeInterval()
             throws InterruptedException {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY);
-
         Instant endTime = SESSION_START_TIME.plus(1, ChronoUnit.HOURS);
         ExerciseSessionRecord session =
                 new ExerciseSessionRecord.Builder(
diff --git a/tests/cts/src/android/healthconnect/cts/ExerciseRouteDisabledFeatureTest.java b/tests/cts/src/android/healthconnect/cts/ExerciseRouteDisabledFeatureTest.java
index 2c9045a..e16e13e 100644
--- a/tests/cts/src/android/healthconnect/cts/ExerciseRouteDisabledFeatureTest.java
+++ b/tests/cts/src/android/healthconnect/cts/ExerciseRouteDisabledFeatureTest.java
@@ -23,6 +23,7 @@
 import android.health.connect.ReadRecordsRequestUsingIds;
 import android.health.connect.datatypes.ExerciseSessionRecord;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.provider.DeviceConfig;
 
@@ -32,6 +33,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.List;
@@ -40,6 +42,11 @@
     private final UiAutomation mUiAutomation =
             InstrumentationRegistry.getInstrumentation().getUiAutomation();
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         setExerciseRouteFeatureEnabledFlag(true);
diff --git a/tests/cts/src/android/healthconnect/cts/ExerciseSessionRecordTest.java b/tests/cts/src/android/healthconnect/cts/ExerciseSessionRecordTest.java
index 0a70837..7fe77f5 100644
--- a/tests/cts/src/android/healthconnect/cts/ExerciseSessionRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/ExerciseSessionRecordTest.java
@@ -20,6 +20,7 @@
 import static android.healthconnect.cts.utils.TestUtils.SESSION_START_TIME;
 import static android.healthconnect.cts.utils.TestUtils.buildExerciseSession;
 import static android.healthconnect.cts.utils.TestUtils.buildLocationTimePoint;
+import static android.healthconnect.cts.utils.TestUtils.distinctByUuid;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -43,6 +44,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Length;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -50,6 +52,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -64,10 +67,10 @@
 @RunWith(AndroidJUnit4.class)
 public class ExerciseSessionRecordTest {
 
-    /** Constructs a new object. */
-    public ExerciseSessionRecordTest() {
-        super();
-    }
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
 
     @After
     public void tearDown() throws InterruptedException {
@@ -588,6 +591,31 @@
         assertThat(response.getDeletedLogs()).isEmpty();
     }
 
+    @Test
+    public void insertRecords_withDuplicatedClientRecordId_readNoDuplicates() throws Exception {
+        int distinctRecordCount = 10;
+        List<ExerciseSessionRecord> records = new ArrayList<>();
+        Instant now = Instant.now();
+        for (int i = 0; i < distinctRecordCount; i++) {
+            ExerciseSessionRecord record =
+                    buildSession(
+                            /* startTime= */ now.minusSeconds(i + 1),
+                            /* endTime= */ now.minusSeconds(i),
+                            /* clientRecordId= */ "client_id_" + i);
+
+            records.add(record);
+            records.add(record); // Add each record twice
+        }
+
+        List<Record> insertedRecords = TestUtils.insertRecords(records);
+        assertThat(insertedRecords.size()).isEqualTo(records.size());
+
+        List<Record> distinctRecords = distinctByUuid(insertedRecords);
+        assertThat(distinctRecords.size()).isEqualTo(distinctRecordCount);
+
+        readAndAssertEquals(distinctRecords);
+    }
+
     private ExerciseSessionRecord buildRecordWithOneSegment(int sessionType, int segmentType) {
         return new ExerciseSessionRecord.Builder(
                         TestUtils.generateMetadata(),
@@ -641,8 +669,14 @@
     }
 
     private static ExerciseSessionRecord buildSession(Instant startTime, Instant endTime) {
+        return buildSession(
+                startTime, endTime, /* clientRecordId= */ "ExerciseSessionClient" + Math.random());
+    }
+
+    private static ExerciseSessionRecord buildSession(
+            Instant startTime, Instant endTime, String clientRecordId) {
         return new ExerciseSessionRecord.Builder(
-                        TestUtils.generateMetadata(),
+                        buildMetadata(clientRecordId),
                         startTime,
                         endTime,
                         ExerciseSessionType.EXERCISE_SESSION_TYPE_FOOTBALL_AMERICAN)
@@ -656,18 +690,22 @@
 
     private static ExerciseSessionRecord buildSessionMinimal() {
         return new ExerciseSessionRecord.Builder(
-                        new Metadata.Builder()
-                                .setDataOrigin(
-                                        new DataOrigin.Builder()
-                                                .setPackageName("android.healthconnect.cts")
-                                                .build())
-                                .setId(UUID.randomUUID().toString())
-                                .setClientRecordId("ExerciseSessionClient" + Math.random())
-                                .setRecordingMethod(Metadata.RECORDING_METHOD_ACTIVELY_RECORDED)
-                                .build(),
+                        buildMetadata("ExerciseSessionClient" + Math.random()),
                         SESSION_START_TIME,
                         SESSION_END_TIME,
                         ExerciseSessionType.EXERCISE_SESSION_TYPE_FOOTBALL_AMERICAN)
                 .build();
     }
+
+    private static Metadata buildMetadata(String clientRecordId) {
+        return new Metadata.Builder()
+                .setDataOrigin(
+                        new DataOrigin.Builder()
+                                .setPackageName("android.healthconnect.cts")
+                                .build())
+                .setId(UUID.randomUUID().toString())
+                .setClientRecordId(clientRecordId)
+                .setRecordingMethod(Metadata.RECORDING_METHOD_ACTIVELY_RECORDED)
+                .build();
+    }
 }
diff --git a/tests/cts/src/android/healthconnect/cts/FloorsClimbedRecordTest.java b/tests/cts/src/android/healthconnect/cts/FloorsClimbedRecordTest.java
index e2b3ff6..9d3badd 100644
--- a/tests/cts/src/android/healthconnect/cts/FloorsClimbedRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/FloorsClimbedRecordTest.java
@@ -38,6 +38,7 @@
 import android.health.connect.datatypes.FloorsClimbedRecord;
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -46,6 +47,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -63,6 +65,11 @@
     private static final String TAG = "FloorsClimbedRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/GetActivityDatesTest.java b/tests/cts/src/android/healthconnect/cts/GetActivityDatesTest.java
index ff009ba..37b2cc6 100644
--- a/tests/cts/src/android/healthconnect/cts/GetActivityDatesTest.java
+++ b/tests/cts/src/android/healthconnect/cts/GetActivityDatesTest.java
@@ -28,12 +28,14 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.StepsRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -49,6 +51,11 @@
 public class GetActivityDatesTest {
     private static final String TAG = "GetActivityDatesTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/GetApplicationInfoTest.java b/tests/cts/src/android/healthconnect/cts/GetApplicationInfoTest.java
index 4e5a497..fbdbe48 100644
--- a/tests/cts/src/android/healthconnect/cts/GetApplicationInfoTest.java
+++ b/tests/cts/src/android/healthconnect/cts/GetApplicationInfoTest.java
@@ -26,6 +26,7 @@
 import android.health.connect.HealthConnectException;
 import android.health.connect.HealthConnectManager;
 import android.health.connect.datatypes.AppInfo;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.os.OutcomeReceiver;
 import android.platform.test.annotations.AppModeFull;
@@ -36,6 +37,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -52,6 +54,11 @@
     private static final UiAutomation sUiAutomation =
             InstrumentationRegistry.getInstrumentation().getUiAutomation();
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     /** TODO(b/257796081): Cleanup the database after each test. */
     @Test
     public void testEmptyApplicationInfo() throws InterruptedException {
diff --git a/tests/cts/src/android/healthconnect/cts/HealthConnectAccessLogsTest.java b/tests/cts/src/android/healthconnect/cts/HealthConnectAccessLogsTest.java
index d69d942..35ba485 100644
--- a/tests/cts/src/android/healthconnect/cts/HealthConnectAccessLogsTest.java
+++ b/tests/cts/src/android/healthconnect/cts/HealthConnectAccessLogsTest.java
@@ -28,6 +28,7 @@
 import android.health.connect.datatypes.HeartRateRecord;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.StepsRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -35,6 +36,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -45,6 +47,12 @@
 @AppModeFull(reason = "HealthConnectManager is not accessible to instant apps")
 @RunWith(AndroidJUnit4.class)
 public class HealthConnectAccessLogsTest {
+
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         Context context = ApplicationProvider.getApplicationContext();
diff --git a/tests/cts/src/android/healthconnect/cts/HealthConnectChangeLogsTests.java b/tests/cts/src/android/healthconnect/cts/HealthConnectChangeLogsTests.java
index 33a3187..1bc2ec4 100644
--- a/tests/cts/src/android/healthconnect/cts/HealthConnectChangeLogsTests.java
+++ b/tests/cts/src/android/healthconnect/cts/HealthConnectChangeLogsTests.java
@@ -27,6 +27,7 @@
 import android.health.connect.datatypes.DataOrigin;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.StepsRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -34,7 +35,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -48,6 +49,11 @@
 @RunWith(AndroidJUnit4.class)
 public class HealthConnectChangeLogsTests {
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         Context context = ApplicationProvider.getApplicationContext();
diff --git a/tests/cts/src/android/healthconnect/cts/HealthConnectManagerTest.java b/tests/cts/src/android/healthconnect/cts/HealthConnectManagerTest.java
index 5538d10..1bf2de0 100644
--- a/tests/cts/src/android/healthconnect/cts/HealthConnectManagerTest.java
+++ b/tests/cts/src/android/healthconnect/cts/HealthConnectManagerTest.java
@@ -80,6 +80,7 @@
 import android.health.connect.datatypes.units.Power;
 import android.health.connect.datatypes.units.Volume;
 import android.health.connect.restore.StageRemoteDataException;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
@@ -95,6 +96,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -126,6 +128,11 @@
     private static final String TAG = "HealthConnectManagerTest";
     private static final String APP_PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void before() throws InterruptedException {
         deleteAllRecords();
diff --git a/tests/cts/src/android/healthconnect/cts/HealthPermissionCategoryPriorityTests.java b/tests/cts/src/android/healthconnect/cts/HealthPermissionCategoryPriorityTests.java
index ac1a3b2..be8f4ac 100644
--- a/tests/cts/src/android/healthconnect/cts/HealthPermissionCategoryPriorityTests.java
+++ b/tests/cts/src/android/healthconnect/cts/HealthPermissionCategoryPriorityTests.java
@@ -33,6 +33,7 @@
 import android.app.UiAutomation;
 import android.health.connect.FetchDataOriginsPriorityOrderResponse;
 import android.health.connect.HealthConnectException;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.InstrumentationRegistry;
@@ -41,6 +42,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +61,11 @@
     public static final String PACKAGE_NAME = "android.healthconnect.cts";
     public static final String OTHER_PACKAGE_NAME = "";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/HealthServicesInitializerTest.java b/tests/cts/src/android/healthconnect/cts/HealthServicesInitializerTest.java
new file mode 100644
index 0000000..f2fbd17
--- /dev/null
+++ b/tests/cts/src/android/healthconnect/cts/HealthServicesInitializerTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.healthconnect.cts;
+
+import static android.healthconnect.cts.utils.TestUtils.isHardwareSupported;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.health.connect.HealthServicesInitializer;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+
+public class HealthServicesInitializerTest {
+    /**
+     * HealthServicesInitializer.registerServiceWrappers() should only be called by
+     * SystemServiceRegistry during boot up. Calling this API at any other time should throw an
+     * exception.
+     */
+    @Test
+    public void testRegisterServiceThrowsException() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        // skip the test if the hardware not supported
+        if (!isHardwareSupported(context)) {
+            return;
+        }
+        assertThrows(
+                IllegalStateException.class, HealthServicesInitializer::registerServiceWrappers);
+    }
+
+    /**
+     * context.getSystemService(Context.HEALTHCONNECT_SERVICE) returns the services on supported
+     * devices.
+     */
+    @Test
+    public void testHealthServiceRegistered() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        Object service = context.getSystemService(Context.HEALTHCONNECT_SERVICE);
+        if (isHardwareSupported(context)) {
+            assertThat(service).isNotNull();
+        } else {
+            assertThat(service).isNull();
+        }
+    }
+}
diff --git a/tests/cts/src/android/healthconnect/cts/HeartRateRecordTest.java b/tests/cts/src/android/healthconnect/cts/HeartRateRecordTest.java
index 623c8ed..2567079 100644
--- a/tests/cts/src/android/healthconnect/cts/HeartRateRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/HeartRateRecordTest.java
@@ -46,6 +46,7 @@
 import android.health.connect.datatypes.HeartRateRecord;
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -55,6 +56,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -74,6 +76,12 @@
 @RunWith(AndroidJUnit4.class)
 public class HeartRateRecordTest {
     private static final String TAG = "HeartRateRecordTest";
+    private static final String PACKAGE_NAME = "android.healthconnect.cts";
+
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
 
     @After
     public void tearDown() throws InterruptedException {
@@ -86,8 +94,6 @@
         TestUtils.deleteAllStagedRemoteData();
     }
 
-    private static final String PACKAGE_NAME = "android.healthconnect.cts";
-
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
@@ -538,7 +544,6 @@
     @Test
     public void testBpmAggregation_timeRange_not_present() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.VITALS);
-
         List<Record> records =
                 Arrays.asList(
                         TestUtils.getHeartRateRecord(71),
@@ -567,7 +572,6 @@
     @Test
     public void testBpmAggregation_withDataOrigin_correct() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.VITALS);
-
         Context context = ApplicationProvider.getApplicationContext();
         List<Record> records =
                 Arrays.asList(
@@ -607,7 +611,6 @@
     @Test
     public void testBpmAggregation_withDataOrigin_incorrect() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.VITALS);
-
         List<Record> records =
                 Arrays.asList(
                         TestUtils.getHeartRateRecord(71),
@@ -687,7 +690,6 @@
     @Test
     public void testBpmAggregation_groupByDuration() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.VITALS);
-
         Instant start = Instant.now().minus(3, ChronoUnit.DAYS);
         Instant end = start.plus(3, ChronoUnit.DAYS);
         insertHeartRateRecordsInPastDays(4);
@@ -754,7 +756,6 @@
     @Test
     public void testHeartAggregation_measurement_count() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.VITALS);
-
         List<Record> records =
                 Arrays.asList(
                         getBaseHeartRateRecord(71),
@@ -909,7 +910,6 @@
     @Test
     public void testAggregateLocalFilter_minOffsetRecord() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.VITALS);
-
         LocalDateTime endTimeLocal = LocalDateTime.now(ZoneOffset.UTC);
         Instant endTimeInstant = Instant.now();
 
diff --git a/tests/cts/src/android/healthconnect/cts/HeartRateVariabilityRmssdRecordTest.java b/tests/cts/src/android/healthconnect/cts/HeartRateVariabilityRmssdRecordTest.java
index 72a0fbb..d53de23 100644
--- a/tests/cts/src/android/healthconnect/cts/HeartRateVariabilityRmssdRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/HeartRateVariabilityRmssdRecordTest.java
@@ -34,6 +34,7 @@
 import android.health.connect.datatypes.HeartRateVariabilityRmssdRecord;
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -42,6 +43,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +61,11 @@
     private static final String TAG = "HeartRateVariabilityRmssdRecordTest";
     private static final Instant TIME = Instant.ofEpochMilli((long) 1e9);
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/HeightRecordTest.java b/tests/cts/src/android/healthconnect/cts/HeightRecordTest.java
index 5022b9c..3a58428 100644
--- a/tests/cts/src/android/healthconnect/cts/HeightRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/HeightRecordTest.java
@@ -19,6 +19,8 @@
 import static android.health.connect.datatypes.HeightRecord.HEIGHT_AVG;
 import static android.health.connect.datatypes.HeightRecord.HEIGHT_MAX;
 import static android.health.connect.datatypes.HeightRecord.HEIGHT_MIN;
+import static android.healthconnect.cts.utils.TestUtils.distinctByUuid;
+import static android.healthconnect.cts.utils.TestUtils.insertRecords;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -42,6 +44,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Length;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -51,6 +54,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -70,6 +74,11 @@
     private static final String TAG = "HeightRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
@@ -522,6 +531,26 @@
                 .build();
     }
 
+    @Test
+    public void insertRecords_withDuplicatedClientRecordId_readNoDuplicates() throws Exception {
+        int distinctRecordCount = 10;
+        List<HeightRecord> records = new ArrayList<>();
+        Instant now = Instant.now();
+        for (int i = 0; i < distinctRecordCount; i++) {
+            HeightRecord record =
+                    getCompleteHeightRecord(
+                            now.minusMillis(i), /* clientRecordId= */ "client_id_" + i);
+
+            records.add(record);
+            records.add(record); // Add each record twice
+        }
+
+        List<Record> distinctRecords = distinctByUuid(insertRecords(records));
+        assertThat(distinctRecords.size()).isEqualTo(distinctRecordCount);
+
+        readHeightRecordUsingIds(distinctRecords);
+    }
+
     HeightRecord getHeightRecord_update(Record record, String id, String clientRecordId) {
         Metadata metadata = record.getMetadata();
         Metadata metadataWithId =
@@ -561,6 +590,10 @@
     }
 
     private static HeightRecord getCompleteHeightRecord() {
+        return getCompleteHeightRecord(Instant.now(), /* clientRecordId= */ "HR" + Math.random());
+    }
+
+    private static HeightRecord getCompleteHeightRecord(Instant time, String clientRecordId) {
         Device device =
                 new Device.Builder()
                         .setManufacturer("google")
@@ -571,11 +604,10 @@
                 new DataOrigin.Builder().setPackageName("android.healthconnect.cts").build();
         Metadata.Builder testMetadataBuilder = new Metadata.Builder();
         testMetadataBuilder.setDevice(device).setDataOrigin(dataOrigin);
-        testMetadataBuilder.setClientRecordId("HR" + Math.random());
+        testMetadataBuilder.setClientRecordId(clientRecordId);
         testMetadataBuilder.setRecordingMethod(Metadata.RECORDING_METHOD_ACTIVELY_RECORDED);
 
-        return new HeightRecord.Builder(
-                        testMetadataBuilder.build(), Instant.now(), Length.fromMeters(1.0))
+        return new HeightRecord.Builder(testMetadataBuilder.build(), time, Length.fromMeters(1.0))
                 .setZoneOffset(ZoneOffset.systemDefault().getRules().getOffset(Instant.now()))
                 .build();
     }
diff --git a/tests/cts/src/android/healthconnect/cts/HistoricAccessLimitTest.java b/tests/cts/src/android/healthconnect/cts/HistoricAccessLimitTest.java
index 86c17d4..21487b5 100644
--- a/tests/cts/src/android/healthconnect/cts/HistoricAccessLimitTest.java
+++ b/tests/cts/src/android/healthconnect/cts/HistoricAccessLimitTest.java
@@ -27,6 +27,7 @@
 import android.health.connect.datatypes.StepsRecord;
 import android.health.connect.datatypes.WeightRecord;
 import android.health.connect.datatypes.units.Mass;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestReceiver;
 import android.healthconnect.cts.utils.TestUtils;
 import android.os.Bundle;
@@ -37,6 +38,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -53,6 +55,11 @@
 
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/tests/cts/src/android/healthconnect/cts/HydrationRecordTest.java b/tests/cts/src/android/healthconnect/cts/HydrationRecordTest.java
index 28b1b65..d5506f8 100644
--- a/tests/cts/src/android/healthconnect/cts/HydrationRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/HydrationRecordTest.java
@@ -40,16 +40,16 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Volume;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
 import java.time.Instant;
 import java.time.ZoneOffset;
@@ -61,11 +61,15 @@
 import java.util.Set;
 import java.util.UUID;
 
-@RunWith(AndroidJUnit4.class)
 public class HydrationRecordTest {
     private static final String TAG = "HydrationRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/IntermenstrualBleedingRecordTest.java b/tests/cts/src/android/healthconnect/cts/IntermenstrualBleedingRecordTest.java
index f706f88..a70f2ac 100644
--- a/tests/cts/src/android/healthconnect/cts/IntermenstrualBleedingRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/IntermenstrualBleedingRecordTest.java
@@ -34,6 +34,7 @@
 import android.health.connect.datatypes.IntermenstrualBleedingRecord;
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -42,6 +43,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +61,11 @@
     private static final String TAG = "IntermenstrualBleedingRecordTest";
     private static final Instant TIME = Instant.ofEpochMilli((long) 1e9);
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/LeanBodyMassRecordTest.java b/tests/cts/src/android/healthconnect/cts/LeanBodyMassRecordTest.java
index cde7e32..05d3e51 100644
--- a/tests/cts/src/android/healthconnect/cts/LeanBodyMassRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/LeanBodyMassRecordTest.java
@@ -35,6 +35,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Mass;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -43,6 +44,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +61,11 @@
 public class LeanBodyMassRecordTest {
     private static final String TAG = "LeanBodyMassRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/MenstruationFlowRecordTest.java b/tests/cts/src/android/healthconnect/cts/MenstruationFlowRecordTest.java
index f48a814..c6296bf 100644
--- a/tests/cts/src/android/healthconnect/cts/MenstruationFlowRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/MenstruationFlowRecordTest.java
@@ -34,6 +34,7 @@
 import android.health.connect.datatypes.MenstruationFlowRecord;
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -42,6 +43,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,6 +60,11 @@
 public class MenstruationFlowRecordTest {
     private static final String TAG = "MenstruationFlowRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/MenstruationPeriodRecordTest.java b/tests/cts/src/android/healthconnect/cts/MenstruationPeriodRecordTest.java
index f87fd26..adb3b42 100644
--- a/tests/cts/src/android/healthconnect/cts/MenstruationPeriodRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/MenstruationPeriodRecordTest.java
@@ -34,6 +34,7 @@
 import android.health.connect.datatypes.MenstruationPeriodRecord;
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -42,6 +43,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -60,6 +62,11 @@
     private static final Instant START_TIME = Instant.ofEpochMilli((long) 1e9);
     private static final Instant END_TIME = Instant.ofEpochMilli((long) 1e10);
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/NutritionRecordTest.java b/tests/cts/src/android/healthconnect/cts/NutritionRecordTest.java
index 0c3848f..7d9f08b 100644
--- a/tests/cts/src/android/healthconnect/cts/NutritionRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/NutritionRecordTest.java
@@ -83,6 +83,7 @@
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Energy;
 import android.health.connect.datatypes.units.Mass;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -91,6 +92,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -107,6 +109,7 @@
 @RunWith(AndroidJUnit4.class)
 public class NutritionRecordTest {
     private static final String TAG = "NutritionRecordTest";
+    private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
     private List<AggregationType<Mass>> mMassAggregateTypesList =
             Arrays.asList(
@@ -151,7 +154,10 @@
                     VITAMIN_K_TOTAL,
                     ZINC_TOTAL);
 
-    private static final String PACKAGE_NAME = "android.healthconnect.cts";
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
 
     @Before
     public void setUp() throws InterruptedException {
@@ -546,7 +552,6 @@
     @Test
     public void testAggregation_NutritionEnergyValuesTotal() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.NUTRITION);
-
         List<Record> records = Arrays.asList(getCompleteNutritionRecord());
         AggregateRecordsResponse<Energy> oldResponse =
                 TestUtils.getAggregateResponse(
diff --git a/tests/cts/src/android/healthconnect/cts/OvulationTestRecordTest.java b/tests/cts/src/android/healthconnect/cts/OvulationTestRecordTest.java
index 4d08043..dded381 100644
--- a/tests/cts/src/android/healthconnect/cts/OvulationTestRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/OvulationTestRecordTest.java
@@ -34,6 +34,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.OvulationTestRecord;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -42,6 +43,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,6 +60,11 @@
 public class OvulationTestRecordTest {
     private static final String TAG = "OvulationTestRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/OxygenSaturationRecordTest.java b/tests/cts/src/android/healthconnect/cts/OxygenSaturationRecordTest.java
index 559e518..49bdb7e 100644
--- a/tests/cts/src/android/healthconnect/cts/OxygenSaturationRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/OxygenSaturationRecordTest.java
@@ -35,6 +35,7 @@
 import android.health.connect.datatypes.OxygenSaturationRecord;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Percentage;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -43,6 +44,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +61,11 @@
 public class OxygenSaturationRecordTest {
     private static final String TAG = "OxygenSaturationRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/PowerRecordTest.java b/tests/cts/src/android/healthconnect/cts/PowerRecordTest.java
index e75f7be..96d1c8a 100644
--- a/tests/cts/src/android/healthconnect/cts/PowerRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/PowerRecordTest.java
@@ -42,6 +42,7 @@
 import android.health.connect.datatypes.PowerRecord;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Power;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -51,6 +52,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -70,6 +72,11 @@
     private static final String TAG = "PowerRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/RespiratoryRateRecordTest.java b/tests/cts/src/android/healthconnect/cts/RespiratoryRateRecordTest.java
index e816d51..6ebb7ae 100644
--- a/tests/cts/src/android/healthconnect/cts/RespiratoryRateRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/RespiratoryRateRecordTest.java
@@ -34,6 +34,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.RespiratoryRateRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -42,6 +43,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,6 +60,11 @@
 public class RespiratoryRateRecordTest {
     private static final String TAG = "RespiratoryRateRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/RestingHeartRateRecordTest.java b/tests/cts/src/android/healthconnect/cts/RestingHeartRateRecordTest.java
index dbfc425..570e7e6 100644
--- a/tests/cts/src/android/healthconnect/cts/RestingHeartRateRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/RestingHeartRateRecordTest.java
@@ -37,6 +37,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.RestingHeartRateRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -46,6 +47,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -65,6 +67,11 @@
     private static final String TAG = "RestingHeartRateRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/SessionDatatypeDisabledFeatureTest.java b/tests/cts/src/android/healthconnect/cts/SessionDatatypeDisabledFeatureTest.java
index cfe1f15..d079dac 100644
--- a/tests/cts/src/android/healthconnect/cts/SessionDatatypeDisabledFeatureTest.java
+++ b/tests/cts/src/android/healthconnect/cts/SessionDatatypeDisabledFeatureTest.java
@@ -25,6 +25,7 @@
 import android.health.connect.datatypes.ExerciseSessionRecord;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.SleepSessionRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.provider.DeviceConfig;
 
@@ -34,6 +35,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.time.Instant;
@@ -49,6 +51,11 @@
                     .setEndTime(Instant.now())
                     .build();
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         setSessionDatatypesFeatureEnabledFlag(true);
diff --git a/tests/cts/src/android/healthconnect/cts/SexualActivityRecordTest.java b/tests/cts/src/android/healthconnect/cts/SexualActivityRecordTest.java
index 50cc1c6..f76f18c 100644
--- a/tests/cts/src/android/healthconnect/cts/SexualActivityRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/SexualActivityRecordTest.java
@@ -34,6 +34,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.SexualActivityRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -42,6 +43,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,6 +60,11 @@
 public class SexualActivityRecordTest {
     private static final String TAG = "SexualActivityRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/SharedMemoryTest.java b/tests/cts/src/android/healthconnect/cts/SharedMemoryTest.java
index 60d5458..6aa64a9 100644
--- a/tests/cts/src/android/healthconnect/cts/SharedMemoryTest.java
+++ b/tests/cts/src/android/healthconnect/cts/SharedMemoryTest.java
@@ -42,12 +42,14 @@
 import android.health.connect.datatypes.WeightRecord;
 import android.health.connect.datatypes.units.Length;
 import android.health.connect.datatypes.units.Mass;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -61,6 +63,11 @@
 @RunWith(AndroidJUnit4.class)
 public class SharedMemoryTest {
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void before() {
         deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/SleepDurationAggregationTest.java b/tests/cts/src/android/healthconnect/cts/SleepDurationAggregationTest.java
index c7b0b5f..9d1dbac 100644
--- a/tests/cts/src/android/healthconnect/cts/SleepDurationAggregationTest.java
+++ b/tests/cts/src/android/healthconnect/cts/SleepDurationAggregationTest.java
@@ -28,10 +28,12 @@
 import android.health.connect.HealthDataCategory;
 import android.health.connect.TimeInstantRangeFilter;
 import android.health.connect.datatypes.SleepSessionRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.time.Duration;
@@ -54,6 +56,11 @@
 
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
@@ -92,7 +99,6 @@
     public void testSimpleAggregation_oneSessionWithAwake_returnsDurationMinusAwake()
             throws InterruptedException {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.SLEEP);
-
         SleepSessionRecord.Stage awakeStage =
                 new SleepSessionRecord.Stage(
                         SESSION_START_TIME,
@@ -139,7 +145,6 @@
     public void testAggregationByDuration_oneSession_returnsSplitDurationIntoGroups()
             throws InterruptedException {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.SLEEP);
-
         Instant endTime = SESSION_START_TIME.plus(10, ChronoUnit.HOURS);
         SleepSessionRecord session =
                 new SleepSessionRecord.Builder(
diff --git a/tests/cts/src/android/healthconnect/cts/SleepSessionRecordTest.java b/tests/cts/src/android/healthconnect/cts/SleepSessionRecordTest.java
index 3bd0a15..570e10b 100644
--- a/tests/cts/src/android/healthconnect/cts/SleepSessionRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/SleepSessionRecordTest.java
@@ -38,12 +38,14 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.SleepSessionRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.time.Instant;
@@ -62,6 +64,11 @@
     private static final CharSequence NOTES = "felt sleepy";
     private static final CharSequence TITLE = "Afternoon nap";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/SpeedRecordTest.java b/tests/cts/src/android/healthconnect/cts/SpeedRecordTest.java
index 7677a42..29af4cc 100644
--- a/tests/cts/src/android/healthconnect/cts/SpeedRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/SpeedRecordTest.java
@@ -19,6 +19,7 @@
 import static android.health.connect.datatypes.SpeedRecord.SPEED_AVG;
 import static android.health.connect.datatypes.SpeedRecord.SPEED_MAX;
 import static android.health.connect.datatypes.SpeedRecord.SPEED_MIN;
+import static android.healthconnect.cts.utils.TestUtils.distinctByUuid;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -43,6 +44,7 @@
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.SpeedRecord;
 import android.health.connect.datatypes.units.Velocity;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -52,6 +54,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -72,6 +75,11 @@
     private static final String TAG = "SpeedRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
@@ -553,6 +561,33 @@
         readSpeedRecordUsingIds(insertedRecords);
     }
 
+    @Test
+    public void insertRecords_withDuplicatedClientRecordId_readNoDuplicates() throws Exception {
+        int distinctRecordCount = 10;
+        List<SpeedRecord> records = new ArrayList<>();
+        Instant now = Instant.now();
+        for (int i = 0; i < distinctRecordCount; i++) {
+            SpeedRecord record =
+                    buildRecordForSpeed(
+                            /* speed= */ 10,
+                            /* millisFromStart= */ 0,
+                            /* startTime= */ now.minusMillis(i + 1),
+                            /* endTime= */ now.minusMillis(i),
+                            /* clientRecordId= */ "client_id_" + i);
+
+            records.add(record);
+            records.add(record); // Add each record twice
+        }
+
+        List<Record> insertedRecords = TestUtils.insertRecords(records);
+        assertThat(insertedRecords.size()).isEqualTo(records.size());
+
+        List<Record> distinctRecords = distinctByUuid(insertedRecords);
+        assertThat(distinctRecords.size()).isEqualTo(distinctRecordCount);
+
+        readSpeedRecordUsingIds(distinctRecords);
+    }
+
     SpeedRecord getSpeedRecord_update(Record record, String id, String clientRecordId) {
         Metadata metadata = record.getMetadata();
         Metadata metadataWithId =
@@ -600,6 +635,20 @@
     }
 
     private static SpeedRecord buildRecordForSpeed(double speed, long millisFromStart) {
+        return buildRecordForSpeed(
+                speed,
+                millisFromStart,
+                /* startTime= */ Instant.now(),
+                /* endTime= */ Instant.now().plusMillis(1000),
+                /* clientRecordId= */ "SPR" + Math.random());
+    }
+
+    private static SpeedRecord buildRecordForSpeed(
+            double speed,
+            long millisFromStart,
+            Instant startTime,
+            Instant endTime,
+            String clientRecordId) {
 
         Device device =
                 new Device.Builder()
@@ -611,23 +660,19 @@
                 new DataOrigin.Builder().setPackageName("android.healthconnect.cts").build();
         Metadata.Builder testMetadataBuilder = new Metadata.Builder();
         testMetadataBuilder.setDevice(device).setDataOrigin(dataOrigin);
-        testMetadataBuilder.setClientRecordId("SPR" + Math.random());
+        testMetadataBuilder.setClientRecordId(clientRecordId);
         testMetadataBuilder.setRecordingMethod(Metadata.RECORDING_METHOD_ACTIVELY_RECORDED);
 
         SpeedRecord.SpeedRecordSample speedRecord =
                 new SpeedRecord.SpeedRecordSample(
-                        Velocity.fromMetersPerSecond(speed),
-                        Instant.now().plusMillis(millisFromStart));
+                        Velocity.fromMetersPerSecond(speed), startTime.plusMillis(millisFromStart));
 
         ArrayList<SpeedRecord.SpeedRecordSample> speedRecords = new ArrayList<>();
         speedRecords.add(speedRecord);
         speedRecords.add(speedRecord);
 
         return new SpeedRecord.Builder(
-                        testMetadataBuilder.build(),
-                        Instant.now(),
-                        Instant.now().plusMillis(1000),
-                        speedRecords)
+                        testMetadataBuilder.build(), startTime, endTime, speedRecords)
                 .build();
     }
 }
diff --git a/tests/cts/src/android/healthconnect/cts/StepsCadenceRecordTest.java b/tests/cts/src/android/healthconnect/cts/StepsCadenceRecordTest.java
index 1465a94..197af79 100644
--- a/tests/cts/src/android/healthconnect/cts/StepsCadenceRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/StepsCadenceRecordTest.java
@@ -42,6 +42,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.StepsCadenceRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -51,6 +52,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -72,6 +74,11 @@
 
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/StepsRecordTest.java b/tests/cts/src/android/healthconnect/cts/StepsRecordTest.java
index 7b6b40e..d453ec3 100644
--- a/tests/cts/src/android/healthconnect/cts/StepsRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/StepsRecordTest.java
@@ -18,10 +18,13 @@
 
 import static android.health.connect.HealthConnectException.ERROR_INVALID_ARGUMENT;
 import static android.health.connect.datatypes.StepsRecord.STEPS_COUNT_TOTAL;
+import static android.healthconnect.cts.utils.TestUtils.distinctByUuid;
 import static android.healthconnect.cts.utils.TestUtils.readRecordsWithPagination;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
 import static java.time.ZoneOffset.UTC;
 import static java.time.temporal.ChronoUnit.DAYS;
 import static java.time.temporal.ChronoUnit.HOURS;
@@ -48,6 +51,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.StepsRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -57,6 +61,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -79,6 +84,11 @@
     private static final String TAG = "StepsRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
@@ -127,7 +137,7 @@
                 Arrays.asList(
                         TestUtils.getCompleteStepsRecord(), TestUtils.getCompleteStepsRecord());
         List<Record> insertedRecords = TestUtils.insertRecords(recordList);
-        readStepsRecordUsingIds(insertedRecords);
+        assertStepsRecordUsingIds(insertedRecords);
     }
 
     @Test
@@ -606,6 +616,25 @@
     }
 
     @Test
+    public void testDeleteStepsRecords_usingInvalidId() throws InterruptedException {
+        List<RecordIdFilter> recordIds =
+                Collections.singletonList(RecordIdFilter.fromId(StepsRecord.class, "foo"));
+        HealthConnectException e =
+                assertThrows(
+                        HealthConnectException.class,
+                        () -> TestUtils.verifyDeleteRecords(recordIds));
+        assertThat(e.getErrorCode()).isEqualTo(ERROR_INVALID_ARGUMENT);
+    }
+
+    @Test
+    public void testDeleteStepsRecord_usingUnknownId() throws InterruptedException {
+        List<RecordIdFilter> recordIds =
+                Collections.singletonList(
+                        RecordIdFilter.fromId(StepsRecord.class, UUID.randomUUID().toString()));
+        TestUtils.verifyDeleteRecords(recordIds);
+    }
+
+    @Test
     public void testDeleteStepsRecord_usingInvalidClientIds() throws InterruptedException {
         List<Record> records = List.of(getBaseStepsRecord(), TestUtils.getCompleteStepsRecord());
         List<Record> insertedRecord = TestUtils.insertRecords(records);
@@ -678,7 +707,7 @@
     }
 
     @Test
-    public void testAggregation_stepsCountTotal() throws Exception {
+    public void testAggregation_StepsCountTotal() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY);
         List<Record> records =
                 Arrays.asList(getStepsRecord(1000, 1, 1), getStepsRecord(1000, 2, 1));
@@ -958,7 +987,7 @@
                                 TestUtils.getCompleteStepsRecord()));
 
         // read inserted records and verify that the data is same as inserted.
-        readStepsRecordUsingIds(insertedRecords);
+        assertStepsRecordUsingIds(insertedRecords);
 
         // Generate a new set of records that will be used to perform the update operation.
         List<Record> updateRecords =
@@ -978,7 +1007,7 @@
         TestUtils.updateRecords(updateRecords);
 
         // assert the inserted data has been modified by reading the data.
-        readStepsRecordUsingIds(updateRecords);
+        assertStepsRecordUsingIds(updateRecords);
     }
 
     @Test
@@ -991,7 +1020,7 @@
                                 TestUtils.getCompleteStepsRecord()));
 
         // read inserted records and verify that the data is same as inserted.
-        readStepsRecordUsingIds(insertedRecords);
+        assertStepsRecordUsingIds(insertedRecords);
 
         // Generate a second set of records that will be used to perform the update operation.
         List<Record> updateRecords =
@@ -1022,7 +1051,7 @@
         }
 
         // assert the inserted data has not been modified by reading the data.
-        readStepsRecordUsingIds(insertedRecords);
+        assertStepsRecordUsingIds(insertedRecords);
     }
 
     @Test
@@ -1035,7 +1064,7 @@
                                 TestUtils.getCompleteStepsRecord()));
 
         // read inserted records and verify that the data is same as inserted.
-        readStepsRecordUsingIds(insertedRecords);
+        assertStepsRecordUsingIds(insertedRecords);
 
         // Generate a second set of records that will be used to perform the update operation.
         List<Record> updateRecords =
@@ -1063,7 +1092,7 @@
         }
 
         // assert the inserted data has not been modified by reading the data.
-        readStepsRecordUsingIds(insertedRecords);
+        assertStepsRecordUsingIds(insertedRecords);
     }
 
     @Test
@@ -1462,6 +1491,31 @@
         testAggregateDurationWithLocalTimeForZoneOffset(ZoneOffset.MAX);
     }
 
+    @Test
+    public void insertRecords_withDuplicatedClientRecordId_readNoDuplicates() throws Exception {
+        int distinctRecordCount = 10;
+        List<StepsRecord> records = new ArrayList<>();
+        Instant now = Instant.now();
+        for (int i = 0; i < distinctRecordCount; i++) {
+            StepsRecord record =
+                    TestUtils.getCompleteStepsRecord(
+                            /* startTime= */ now.minusMillis(i + 1),
+                            /* endTime= */ now.minusMillis(i),
+                            /* clientRecordId= */ "client_id_" + i);
+
+            records.add(record);
+            records.add(record); // Add each record twice
+        }
+
+        List<Record> insertedRecords = TestUtils.insertRecords(records);
+        assertThat(insertedRecords.size()).isEqualTo(records.size());
+
+        List<Record> distinctRecords = distinctByUuid(insertedRecords);
+        assertThat(distinctRecords.size()).isEqualTo(distinctRecordCount);
+
+        assertStepsRecordUsingIds(distinctRecords);
+    }
+
     private void testAggregateDurationWithLocalTimeForZoneOffset(ZoneOffset offset)
             throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY);
@@ -1536,7 +1590,7 @@
                 .build();
     }
 
-    static void readStepsRecordUsingIds(List<Record> recordList) throws InterruptedException {
+    static void assertStepsRecordUsingIds(List<Record> recordList) throws InterruptedException {
         ReadRecordsRequestUsingIds.Builder<StepsRecord> request =
                 new ReadRecordsRequestUsingIds.Builder<>(StepsRecord.class);
         for (Record record : recordList) {
diff --git a/tests/cts/src/android/healthconnect/cts/TotalCaloriesBurnedRecordTest.java b/tests/cts/src/android/healthconnect/cts/TotalCaloriesBurnedRecordTest.java
index 9d44665..e98b721 100644
--- a/tests/cts/src/android/healthconnect/cts/TotalCaloriesBurnedRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/TotalCaloriesBurnedRecordTest.java
@@ -39,6 +39,7 @@
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.TotalCaloriesBurnedRecord;
 import android.health.connect.datatypes.units.Energy;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -47,6 +48,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -67,6 +69,11 @@
     private static final String TAG = "TotalCaloriesBurnedRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
@@ -572,7 +579,6 @@
     @Test(expected = UnsupportedOperationException.class)
     public void testAggregation_totalCaloriesBurnt_activeCalories_groupBy() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY);
-
         Context context = ApplicationProvider.getApplicationContext();
         Instant now = Instant.now();
         TestUtils.getAggregateResponseGroupByPeriod(
@@ -594,7 +600,6 @@
     public void testAggregation_totalCaloriesBurnt_activeCalories_groupBy_duration()
             throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY);
-
         Context context = ApplicationProvider.getApplicationContext();
         Instant now = Instant.now();
         List<Record> records =
@@ -642,7 +647,6 @@
     public void testAggregation_groupByDurationLocalFilter_shiftRecordsAndFilterWithOffset()
             throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.ACTIVITY);
-
         Context context = ApplicationProvider.getApplicationContext();
         Instant now = Instant.now();
         ZoneOffset offset = ZoneOffset.ofHours(-1);
diff --git a/tests/cts/src/android/healthconnect/cts/Vo2MaxRecordTest.java b/tests/cts/src/android/healthconnect/cts/Vo2MaxRecordTest.java
index 52af376..5d7bc41 100644
--- a/tests/cts/src/android/healthconnect/cts/Vo2MaxRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/Vo2MaxRecordTest.java
@@ -34,6 +34,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.Vo2MaxRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -42,6 +43,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,6 +60,11 @@
 public class Vo2MaxRecordTest {
     private static final String TAG = "Vo2MaxRecordTest";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @After
     public void tearDown() throws InterruptedException {
         TestUtils.verifyDeleteRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/WeightRecordTest.java b/tests/cts/src/android/healthconnect/cts/WeightRecordTest.java
index 06febc4..877be6e 100644
--- a/tests/cts/src/android/healthconnect/cts/WeightRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/WeightRecordTest.java
@@ -47,6 +47,7 @@
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.WeightRecord;
 import android.health.connect.datatypes.units.Mass;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
@@ -56,6 +57,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -78,6 +80,11 @@
     private static final String TAG = "WeightRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
@@ -605,7 +612,6 @@
 
     void testAggregatePeriodForZoneOffset(ZoneOffset offset) throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         Instant endTime = Instant.now();
         LocalDateTime endTimeLocal = LocalDateTime.ofInstant(endTime, offset);
         insertThreeWeightRecordsWithZoneOffset(endTime, offset);
@@ -649,6 +655,7 @@
 
     @Test
     public void testAggregateDuration_differentTimeZones_correctBucketTimes() throws Exception {
+        TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
         Context context = ApplicationProvider.getApplicationContext();
         Duration oneHour = Duration.ofHours(1);
         Instant t1 = Instant.now().minus(Duration.ofDays(1)).truncatedTo(ChronoUnit.MILLIS);
@@ -722,7 +729,6 @@
     private void testDurationLocalTimeAggregationZoneOffset(ZoneOffset offset)
             throws InterruptedException {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         Instant endTime = Instant.now();
         LocalDateTime endTimeLocal = LocalDateTime.ofInstant(endTime, offset);
         insertThreeWeightRecordsWithZoneOffset(endTime, offset);
@@ -774,7 +780,6 @@
     @Test
     public void testAggregateLocalFilter_minOffsetRecord() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         LocalDateTime endTimeLocal = LocalDateTime.now(ZoneOffset.UTC);
         Instant endTimeInstant = Instant.now();
 
@@ -822,7 +827,6 @@
 
     private void testOffset(ZoneOffset offset) throws InterruptedException {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         Instant endTimeInstant = Instant.now();
         LocalDateTime endTimeLocal = LocalDateTime.ofInstant(endTimeInstant, offset);
 
@@ -864,7 +868,6 @@
     @Test
     public void testAggregateLocalFilter_daysPeriod() throws Exception {
         TestUtils.setupAggregation(PACKAGE_NAME, HealthDataCategory.BODY_MEASUREMENTS);
-
         LocalDateTime endTimeLocal = LocalDateTime.now(ZoneOffset.UTC);
         Instant endTimeInstant = Instant.now();
         TestUtils.insertRecords(
diff --git a/tests/cts/src/android/healthconnect/cts/WheelchairPushesRecordTest.java b/tests/cts/src/android/healthconnect/cts/WheelchairPushesRecordTest.java
index 66efed7..bb28514 100644
--- a/tests/cts/src/android/healthconnect/cts/WheelchairPushesRecordTest.java
+++ b/tests/cts/src/android/healthconnect/cts/WheelchairPushesRecordTest.java
@@ -38,6 +38,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.WheelchairPushesRecord;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -46,6 +47,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -63,6 +65,11 @@
     private static final String TAG = "WheelchairPushesRecordTest";
     private static final String PACKAGE_NAME = "android.healthconnect.cts";
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws InterruptedException {
         TestUtils.deleteAllStagedRemoteData();
diff --git a/tests/cts/src/android/healthconnect/cts/nopermission/HealthConnectManagerNoPermissionsGrantedTest.java b/tests/cts/src/android/healthconnect/cts/nopermission/HealthConnectManagerNoPermissionsGrantedTest.java
index 4056741..3bb27a0 100644
--- a/tests/cts/src/android/healthconnect/cts/nopermission/HealthConnectManagerNoPermissionsGrantedTest.java
+++ b/tests/cts/src/android/healthconnect/cts/nopermission/HealthConnectManagerNoPermissionsGrantedTest.java
@@ -29,12 +29,14 @@
 import android.health.connect.changelog.ChangeLogTokenRequest;
 import android.health.connect.datatypes.DataOrigin;
 import android.health.connect.datatypes.Record;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -52,6 +54,12 @@
 @AppModeFull(reason = "HealthConnectManager is not accessible to instant apps")
 @RunWith(AndroidJUnit4.class)
 public class HealthConnectManagerNoPermissionsGrantedTest {
+
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Test
     public void testInsertNotAllowed() throws InterruptedException {
         for (Record testRecord : TestUtils.getTestRecords()) {
diff --git a/tests/cts/src/android/healthconnect/cts/showmigrationinfointent/ShowMigrationInfoIntentAbsentTest.java b/tests/cts/src/android/healthconnect/cts/showmigrationinfointent/ShowMigrationInfoIntentAbsentTest.java
index 1f1ceaa..a7c94b5 100644
--- a/tests/cts/src/android/healthconnect/cts/showmigrationinfointent/ShowMigrationInfoIntentAbsentTest.java
+++ b/tests/cts/src/android/healthconnect/cts/showmigrationinfointent/ShowMigrationInfoIntentAbsentTest.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.health.connect.HealthConnectManager;
 import android.health.connect.migration.MigrationException;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestUtils;
 import android.os.Build;
 import android.os.OutcomeReceiver;
@@ -35,6 +36,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -51,6 +53,11 @@
     private HealthConnectManager mManager;
     UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
diff --git a/tests/cts/src/android/healthconnect/cts/ui/CategoriesFragmentTest.kt b/tests/cts/src/android/healthconnect/cts/ui/CategoriesFragmentTest.kt
index 90ca2f5..2b7dba5 100644
--- a/tests/cts/src/android/healthconnect/cts/ui/CategoriesFragmentTest.kt
+++ b/tests/cts/src/android/healthconnect/cts/ui/CategoriesFragmentTest.kt
@@ -23,6 +23,7 @@
 import android.healthconnect.cts.lib.UiTestUtils.clickOnText
 import android.healthconnect.cts.lib.UiTestUtils.stepsRecordFromTestApp
 import android.healthconnect.cts.lib.UiTestUtils.waitDisplayed
+import android.healthconnect.cts.utils.TestUtils
 import android.healthconnect.cts.utils.TestUtils.insertRecords
 import android.healthconnect.cts.utils.TestUtils.verifyDeleteRecords
 import androidx.test.uiautomator.By
@@ -39,6 +40,9 @@
         @JvmStatic
         @BeforeClass
         fun setup() {
+            if (!TestUtils.isHardwareSupported()) {
+                return
+            }
             val records: List<Record> = listOf(stepsRecordFromTestApp(), stepsRecordFromTestApp())
             insertRecords(records)
         }
@@ -46,6 +50,9 @@
         @JvmStatic
         @AfterClass
         fun teardown() {
+            if (!TestUtils.isHardwareSupported()) {
+                return
+            }
             verifyDeleteRecords(
                 StepsRecord::class.java,
                 TimeInstantRangeFilter.Builder()
diff --git a/tests/cts/src/android/healthconnect/cts/ui/DataAccessFragmentTest.kt b/tests/cts/src/android/healthconnect/cts/ui/DataAccessFragmentTest.kt
index dbe0665..7c87eb1 100644
--- a/tests/cts/src/android/healthconnect/cts/ui/DataAccessFragmentTest.kt
+++ b/tests/cts/src/android/healthconnect/cts/ui/DataAccessFragmentTest.kt
@@ -17,12 +17,13 @@
 
 import android.health.connect.TimeInstantRangeFilter
 import android.health.connect.datatypes.StepsRecord
-import android.healthconnect.cts.utils.TestUtils.insertRecords
-import android.healthconnect.cts.utils.TestUtils.verifyDeleteRecords
 import android.healthconnect.cts.lib.ActivityLauncher.launchDataActivity
 import android.healthconnect.cts.lib.UiTestUtils.clickOnText
 import android.healthconnect.cts.lib.UiTestUtils.stepsRecordFromTestApp
 import android.healthconnect.cts.lib.UiTestUtils.waitDisplayed
+import android.healthconnect.cts.utils.TestUtils
+import android.healthconnect.cts.utils.TestUtils.insertRecords
+import android.healthconnect.cts.utils.TestUtils.verifyDeleteRecords
 import androidx.test.uiautomator.By
 import java.time.Duration
 import java.time.Instant
@@ -38,6 +39,9 @@
         @JvmStatic
         @AfterClass
         fun tearDown() {
+            if (!TestUtils.isHardwareSupported()) {
+                return
+            }
             verifyDeleteRecords(
                 StepsRecord::class.java,
                 TimeInstantRangeFilter.Builder()
diff --git a/tests/cts/src/android/healthconnect/cts/ui/DataEntriesFragmentTest.kt b/tests/cts/src/android/healthconnect/cts/ui/DataEntriesFragmentTest.kt
index 783393f..a14b1dd 100644
--- a/tests/cts/src/android/healthconnect/cts/ui/DataEntriesFragmentTest.kt
+++ b/tests/cts/src/android/healthconnect/cts/ui/DataEntriesFragmentTest.kt
@@ -18,12 +18,13 @@
 import android.health.connect.TimeInstantRangeFilter
 import android.health.connect.datatypes.DistanceRecord
 import android.health.connect.datatypes.StepsRecord
-import android.healthconnect.cts.utils.TestUtils.insertRecords
-import android.healthconnect.cts.utils.TestUtils.verifyDeleteRecords
 import android.healthconnect.cts.lib.ActivityLauncher.launchDataActivity
 import android.healthconnect.cts.lib.UiTestUtils.clickOnText
 import android.healthconnect.cts.lib.UiTestUtils.distanceRecordFromTestApp
 import android.healthconnect.cts.lib.UiTestUtils.stepsRecordFromTestApp
+import android.healthconnect.cts.utils.TestUtils
+import android.healthconnect.cts.utils.TestUtils.insertRecords
+import android.healthconnect.cts.utils.TestUtils.verifyDeleteRecords
 import java.time.Instant
 import java.time.Period.ofDays
 import org.junit.AfterClass
@@ -38,6 +39,9 @@
         @JvmStatic
         @AfterClass
         fun tearDown() {
+            if (!TestUtils.isHardwareSupported()) {
+                return
+            }
             verifyDeleteRecords(
                 StepsRecord::class.java,
                 TimeInstantRangeFilter.Builder()
diff --git a/tests/cts/src/android/healthconnect/cts/ui/HomeFragmentTest.kt b/tests/cts/src/android/healthconnect/cts/ui/HomeFragmentTest.kt
index c5828ed..ebbc37a 100644
--- a/tests/cts/src/android/healthconnect/cts/ui/HomeFragmentTest.kt
+++ b/tests/cts/src/android/healthconnect/cts/ui/HomeFragmentTest.kt
@@ -16,17 +16,17 @@
 package android.healthconnect.cts.ui
 
 import android.health.connect.TimeInstantRangeFilter
-import android.health.connect.datatypes.BasalMetabolicRateRecord
-import android.health.connect.datatypes.HeartRateRecord
 import android.health.connect.datatypes.StepsRecord
 import android.healthconnect.cts.lib.ActivityLauncher.launchMainActivity
-import android.healthconnect.cts.lib.MultiAppTestUtils.insertRecordAs
+import android.healthconnect.cts.lib.TestAppProxy
 import android.healthconnect.cts.lib.UiTestUtils.clickOnText
 import android.healthconnect.cts.lib.UiTestUtils.waitDisplayed
+import android.healthconnect.cts.utils.TestUtils
+import android.healthconnect.cts.utils.TestUtils.getEmptyMetadata
 import android.healthconnect.cts.utils.TestUtils.verifyDeleteRecords
 import androidx.test.uiautomator.By
-import com.android.cts.install.lib.TestApp
 import java.time.Instant
+import java.time.temporal.ChronoUnit
 import org.junit.AfterClass
 import org.junit.BeforeClass
 import org.junit.Test
@@ -36,45 +36,32 @@
 
     companion object {
 
-        private const val TAG = "HomeFragmentTest"
-
-        private const val VERSION_CODE: Long = 1
-
-        private val APP_A_WITH_READ_WRITE_PERMS: TestApp =
-            TestApp(
-                "TestAppA",
-                "android.healthconnect.cts.testapp.readWritePerms.A",
-                VERSION_CODE,
-                false,
-                "CtsHealthConnectTestAppA.apk")
+        private val APP_A_WITH_READ_WRITE_PERMS: TestAppProxy =
+            TestAppProxy.forPackageName("android.healthconnect.cts.testapp.readWritePerms.A")
 
         @JvmStatic
         @BeforeClass
         fun setup() {
-            insertRecordAs(APP_A_WITH_READ_WRITE_PERMS)
+            if (!TestUtils.isHardwareSupported()) {
+                return
+            }
+            val now = Instant.now().truncatedTo(ChronoUnit.MILLIS)
+            APP_A_WITH_READ_WRITE_PERMS.insertRecords(
+                StepsRecord.Builder(getEmptyMetadata(), now.minusSeconds(30), now, 43).build())
         }
 
         @JvmStatic
         @AfterClass
         fun teardown() {
+            if (!TestUtils.isHardwareSupported()) {
+                return
+            }
             verifyDeleteRecords(
                 StepsRecord::class.java,
                 TimeInstantRangeFilter.Builder()
                     .setStartTime(Instant.EPOCH)
                     .setEndTime(Instant.now())
                     .build())
-            verifyDeleteRecords(
-                HeartRateRecord::class.java,
-                TimeInstantRangeFilter.Builder()
-                    .setStartTime(Instant.EPOCH)
-                    .setEndTime(Instant.now())
-                    .build())
-            verifyDeleteRecords(
-                BasalMetabolicRateRecord::class.java,
-                TimeInstantRangeFilter.Builder()
-                    .setStartTime(Instant.EPOCH)
-                    .setEndTime(Instant.now())
-                    .build())
         }
     }
 
diff --git a/tests/cts/src/android/healthconnect/cts/ui/PermissionTypesFragmentTest.kt b/tests/cts/src/android/healthconnect/cts/ui/PermissionTypesFragmentTest.kt
index 9ff9024..919b027 100644
--- a/tests/cts/src/android/healthconnect/cts/ui/PermissionTypesFragmentTest.kt
+++ b/tests/cts/src/android/healthconnect/cts/ui/PermissionTypesFragmentTest.kt
@@ -19,14 +19,15 @@
 import android.health.connect.datatypes.BasalMetabolicRateRecord
 import android.health.connect.datatypes.HeartRateRecord
 import android.health.connect.datatypes.StepsRecord
-import android.healthconnect.cts.utils.TestUtils.verifyDeleteRecords
 import android.healthconnect.cts.lib.ActivityLauncher.launchDataActivity
-import android.healthconnect.cts.lib.MultiAppTestUtils.insertRecordAs
+import android.healthconnect.cts.lib.TestAppProxy
 import android.healthconnect.cts.lib.UiTestUtils.clickOnText
 import android.healthconnect.cts.lib.UiTestUtils.waitDisplayed
+import android.healthconnect.cts.utils.TestUtils
+import android.healthconnect.cts.utils.TestUtils.verifyDeleteRecords
 import androidx.test.uiautomator.By
-import com.android.cts.install.lib.TestApp
 import java.time.Instant
+import java.time.temporal.ChronoUnit
 import org.junit.AfterClass
 import org.junit.BeforeClass
 import org.junit.Test
@@ -35,36 +36,31 @@
 class PermissionTypesFragmentTest : HealthConnectBaseTest() {
 
     companion object {
-        private const val TAG = "PermissionTypesFragmentTest"
-
-        private const val VERSION_CODE: Long = 1
-
-        private val APP_A_WITH_READ_WRITE_PERMS: TestApp =
-            TestApp(
-                "TestAppA",
-                "android.healthconnect.cts.testapp.readWritePerms.A",
-                VERSION_CODE,
-                false,
-                "CtsHealthConnectTestAppA.apk")
-
-        private val APP_B_WITH_READ_WRITE_PERMS: TestApp =
-            TestApp(
-                "TestAppB",
-                "android.healthconnect.cts.testapp.readWritePerms.B",
-                VERSION_CODE,
-                false,
-                "CtsHealthConnectTestAppB.apk")
+        private val APP_A_WITH_READ_WRITE_PERMS: TestAppProxy =
+            TestAppProxy.forPackageName("android.healthconnect.cts.testapp.readWritePerms.A")
+        private val APP_B_WITH_READ_WRITE_PERMS: TestAppProxy =
+            TestAppProxy.forPackageName("android.healthconnect.cts.testapp.readWritePerms.B")
 
         @JvmStatic
         @BeforeClass
         fun setup() {
-            insertRecordAs(APP_A_WITH_READ_WRITE_PERMS)
-            insertRecordAs(APP_B_WITH_READ_WRITE_PERMS)
+            if (!TestUtils.isHardwareSupported()) {
+                return
+            }
+            val now = Instant.now().truncatedTo(ChronoUnit.MILLIS)
+            val record =
+                StepsRecord.Builder(TestUtils.getEmptyMetadata(), now.minusSeconds(30), now, 43)
+                    .build()
+            APP_A_WITH_READ_WRITE_PERMS.insertRecords(record)
+            APP_B_WITH_READ_WRITE_PERMS.insertRecords(record)
         }
 
         @JvmStatic
         @AfterClass
         fun teardown() {
+            if (!TestUtils.isHardwareSupported()) {
+                return
+            }
             verifyDeleteRecords(
                 StepsRecord::class.java,
                 TimeInstantRangeFilter.Builder()
diff --git a/tests/cts/utils/HealthConnectTestUtils/src/android/healthconnect/cts/utils/AssumptionCheckerRule.java b/tests/cts/utils/HealthConnectTestUtils/src/android/healthconnect/cts/utils/AssumptionCheckerRule.java
new file mode 100644
index 0000000..b13b23b
--- /dev/null
+++ b/tests/cts/utils/HealthConnectTestUtils/src/android/healthconnect/cts/utils/AssumptionCheckerRule.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.healthconnect.cts.utils;
+
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Rule which checks an assumption, and skips the test if it evaluates false. Primarily useful for
+ * short-circuiting rule evaluations.
+ */
+public class AssumptionCheckerRule implements TestRule {
+
+    private final String mAssumptionDescription;
+    private final BooleanSupplier mShouldRunTestSupplier;
+
+    /**
+     * Initialize the rule with a supplier to assume on and a description.
+     *
+     * @param shouldRunTestSupplier - Evaluated prior to each test statement, and if false, test is
+     *     skipped.
+     * @param description - Message for failed assumption if test is skipped.
+     */
+    public AssumptionCheckerRule(BooleanSupplier shouldRunTestSupplier, String description) {
+        mShouldRunTestSupplier = shouldRunTestSupplier;
+        mAssumptionDescription = description;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                assumeTrue(mAssumptionDescription, mShouldRunTestSupplier.getAsBoolean());
+                base.evaluate();
+            }
+        };
+    }
+
+    @Override
+    public String toString() {
+        return "AssumptionCheckerRule["
+                + mAssumptionDescription
+                + ", "
+                + mShouldRunTestSupplier
+                + "]";
+    }
+}
diff --git a/tests/cts/utils/HealthConnectTestUtils/src/android/healthconnect/cts/utils/TestUtils.java b/tests/cts/utils/HealthConnectTestUtils/src/android/healthconnect/cts/utils/TestUtils.java
index 17e776f..7570a5f 100644
--- a/tests/cts/utils/HealthConnectTestUtils/src/android/healthconnect/cts/utils/TestUtils.java
+++ b/tests/cts/utils/HealthConnectTestUtils/src/android/healthconnect/cts/utils/TestUtils.java
@@ -138,7 +138,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.SystemUtil;
-import com.android.cts.install.lib.TestApp;
 
 import java.io.BufferedReader;
 import java.io.FileInputStream;
@@ -150,6 +149,7 @@
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.time.Period;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
@@ -162,12 +162,16 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 public final class TestUtils {
     public static final String MANAGE_HEALTH_PERMISSIONS =
@@ -266,15 +270,29 @@
         return recordTypeAndRecordIdsList;
     }
 
+    /**
+     * Returns all records from the `records` list in their original order, but distinct by UUID.
+     */
+    public static <T extends Record> List<T> distinctByUuid(List<T> records) {
+        return records.stream().filter(distinctByUuid()).toList();
+    }
+
+    private static Predicate<? super Record> distinctByUuid() {
+        Set<String> seen = ConcurrentHashMap.newKeySet();
+        return record -> seen.add(record.getMetadata().getId());
+    }
+
     public static void updateRecords(List<Record> records) throws InterruptedException {
         updateRecords(records, ApplicationProvider.getApplicationContext());
     }
 
-    public static void updateRecords(List<Record> records, Context context)
+    /** Synchronously updates records in HC. */
+    public static void updateRecords(List<? extends Record> records, Context context)
             throws InterruptedException {
         HealthConnectReceiver<Void> receiver = new HealthConnectReceiver<>();
         getHealthConnectManager(context)
-                .updateRecords(records, Executors.newSingleThreadExecutor(), receiver);
+                .updateRecords(
+                        unmodifiableList(records), Executors.newSingleThreadExecutor(), receiver);
         receiver.verifyNoExceptionOrThrow();
     }
 
@@ -318,11 +336,6 @@
                 buildExerciseSession());
     }
 
-    public static List<Record> getTestRecords(String packageName) {
-        double clientId = Math.random();
-        return getTestRecords(packageName, clientId);
-    }
-
     public static List<Record> getTestRecords(String packageName, Double clientId) {
         return Arrays.asList(
                 getExerciseSessionRecord(packageName, clientId, /* withRoute= */ true),
@@ -943,7 +956,17 @@
                 .build();
     }
 
+    /** Creates and returns a {@link StepsRecord} with default arguments. */
     public static StepsRecord getCompleteStepsRecord() {
+        return getCompleteStepsRecord(
+                Instant.now(),
+                Instant.now().plusMillis(1000),
+                /* clientRecordId= */ "SR" + Math.random());
+    }
+
+    /** Creates and returns a {@link StepsRecord} with the specified arguments. */
+    public static StepsRecord getCompleteStepsRecord(
+            Instant startTime, Instant endTime, String clientRecordId) {
         Device device =
                 new Device.Builder().setManufacturer("google").setModel("Pixel").setType(1).build();
         DataOrigin dataOrigin =
@@ -951,13 +974,11 @@
 
         Metadata.Builder testMetadataBuilder = new Metadata.Builder();
         testMetadataBuilder.setDevice(device).setDataOrigin(dataOrigin);
-        testMetadataBuilder.setClientRecordId("SR" + Math.random());
+        testMetadataBuilder.setClientRecordId(clientRecordId);
         testMetadataBuilder.setRecordingMethod(RECORDING_METHOD_ACTIVELY_RECORDED);
         Metadata testMetaData = testMetadataBuilder.build();
         assertThat(testMetaData.getRecordingMethod()).isEqualTo(RECORDING_METHOD_ACTIVELY_RECORDED);
-        return new StepsRecord.Builder(
-                        testMetaData, Instant.now(), Instant.now().plusMillis(1000), 10)
-                .build();
+        return new StepsRecord.Builder(testMetaData, startTime, endTime, 10).build();
     }
 
     public static StepsRecord getStepsRecord_update(
@@ -1349,13 +1370,14 @@
                         .build());
     }
 
-    public static void revokeAndThenGrantHealthPermissions(TestApp testApp) {
-        List<String> healthPerms = getGrantedHealthPermissions(testApp.getPackageName());
+    /** Revokes all granted Health permissions and re-grants them back. */
+    public static void revokeAndThenGrantHealthPermissions(String packageName) {
+        List<String> healthPerms = getGrantedHealthPermissions(packageName);
 
-        revokeHealthPermissions(testApp.getPackageName());
+        revokeHealthPermissions(packageName);
 
         for (String perm : healthPerms) {
-            grantPermission(testApp.getPackageName(), perm);
+            grantPermission(packageName, perm);
         }
     }
 
@@ -1510,6 +1532,19 @@
         }
     }
 
+    public static boolean isHardwareSupported() {
+        return isHardwareSupported(ApplicationProvider.getApplicationContext());
+    }
+
+    /** returns true if the hardware is supported by HealthConnect. */
+    public static boolean isHardwareSupported(Context context) {
+        PackageManager pm = context.getPackageManager();
+        return (!pm.hasSystemFeature(PackageManager.FEATURE_EMBEDDED)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+    }
+
     /** Gets the priority list after getting the MANAGE_HEALTH_DATA permission. */
     public static FetchDataOriginsPriorityOrderResponse getPriorityWithManageHealthDataPermission(
             int permissionCategory) throws InterruptedException {
@@ -1564,6 +1599,76 @@
         return response.get();
     }
 
+    /** Zips given id and records lists to create a list of {@link RecordIdFilter}. */
+    public static List<RecordIdFilter> getRecordIdFilters(
+            List<String> recordIds, List<Record> records) {
+        return IntStream.range(0, recordIds.size())
+                .mapToObj(
+                        i -> {
+                            Class<? extends Record> recordClass = records.get(i).getClass();
+                            String id = recordIds.get(i);
+                            return RecordIdFilter.fromId(recordClass, id);
+                        })
+                .toList();
+    }
+
+    public static Metadata getEmptyMetadata() {
+        return new Metadata.Builder().build();
+    }
+
+    /** Creates a {@link Metadata} with the given record id. */
+    public static Metadata getMetadataForId(String id) {
+        return new Metadata.Builder().setId(id).build();
+    }
+
+    /** Creates a {@link Metadata} with the given record id and data origin. */
+    public static Metadata getMetadataForId(String id, DataOrigin dataOrigin) {
+        return new Metadata.Builder().setId(id).setDataOrigin(dataOrigin).build();
+    }
+
+    /** Creates a {@link Metadata} with the given client record id. */
+    public static Metadata getMetadataForClientId(String clientId) {
+        return new Metadata.Builder().setClientRecordId(clientId).build();
+    }
+
+    /** Creates a {@link Metadata} with the given client record id and data origin. */
+    public static Metadata getMetadataForClientId(String clientId, DataOrigin dataOrigin) {
+        return new Metadata.Builder().setClientRecordId(clientId).setDataOrigin(dataOrigin).build();
+    }
+
+    /** Creates a {@link Metadata} with the given data origin. */
+    public static Metadata getMetadata(DataOrigin dataOrigin) {
+        return new Metadata.Builder().setDataOrigin(dataOrigin).build();
+    }
+
+    /** Creates a {@link DataOrigin} with the given package name. */
+    public static DataOrigin getDataOrigin(String packageName) {
+        return new DataOrigin.Builder().setPackageName(packageName).build();
+    }
+
+    /** Creates a list of {@link DataOrigin} from a list of package names. */
+    public static List<DataOrigin> getDataOrigins(String... packageNames) {
+        return Arrays.stream(packageNames).map(TestUtils::getDataOrigin).toList();
+    }
+
+    /** Creates a {@link ExerciseRoute.Location}. */
+    public static ExerciseRoute.Location getLocation(
+            Instant time, double latitude, double longitude) {
+        return new ExerciseRoute.Location.Builder(time, latitude, longitude).build();
+    }
+
+    /** Creates a {@link ExerciseRoute} with given locations. */
+    public static ExerciseRoute getExerciseRoute(ExerciseRoute.Location... locations) {
+        return new ExerciseRoute(Arrays.asList(locations));
+    }
+
+    /** Creates an {@link Instant} representing the given local time yesterday at UTC. */
+    public static Instant yesterdayAt(String localTime) {
+        return LocalTime.parse(localTime)
+                .atDate(LocalDate.now().minusDays(1))
+                .toInstant(ZoneOffset.UTC);
+    }
+
     public static final class RecordAndIdentifier {
         private final int mId;
         private final Record mRecordClass;
diff --git a/tests/integrationtests/TestApp/src/android/healthconnect/test/app/BlockingOutcomeReceiver.java b/tests/integrationtests/TestApp/src/android/healthconnect/test/app/BlockingOutcomeReceiver.java
index 16a723f..049c06b 100644
--- a/tests/integrationtests/TestApp/src/android/healthconnect/test/app/BlockingOutcomeReceiver.java
+++ b/tests/integrationtests/TestApp/src/android/healthconnect/test/app/BlockingOutcomeReceiver.java
@@ -16,18 +16,17 @@
 
 package android.healthconnect.test.app;
 
-import android.health.connect.HealthConnectException;
 import android.os.OutcomeReceiver;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public final class BlockingOutcomeReceiver<T>
-        implements OutcomeReceiver<T, HealthConnectException> {
+/** A blocking implementation of {@link OutcomeReceiver} that allows waiting for responses. */
+public class BlockingOutcomeReceiver<T, E extends Throwable> implements OutcomeReceiver<T, E> {
 
     private final CountDownLatch mLatch = new CountDownLatch(1);
     private T mResult;
-    private HealthConnectException mError;
+    private E mError;
 
     @Override
     public void onResult(T result) {
@@ -36,17 +35,28 @@
     }
 
     @Override
-    public void onError(HealthConnectException error) {
+    public void onError(E error) {
         mError = error;
         mLatch.countDown();
     }
 
-    public T getResult() throws HealthConnectException {
-        await();
+    /** Waits for a response and returns the result if successful, or throws the error if failed. */
+    public T getResult() throws E {
+        awaitSuccess();
         return mResult;
     }
 
-    public HealthConnectException getError() {
+    /** Waits for a response, throws the error if failed. */
+    public void awaitSuccess() throws E {
+        await();
+
+        if (mError != null) {
+            throw mError;
+        }
+    }
+
+    /** Waits for a response and returns the error if failed, or {@code null} if successful. */
+    public E getError() {
         await();
         return mError;
     }
diff --git a/tests/integrationtests/TestApp/src/android/healthconnect/test/app/DefaultOutcomeReceiver.java b/tests/integrationtests/TestApp/src/android/healthconnect/test/app/DefaultOutcomeReceiver.java
new file mode 100644
index 0000000..d3edfb0
--- /dev/null
+++ b/tests/integrationtests/TestApp/src/android/healthconnect/test/app/DefaultOutcomeReceiver.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.healthconnect.test.app;
+
+import android.health.connect.HealthConnectException;
+
+/**
+ * A predefined {@link BlockingOutcomeReceiver} with the error type {@link HealthConnectException}.
+ * Can be used instead of {@link BlockingOutcomeReceiver} to avoid specifying the error type every
+ * time.
+ */
+public final class DefaultOutcomeReceiver<T>
+        extends BlockingOutcomeReceiver<T, HealthConnectException> {}
diff --git a/tests/integrationtests/TestApp/src/android/healthconnect/test/app/TestAppReceiver.java b/tests/integrationtests/TestApp/src/android/healthconnect/test/app/TestAppReceiver.java
index 1253b64..98c1cd2 100644
--- a/tests/integrationtests/TestApp/src/android/healthconnect/test/app/TestAppReceiver.java
+++ b/tests/integrationtests/TestApp/src/android/healthconnect/test/app/TestAppReceiver.java
@@ -110,22 +110,22 @@
     }
 
     private static void insertStepsRecords(Context context, Intent intent) {
-        BlockingOutcomeReceiver<InsertRecordsResponse> outcome = new BlockingOutcomeReceiver<>();
+        DefaultOutcomeReceiver<InsertRecordsResponse> outcome = new DefaultOutcomeReceiver<>();
         getHealthConnectManager(context)
                 .insertRecords(createStepsRecords(intent), newSingleThreadExecutor(), outcome);
         sendInsertRecordsResult(context, intent, outcome);
     }
 
     private static void insertWeightRecords(Context context, Intent intent) {
-        BlockingOutcomeReceiver<InsertRecordsResponse> outcome = new BlockingOutcomeReceiver<>();
+        DefaultOutcomeReceiver<InsertRecordsResponse> outcome = new DefaultOutcomeReceiver<>();
         getHealthConnectManager(context)
                 .insertRecords(createWeightRecords(intent), newSingleThreadExecutor(), outcome);
         sendInsertRecordsResult(context, intent, outcome);
     }
 
     private void readRecordsForOtherApp(Context context, Intent intent) {
-        final BlockingOutcomeReceiver<ReadRecordsResponse<ActiveCaloriesBurnedRecord>> outcome =
-                new BlockingOutcomeReceiver<>();
+        DefaultOutcomeReceiver<ReadRecordsResponse<ActiveCaloriesBurnedRecord>> outcome =
+                new DefaultOutcomeReceiver<>();
 
         getHealthConnectManager(context)
                 .readRecords(
@@ -143,8 +143,8 @@
     }
 
     private void aggregate(Context context, Intent intent) {
-        final BlockingOutcomeReceiver<AggregateRecordsResponse<Energy>> outcome =
-                new BlockingOutcomeReceiver<>();
+        DefaultOutcomeReceiver<AggregateRecordsResponse<Energy>> outcome =
+                new DefaultOutcomeReceiver<>();
 
         getHealthConnectManager(context)
                 .aggregate(
@@ -162,8 +162,7 @@
     }
 
     private void getChangeLogToken(Context context, Intent intent) {
-        final BlockingOutcomeReceiver<ChangeLogTokenResponse> outcome =
-                new BlockingOutcomeReceiver<>();
+        DefaultOutcomeReceiver<ChangeLogTokenResponse> outcome = new DefaultOutcomeReceiver<>();
 
         getHealthConnectManager(context)
                 .getChangeLogToken(
@@ -185,7 +184,7 @@
 
     private void getChangeLogs(Context context, Intent intent) {
         String token = intent.getStringExtra(EXTRA_TOKEN);
-        final BlockingOutcomeReceiver<ChangeLogsResponse> outcome = new BlockingOutcomeReceiver<>();
+        DefaultOutcomeReceiver<ChangeLogsResponse> outcome = new DefaultOutcomeReceiver<>();
 
         getHealthConnectManager(context)
                 .getChangeLogs(
@@ -203,7 +202,7 @@
     private static void sendReadRecordsResult(
             Context context,
             Intent intent,
-            BlockingOutcomeReceiver<? extends ReadRecordsResponse<?>> outcome) {
+            DefaultOutcomeReceiver<? extends ReadRecordsResponse<?>> outcome) {
         final HealthConnectException error = outcome.getError();
         if (error != null) {
             sendError(context, intent, error);
@@ -218,7 +217,7 @@
     private static void sendInsertRecordsResult(
             Context context,
             Intent intent,
-            BlockingOutcomeReceiver<? extends InsertRecordsResponse> outcome) {
+            DefaultOutcomeReceiver<? extends InsertRecordsResponse> outcome) {
         final HealthConnectException error = outcome.getError();
         if (error != null) {
             sendError(context, intent, error);
@@ -239,7 +238,7 @@
     }
 
     private static void sendResult(
-            Context context, Intent intent, BlockingOutcomeReceiver<?> outcomeReceiver) {
+            Context context, Intent intent, DefaultOutcomeReceiver<?> outcomeReceiver) {
         final HealthConnectException error = outcomeReceiver.getError();
         if (error != null) {
             sendError(context, intent, error);
diff --git a/tests/integrationtests/src/android/healthconnect/tests/IntegrationTestUtils.java b/tests/integrationtests/src/android/healthconnect/tests/IntegrationTestUtils.java
new file mode 100644
index 0000000..54c31d3
--- /dev/null
+++ b/tests/integrationtests/src/android/healthconnect/tests/IntegrationTestUtils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.healthconnect.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+
+import android.content.Context;
+import android.health.connect.HealthConnectManager;
+import android.health.connect.migration.MigrationEntity;
+import android.health.connect.migration.MigrationException;
+import android.healthconnect.test.app.BlockingOutcomeReceiver;
+import android.os.OutcomeReceiver;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/** Utils for permission tests. */
+public final class IntegrationTestUtils {
+
+    private IntegrationTestUtils() {}
+
+    /**
+     * Calls {@link HealthConnectManager#startMigration(Executor, OutcomeReceiver)} and waits for
+     * the call to finish.
+     */
+    public static void startMigration() {
+        BlockingOutcomeReceiver<Void, MigrationException> outcome = new BlockingOutcomeReceiver<>();
+        getHealthConnectManager().startMigration(newSingleThreadExecutor(), outcome);
+        outcome.awaitSuccess();
+    }
+
+    /**
+     * Calls {@link HealthConnectManager#writeMigrationData(List, Executor, OutcomeReceiver)} and
+     * waits for the call to finish.
+     */
+    public static void writeMigrationData(List<MigrationEntity> entities) {
+        BlockingOutcomeReceiver<Void, MigrationException> outcome = new BlockingOutcomeReceiver<>();
+        getHealthConnectManager().writeMigrationData(entities, newSingleThreadExecutor(), outcome);
+        outcome.awaitSuccess();
+    }
+
+    /**
+     * Calls {@link HealthConnectManager#finishMigration(Executor, OutcomeReceiver)} and waits for
+     * the call to finish.
+     */
+    public static void finishMigration() {
+        BlockingOutcomeReceiver<Void, MigrationException> outcome = new BlockingOutcomeReceiver<>();
+        getHealthConnectManager().finishMigration(newSingleThreadExecutor(), outcome);
+        outcome.awaitSuccess();
+    }
+
+    private static HealthConnectManager getHealthConnectManager() {
+        Context context = ApplicationProvider.getApplicationContext();
+        HealthConnectManager service = context.getSystemService(HealthConnectManager.class);
+        assertThat(service).isNotNull();
+
+        return service;
+    }
+}
diff --git a/tests/integrationtests/src/android/healthconnect/tests/backgroundread/BackgroundReadTest.java b/tests/integrationtests/src/android/healthconnect/tests/backgroundread/BackgroundReadTest.java
index 53a0eb6..de3cce1 100644
--- a/tests/integrationtests/src/android/healthconnect/tests/backgroundread/BackgroundReadTest.java
+++ b/tests/integrationtests/src/android/healthconnect/tests/backgroundread/BackgroundReadTest.java
@@ -42,8 +42,10 @@
 import android.health.connect.datatypes.ActiveCaloriesBurnedRecord;
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.units.Energy;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.healthconnect.cts.utils.TestReceiver;
-import android.healthconnect.test.app.BlockingOutcomeReceiver;
+import android.healthconnect.cts.utils.TestUtils;
+import android.healthconnect.test.app.DefaultOutcomeReceiver;
 import android.os.Bundle;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -51,6 +53,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -69,6 +72,11 @@
     private HealthConnectManager mManager;
     private String mInitialFeatureFlagValue;
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -88,7 +96,8 @@
     }
 
     @Test
-    public void testReadRecords_inBackgroundWithoutPermission_cantReadRecordsForOtherApp() {
+    public void testReadRecords_inBackgroundWithoutPermission_cantReadRecordsForOtherApp()
+            throws Exception {
         revokeBackgroundReadPermissionForTestApp();
         insertRecords();
 
@@ -111,8 +120,8 @@
     }
 
     private void insertRecords() {
-        final BlockingOutcomeReceiver<InsertRecordsResponse> outcomeReceiver =
-                new BlockingOutcomeReceiver<>();
+        DefaultOutcomeReceiver<InsertRecordsResponse> outcomeReceiver =
+                new DefaultOutcomeReceiver<>();
 
         mManager.insertRecords(
                 List.of(
@@ -129,7 +138,7 @@
     }
 
     @Test
-    public void testAggregate_inBackgroundWithoutPermission_securityError() {
+    public void testAggregate_inBackgroundWithoutPermission_securityError() throws Exception {
         revokeBackgroundReadPermissionForTestApp();
 
         sendCommandToTestAppReceiver(mContext, ACTION_AGGREGATE);
@@ -147,7 +156,7 @@
     }
 
     @Test
-    public void testGetChangeLogs_inBackgroundWithoutPermission_securityError() {
+    public void testGetChangeLogs_inBackgroundWithoutPermission_securityError() throws Exception {
         revokeBackgroundReadPermissionForTestApp();
 
         final Bundle extras = new Bundle();
@@ -158,7 +167,7 @@
     }
 
     @Test
-    public void testGetChangeLogs_inBackgroundWithPermission_success() {
+    public void testGetChangeLogs_inBackgroundWithPermission_success() throws Exception {
         revokeBackgroundReadPermissionForTestApp();
         sendCommandToTestAppReceiver(mContext, ACTION_GET_CHANGE_LOG_TOKEN);
         final String token = requireNonNull(TestReceiver.getResult()).getString(EXTRA_TOKEN);
@@ -178,11 +187,14 @@
                                 PKG_TEST_APP, READ_HEALTH_DATA_IN_BACKGROUND, mContext.getUser()));
     }
 
-    private void revokeBackgroundReadPermissionForTestApp() {
+    private void revokeBackgroundReadPermissionForTestApp() throws InterruptedException {
         runWithShellPermissionIdentity(
                 () ->
                         mPackageManager.revokeRuntimePermission(
                                 PKG_TEST_APP, READ_HEALTH_DATA_IN_BACKGROUND, mContext.getUser()));
+
+        // Wait a bit for the process to be killed
+        Thread.sleep(500);
     }
 
     private void assertSecurityError() {
diff --git a/tests/integrationtests/src/android/healthconnect/tests/backuprestore/BackupRestoreApiTest.java b/tests/integrationtests/src/android/healthconnect/tests/backuprestore/BackupRestoreApiTest.java
index 8bbdbd9..f34f799 100644
--- a/tests/integrationtests/src/android/healthconnect/tests/backuprestore/BackupRestoreApiTest.java
+++ b/tests/integrationtests/src/android/healthconnect/tests/backuprestore/BackupRestoreApiTest.java
@@ -36,6 +36,8 @@
 import android.health.connect.datatypes.HeightRecord;
 import android.health.connect.datatypes.Record;
 import android.health.connect.restore.StageRemoteDataException;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
+import android.healthconnect.cts.utils.TestUtils;
 import android.healthconnect.integrationtests.backuprestore.R;
 import android.os.FileUtils;
 import android.os.OutcomeReceiver;
@@ -49,6 +51,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -70,6 +73,11 @@
     private Context mContext;
     private HealthConnectManager mService;
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/tests/integrationtests/src/android/healthconnect/tests/backuprestore/BackupRestoreE2ETest.java b/tests/integrationtests/src/android/healthconnect/tests/backuprestore/BackupRestoreE2ETest.java
index 999cd0d..7c17347 100644
--- a/tests/integrationtests/src/android/healthconnect/tests/backuprestore/BackupRestoreE2ETest.java
+++ b/tests/integrationtests/src/android/healthconnect/tests/backuprestore/BackupRestoreE2ETest.java
@@ -43,6 +43,7 @@
 import android.health.connect.datatypes.Metadata;
 import android.health.connect.datatypes.Record;
 import android.health.connect.datatypes.units.Energy;
+import android.healthconnect.cts.utils.TestUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.platform.test.annotations.AppModeFull;
@@ -98,6 +99,9 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        if (!TestUtils.isHardwareSupported()) {
+            return;
+        }
         mBackupRestoreApkPackageName = getBackupRestoreApkPackageName();
         // enable backup on the test device
         mBackupUtils.enableBackup(true);
@@ -117,6 +121,9 @@
 
     public void testBackupThenRestore_over2000Records_expectDataIsRestoredCorrectly()
             throws Exception {
+        if (!TestUtils.isHardwareSupported()) {
+            return;
+        }
         int numOfRecords = 2050;
         List<Record> insertedRecords =
                 insertRecordsWithChunking(
@@ -137,6 +144,9 @@
     public void
             testPermissionsControllerIsRestoredBeforeHCRestore_expectGrantTimeIsRestoredCorrectly()
                     throws Exception {
+        if (!TestUtils.isHardwareSupported()) {
+            return;
+        }
         // revoke all permissions for both test apps to remove all stored grant time as setup step
         revokeAllPermissionsWithDelay(TEST_APP_1_PACKAGE_NAME, "");
         revokeAllPermissionsWithDelay(TEST_APP_2_PACKAGE_NAME, "");
diff --git a/tests/integrationtests/src/android/healthconnect/tests/migration/HealthConnectPermissionsMigrationTest.java b/tests/integrationtests/src/android/healthconnect/tests/migration/HealthConnectPermissionsMigrationTest.java
index e4a3b63..5f5153e 100644
--- a/tests/integrationtests/src/android/healthconnect/tests/migration/HealthConnectPermissionsMigrationTest.java
+++ b/tests/integrationtests/src/android/healthconnect/tests/migration/HealthConnectPermissionsMigrationTest.java
@@ -27,32 +27,28 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assume.assumeFalse;
-
 import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.health.connect.HealthConnectManager;
 import android.health.connect.migration.MigrationEntity;
-import android.health.connect.migration.MigrationException;
 import android.health.connect.migration.PermissionMigrationPayload;
-import android.healthconnect.tests.permissions.PermissionsTestUtils;
-import android.os.OutcomeReceiver;
-import android.util.Log;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
+import android.healthconnect.cts.utils.TestUtils;
+import android.healthconnect.tests.IntegrationTestUtils;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.time.Instant;
 import java.time.Period;
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicReference;
 
 /** Integration tests for Health Connect permissions migration. */
@@ -64,9 +60,13 @@
     private Context mContext;
     private HealthConnectManager mHealthConnectManager;
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws Exception {
-        assumeFalse(isHardwareAutomotive());
         mContext = InstrumentationRegistry.getTargetContext();
         mHealthConnectManager = mContext.getSystemService(HealthConnectManager.class);
         revokeAllHealthPermissions(DEFAULT_APP_PACKAGE, null);
@@ -112,33 +112,17 @@
         return readGrantTime.get();
     }
 
-    private void migrate(MigrationEntity... entities) throws InterruptedException {
+    private void migrate(MigrationEntity... entities) {
         runWithShellPermissionIdentity(
-                PermissionsTestUtils::startMigration,
+                IntegrationTestUtils::startMigration,
                 Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA);
 
         runWithShellPermissionIdentity(
-                () -> {
-                    CountDownLatch latch = new CountDownLatch(1);
-                    mHealthConnectManager.writeMigrationData(
-                            List.of(entities),
-                            Executors.newSingleThreadExecutor(),
-                            new OutcomeReceiver<>() {
-                                @Override
-                                public void onResult(Void result) {
-                                    latch.countDown();
-                                }
-
-                                @Override
-                                public void onError(MigrationException exception) {
-                                    Log.e(TAG, exception.getMessage());
-                                }
-                            });
-                },
+                () -> IntegrationTestUtils.writeMigrationData(List.of(entities)),
                 Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA);
 
         runWithShellPermissionIdentity(
-                PermissionsTestUtils::finishMigration,
+                IntegrationTestUtils::finishMigration,
                 Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA);
     }
 
diff --git a/tests/integrationtests/src/android/healthconnect/tests/permissions/GrantTimeIntegrationTest.java b/tests/integrationtests/src/android/healthconnect/tests/permissions/GrantTimeIntegrationTest.java
index 0290cfd..f4a3485 100644
--- a/tests/integrationtests/src/android/healthconnect/tests/permissions/GrantTimeIntegrationTest.java
+++ b/tests/integrationtests/src/android/healthconnect/tests/permissions/GrantTimeIntegrationTest.java
@@ -29,11 +29,14 @@
 import android.content.Context;
 import android.health.connect.HealthConnectManager;
 import android.health.connect.HealthPermissions;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
+import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,6 +61,11 @@
     private Context mContext;
     private HealthConnectManager mHealthConnectManager;
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
diff --git a/tests/integrationtests/src/android/healthconnect/tests/permissions/HealthConnectWithManagePermissionsTest.java b/tests/integrationtests/src/android/healthconnect/tests/permissions/HealthConnectWithManagePermissionsTest.java
index 073bb16..6018f8c 100644
--- a/tests/integrationtests/src/android/healthconnect/tests/permissions/HealthConnectWithManagePermissionsTest.java
+++ b/tests/integrationtests/src/android/healthconnect/tests/permissions/HealthConnectWithManagePermissionsTest.java
@@ -32,12 +32,16 @@
 import android.content.pm.PackageManager;
 import android.health.connect.HealthConnectManager;
 import android.health.connect.HealthPermissions;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
+import android.healthconnect.cts.utils.TestUtils;
+import android.healthconnect.tests.IntegrationTestUtils;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -76,6 +80,11 @@
     private Context mContext;
     private HealthConnectManager mHealthConnectManager;
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
@@ -435,7 +444,7 @@
     @Test
     public void testPermissionApis_migrationInProgress_apisBlocked() throws Exception {
         runWithShellPermissionIdentity(
-                PermissionsTestUtils::startMigration,
+                IntegrationTestUtils::startMigration,
                 Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA);
 
         // Grant permission
@@ -451,7 +460,7 @@
 
         // Revoke permission
         runWithShellPermissionIdentity(
-                PermissionsTestUtils::startMigration,
+                IntegrationTestUtils::startMigration,
                 Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA);
 
         grantPermissionViaPackageManager(DEFAULT_APP_PACKAGE, DEFAULT_PERM);
@@ -492,7 +501,7 @@
                                 DEFAULT_APP_PACKAGE, List.of(DEFAULT_PERM, DEFAULT_PERM_2)));
 
         runWithShellPermissionIdentity(
-                PermissionsTestUtils::finishMigration,
+                IntegrationTestUtils::finishMigration,
                 Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA);
         assertPermGrantedForApp(DEFAULT_APP_PACKAGE, DEFAULT_PERM);
     }
diff --git a/tests/integrationtests/src/android/healthconnect/tests/permissions/HealthConnectWithoutManagePermissionsTest.java b/tests/integrationtests/src/android/healthconnect/tests/permissions/HealthConnectWithoutManagePermissionsTest.java
index 4807520..d6b30b4 100644
--- a/tests/integrationtests/src/android/healthconnect/tests/permissions/HealthConnectWithoutManagePermissionsTest.java
+++ b/tests/integrationtests/src/android/healthconnect/tests/permissions/HealthConnectWithoutManagePermissionsTest.java
@@ -22,11 +22,14 @@
 import android.content.Context;
 import android.health.connect.HealthConnectManager;
 import android.health.connect.HealthPermissions;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
+import android.healthconnect.cts.utils.TestUtils;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -50,6 +53,11 @@
     private Context mContext;
     private HealthConnectManager mHealthConnectManager;
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    TestUtils::isHardwareSupported, "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
diff --git a/tests/integrationtests/src/android/healthconnect/tests/permissions/PermissionsTestUtils.java b/tests/integrationtests/src/android/healthconnect/tests/permissions/PermissionsTestUtils.java
deleted file mode 100644
index b581932..0000000
--- a/tests/integrationtests/src/android/healthconnect/tests/permissions/PermissionsTestUtils.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.healthconnect.tests.permissions;
-
-import static com.google.common.truth.Truth.assertThat;
-
-
-import android.content.Context;
-import android.health.connect.HealthConnectManager;
-import android.health.connect.migration.MigrationException;
-import android.os.OutcomeReceiver;
-import android.util.Log;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-public class PermissionsTestUtils {
-    private static final String TAG = "HCPermissionsTestUtils";
-
-    public static void startMigration() throws InterruptedException {
-        Context context = ApplicationProvider.getApplicationContext();
-        CountDownLatch latch = new CountDownLatch(1);
-
-        HealthConnectManager service = context.getSystemService(HealthConnectManager.class);
-        assertThat(service).isNotNull();
-        service.startMigration(
-                Executors.newSingleThreadExecutor(),
-                new OutcomeReceiver<Void, MigrationException>() {
-                    @Override
-                    public void onResult(Void result) {
-                        latch.countDown();
-                    }
-
-                    @Override
-                    public void onError(MigrationException exception) {
-                        Log.e(TAG, exception.getMessage());
-                    }
-                });
-        assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue();
-    }
-
-    public static void finishMigration() throws InterruptedException {
-        Context context = ApplicationProvider.getApplicationContext();
-        CountDownLatch latch = new CountDownLatch(1);
-
-        HealthConnectManager service = context.getSystemService(HealthConnectManager.class);
-        assertThat(service).isNotNull();
-        service.finishMigration(
-                Executors.newSingleThreadExecutor(),
-                new OutcomeReceiver<Void, MigrationException>() {
-
-                    @Override
-                    public void onResult(Void result) {
-                        latch.countDown();
-                    }
-
-                    @Override
-                    public void onError(MigrationException exception) {
-                        Log.e(TAG, exception.getMessage());
-                    }
-                });
-        assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue();
-    }
-}
diff --git a/tests/unittests/Android.bp b/tests/unittests/Android.bp
index 23d2c18..0e18283 100644
--- a/tests/unittests/Android.bp
+++ b/tests/unittests/Android.bp
@@ -38,6 +38,7 @@
         "truth-prebuilt",
         "services.core",
         "androidx.test.ext.truth",
+        "cts-healthconnect-utils",
     ],
     jni_libs: [
         // Required for ExtendedMockito
diff --git a/tests/unittests/src/android/healthconnect/PageTokenWrapperTest.java b/tests/unittests/src/android/healthconnect/PageTokenWrapperTest.java
new file mode 100644
index 0000000..9add008
--- /dev/null
+++ b/tests/unittests/src/android/healthconnect/PageTokenWrapperTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.healthconnect;
+
+import static android.health.connect.Constants.DEFAULT_LONG;
+import static android.health.connect.PageTokenWrapper.EMPTY_PAGE_TOKEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.health.connect.PageTokenWrapper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Instant;
+
+@RunWith(AndroidJUnit4.class)
+public class PageTokenWrapperTest {
+    private static final long MAX_ALLOWED_TIME_MILLIS = (1L << 44) - 1;
+    private static final long MAX_ALLOWED_OFFSET = (1 << 18) - 1;
+
+    @Test
+    public void of_createInstance() {
+        boolean isAscending = false;
+        long timeMillis = 123;
+        int offset = 456;
+        PageTokenWrapper wrapper = PageTokenWrapper.of(isAscending, timeMillis, offset);
+
+        assertThat(wrapper.isAscending()).isEqualTo(isAscending);
+        assertThat(wrapper.timeMillis()).isEqualTo(timeMillis);
+        assertThat(wrapper.offset()).isEqualTo(offset);
+        assertThat(wrapper.isTimestampSet()).isTrue();
+        assertThat(wrapper.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void of_offsetTooLarge_setToMax() {
+        boolean isAscending = true;
+        PageTokenWrapper wrapper =
+                PageTokenWrapper.of(isAscending, /* timeMillis= */ 0, (int) MAX_ALLOWED_OFFSET + 1);
+        assertThat(wrapper.offset()).isEqualTo(MAX_ALLOWED_OFFSET);
+    }
+
+    @Test
+    public void of_invalidArgument_throws() {
+        boolean isAscending = true;
+        Throwable thrown;
+
+        thrown =
+                assertThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                PageTokenWrapper.of(
+                                        isAscending, /* timeMillis= */ -1, /* offset= */ 0));
+        assertThat(thrown.getMessage()).isEqualTo("timestamp can not be negative");
+
+        thrown =
+                assertThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                PageTokenWrapper.of(
+                                        isAscending, /* timeMillis= */ 0, /* offset= */ -1));
+        assertThat(thrown.getMessage()).isEqualTo("offset can not be negative");
+
+        thrown =
+                assertThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                PageTokenWrapper.of(
+                                        isAscending, MAX_ALLOWED_TIME_MILLIS + 1, /* offset= */ 0));
+        assertThat(thrown.getMessage()).isEqualTo("timestamp too large");
+    }
+
+    @Test
+    public void ofAscending_timestampNotSet() {
+        PageTokenWrapper wrapper = PageTokenWrapper.ofAscending(true);
+        assertThat(wrapper.isAscending()).isTrue();
+        assertThat(wrapper.isTimestampSet()).isFalse();
+        assertThat(wrapper.isEmpty()).isFalse();
+
+        wrapper = PageTokenWrapper.ofAscending(false);
+        assertThat(wrapper.isAscending()).isFalse();
+        assertThat(wrapper.isTimestampSet()).isFalse();
+        assertThat(wrapper.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void from_validPageToken_expectCorrectResult() {
+        boolean expectedIsAsc = true;
+        boolean unusedDefaultIsAsc = false;
+        long expectedTimeMillis = 1234;
+        int expectedOffset = 5678;
+
+        long validToken =
+                PageTokenWrapper.of(expectedIsAsc, expectedTimeMillis, expectedOffset).encode();
+
+        PageTokenWrapper wrapper = PageTokenWrapper.from(validToken, unusedDefaultIsAsc);
+
+        assertThat(wrapper.isAscending()).isEqualTo(expectedIsAsc);
+        assertThat(wrapper.timeMillis()).isEqualTo(expectedTimeMillis);
+        assertThat(wrapper.offset()).isEqualTo(expectedOffset);
+        assertThat(wrapper.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void from_pageTokenUnset_useDefaultIsAscending() {
+        PageTokenWrapper wrapper =
+                PageTokenWrapper.from(DEFAULT_LONG, /* defaultIsAscending= */ true);
+        assertThat(wrapper.isAscending()).isTrue();
+        assertThat(wrapper.isEmpty()).isFalse();
+
+        wrapper = PageTokenWrapper.from(DEFAULT_LONG, /* defaultIsAscending= */ false);
+        assertThat(wrapper.isAscending()).isFalse();
+        assertThat(wrapper.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void from_negativePageToken_throws() {
+        boolean unusedDefault = true;
+
+        Throwable thrown =
+                assertThrows(
+                        IllegalArgumentException.class,
+                        () -> PageTokenWrapper.from(-100, unusedDefault));
+        assertThat(thrown).hasMessageThat().contains("pageToken cannot be negative");
+    }
+
+    @Test
+    public void encodeAndFromToken_ascending_expectIsAscendingTrue() {
+        boolean unusedDefault = false;
+        PageTokenWrapper expected =
+                PageTokenWrapper.of(/* isAscending= */ true, 1234, /* offset= */ 0);
+        long token = expected.encode();
+
+        PageTokenWrapper result = PageTokenWrapper.from(token, unusedDefault);
+        assertThat(result.isAscending()).isTrue();
+    }
+
+    @Test
+    public void encodeAndFromToken_descending_expectIsAscendingFalse() {
+        boolean unusedDefault = true;
+        PageTokenWrapper wrapper =
+                PageTokenWrapper.of(/* isAscending= */ false, 5678, /* offset= */ 0);
+        long token = wrapper.encode();
+
+        PageTokenWrapper result = PageTokenWrapper.from(token, unusedDefault);
+        assertThat(result.isAscending()).isFalse();
+    }
+
+    @Test
+    public void encodeAndFromToken_currentTimestamp_expectCorrectTime() {
+        boolean unusedDefault = true;
+        long nowTimeMillis = Instant.now().toEpochMilli();
+        PageTokenWrapper wrapper =
+                PageTokenWrapper.of(/* isAscending= */ false, nowTimeMillis, /* offset= */ 0);
+        long token = wrapper.encode();
+
+        PageTokenWrapper result = PageTokenWrapper.from(token, unusedDefault);
+        assertThat(result.timeMillis()).isEqualTo(nowTimeMillis);
+    }
+
+    @Test
+    public void encodeAndFromToken_minTimestamps_expectCorrectTime() {
+        boolean unusedDefault = false;
+        PageTokenWrapper wrapper =
+                PageTokenWrapper.of(/* isAscending= */ true, /* timeMillis= */ 0, /* offset= */ 0);
+        long token = wrapper.encode();
+
+        PageTokenWrapper result = PageTokenWrapper.from(token, unusedDefault);
+        assertThat(result.timeMillis()).isEqualTo(0);
+    }
+
+    @Test
+    public void encodeAndFromToken_maxTimestamps_expectCorrectTime() {
+        boolean unusedDefault = true;
+        PageTokenWrapper wrapper =
+                PageTokenWrapper.of(
+                        /* isAscending= */ false, MAX_ALLOWED_TIME_MILLIS, /* offset= */ 0);
+        long token = wrapper.encode();
+
+        PageTokenWrapper result = PageTokenWrapper.from(token, unusedDefault);
+        assertThat(result.timeMillis()).isEqualTo(MAX_ALLOWED_TIME_MILLIS);
+    }
+
+    @Test
+    public void encodeAndFromToken_maxOffset_expectCorrectResult() {
+        boolean unusedDefault = true;
+        int maxOffset = (1 << 18) - 1;
+        long timestamp = Instant.now().toEpochMilli();
+
+        PageTokenWrapper wrapper =
+                PageTokenWrapper.of(/* isAscending= */ false, timestamp, maxOffset);
+        long token = wrapper.encode();
+
+        PageTokenWrapper result = PageTokenWrapper.from(token, unusedDefault);
+        assertThat(result.offset()).isEqualTo(maxOffset);
+    }
+
+    @Test
+    public void encodeAndFromToken_minOffset_expectCorrectResult() {
+        boolean unusedDefault = false;
+        int minOffset = 0;
+        long timestamp = Instant.now().toEpochMilli();
+        PageTokenWrapper wrapper =
+                PageTokenWrapper.of(/* isAscending= */ true, timestamp, minOffset);
+        long token = wrapper.encode();
+
+        PageTokenWrapper result = PageTokenWrapper.from(token, unusedDefault);
+        assertThat(result.offset()).isEqualTo(minOffset);
+    }
+
+    @Test
+    public void encode_pageTokenUnset_returnsDefaultLong() {
+        assertThat(EMPTY_PAGE_TOKEN.encode()).isEqualTo(DEFAULT_LONG);
+        assertThat(PageTokenWrapper.ofAscending(true).encode()).isEqualTo(DEFAULT_LONG);
+        assertThat(PageTokenWrapper.ofAscending(false).encode()).isEqualTo(DEFAULT_LONG);
+    }
+
+    @Test
+    public void emptyPageToken_isEmpty_expectTrue() {
+        assertThat(EMPTY_PAGE_TOKEN.isEmpty()).isTrue();
+    }
+
+    @Test
+    public void equals_sameValue_expectTrue() {
+        PageTokenWrapper wrapper1 =
+                PageTokenWrapper.of(
+                        /* isAscending= */ false, /* timeMillis= */ 1234, /* offset= */ 567);
+        PageTokenWrapper wrapper2 =
+                PageTokenWrapper.of(
+                        /* isAscending= */ false, /* timeMillis= */ 1234, /* offset= */ 567);
+
+        assertThat(wrapper1.equals(wrapper2)).isTrue();
+    }
+
+    @Test
+    public void equals_differentValue_expectFalse() {
+        PageTokenWrapper wrapper =
+                PageTokenWrapper.of(
+                        /* isAscending= */ false, /* timeMillis= */ 1234, /* offset= */ 567);
+        PageTokenWrapper differentIsAscending =
+                PageTokenWrapper.of(
+                        /* isAscending= */ true, /* timeMillis= */ 1234, /* offset= */ 567);
+        PageTokenWrapper differentTime =
+                PageTokenWrapper.of(
+                        /* isAscending= */ false, /* timeMillis= */ 123, /* offset= */ 567);
+        PageTokenWrapper differentOffset =
+                PageTokenWrapper.of(
+                        /* isAscending= */ false, /* timeMillis= */ 1234, /* offset= */ 5678);
+
+        assertThat(wrapper.equals(differentIsAscending)).isFalse();
+        assertThat(wrapper.equals(differentTime)).isFalse();
+        assertThat(wrapper.equals(differentOffset)).isFalse();
+    }
+
+    @Test
+    public void equals_nonEmptyAndEmpty_expectFalse() {
+        PageTokenWrapper ascWrapper = PageTokenWrapper.ofAscending(true);
+        PageTokenWrapper descWrapper = PageTokenWrapper.ofAscending(false);
+
+        assertThat(EMPTY_PAGE_TOKEN.equals(ascWrapper)).isFalse();
+        assertThat(EMPTY_PAGE_TOKEN.equals(descWrapper)).isFalse();
+    }
+}
diff --git a/tests/unittests/src/com/android/server/healthconnect/HealthConnectServiceImplTest.java b/tests/unittests/src/com/android/server/healthconnect/HealthConnectServiceImplTest.java
index 5c26019..d4dcb12 100644
--- a/tests/unittests/src/com/android/server/healthconnect/HealthConnectServiceImplTest.java
+++ b/tests/unittests/src/com/android/server/healthconnect/HealthConnectServiceImplTest.java
@@ -49,6 +49,7 @@
 import android.health.connect.migration.MigrationEntityParcel;
 import android.health.connect.migration.MigrationException;
 import android.health.connect.restore.StageRemoteDataRequest;
+import android.healthconnect.cts.utils.AssumptionCheckerRule;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -173,6 +174,12 @@
     private UserHandle mUserHandle;
     private File mMockDataDirectory;
 
+    @Rule
+    public AssumptionCheckerRule mSupportedHardwareRule =
+            new AssumptionCheckerRule(
+                    android.healthconnect.cts.utils.TestUtils::isHardwareSupported,
+                    "Tests should run on supported hardware only.");
+
     @Before
     public void setUp() throws Exception {
         when(UserHandle.of(anyInt())).thenCallRealMethod();
diff --git a/tests/unittests/src/com/android/server/healthconnect/storage/TransactionManagerTest.java b/tests/unittests/src/com/android/server/healthconnect/storage/TransactionManagerTest.java
index e3e8ab7..02d9351 100644
--- a/tests/unittests/src/com/android/server/healthconnect/storage/TransactionManagerTest.java
+++ b/tests/unittests/src/com/android/server/healthconnect/storage/TransactionManagerTest.java
@@ -24,6 +24,7 @@
 
 import static org.junit.Assert.assertThrows;
 
+import android.health.connect.PageTokenWrapper;
 import android.health.connect.ReadRecordsRequestUsingFilters;
 import android.health.connect.ReadRecordsRequestUsingIds;
 import android.health.connect.TimeInstantRangeFilter;
@@ -40,8 +41,6 @@
 import com.android.server.healthconnect.storage.datatypehelpers.HealthConnectDatabaseTestRule;
 import com.android.server.healthconnect.storage.datatypehelpers.TransactionTestUtils;
 import com.android.server.healthconnect.storage.request.ReadTransactionRequest;
-import com.android.server.healthconnect.storage.utils.PageTokenUtil;
-import com.android.server.healthconnect.storage.utils.PageTokenWrapper;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -165,14 +164,13 @@
                                         .build())
                         .setPageSize(1)
                         .build();
-        long expectedToken =
-                PageTokenUtil.encode(
-                        PageTokenWrapper.of(
-                                /* isAscending= */ true, /* timeMillis= */ 500, /* offset= */ 0));
+        PageTokenWrapper expectedToken =
+                PageTokenWrapper.of(
+                        /* isAscending= */ true, /* timeMillis= */ 500, /* offset= */ 0);
 
         ReadTransactionRequest readTransactionRequest =
                 getReadTransactionRequest(request.toReadRecordsRequestParcel());
-        Pair<List<RecordInternal<?>>, Long> result =
+        Pair<List<RecordInternal<?>>, PageTokenWrapper> result =
                 mTransactionManager.readRecordsAndPageToken(readTransactionRequest);
         List<RecordInternal<?>> records = result.first;
         assertThat(records).hasSize(1);
diff --git a/tests/unittests/src/com/android/server/healthconnect/storage/datatypehelpers/RecordHelperTest.java b/tests/unittests/src/com/android/server/healthconnect/storage/datatypehelpers/RecordHelperTest.java
index 397d1b9..f8b1112 100644
--- a/tests/unittests/src/com/android/server/healthconnect/storage/datatypehelpers/RecordHelperTest.java
+++ b/tests/unittests/src/com/android/server/healthconnect/storage/datatypehelpers/RecordHelperTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.healthconnect.storage.datatypehelpers;
 
-import static android.health.connect.Constants.DEFAULT_LONG;
 import static android.health.connect.Constants.MAXIMUM_ALLOWED_CURSOR_COUNT;
+import static android.health.connect.PageTokenWrapper.EMPTY_PAGE_TOKEN;
 
 import static com.android.server.healthconnect.storage.datatypehelpers.StepsRecordHelper.STEPS_TABLE_NAME;
 import static com.android.server.healthconnect.storage.datatypehelpers.TransactionTestUtils.createStepsRecord;
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertThrows;
 
 import android.database.Cursor;
+import android.health.connect.PageTokenWrapper;
 import android.health.connect.ReadRecordsRequestUsingFilters;
 import android.health.connect.TimeInstantRangeFilter;
 import android.health.connect.aidl.ReadRecordsRequestParcel;
@@ -42,8 +43,6 @@
 import com.android.server.healthconnect.storage.TransactionManager;
 import com.android.server.healthconnect.storage.request.ReadTableRequest;
 import com.android.server.healthconnect.storage.utils.OrderByClause;
-import com.android.server.healthconnect.storage.utils.PageTokenUtil;
-import com.android.server.healthconnect.storage.utils.PageTokenWrapper;
 import com.android.server.healthconnect.storage.utils.WhereClauses;
 
 import org.junit.After;
@@ -167,12 +166,12 @@
                         .setOrderBy(orderByStartTime)
                         .setLimit(pageSize + 1);
         try (Cursor cursor = mTransactionManager.read(request1)) {
-            Pair<List<RecordInternal<?>>, Long> page1 =
+            Pair<List<RecordInternal<?>>, PageTokenWrapper> page1 =
                     helper.getNextInternalRecordsPageAndToken(
                             cursor, pageSize, PageTokenWrapper.ofAscending(isAscending));
             assertThat(page1.first).hasSize(pageSize);
             assertThat(page1.first.get(0).getClientRecordId()).isEqualTo("client.id2");
-            assertThat(page1.second).isEqualTo(PageTokenUtil.encode(expectedPageToken));
+            assertThat(page1.second).isEqualTo(expectedPageToken);
         }
 
         WhereClauses whereClause =
@@ -185,11 +184,11 @@
                         .setWhereClause(whereClause)
                         .setLimit(pageSize + 1 + expectedOffset);
         try (Cursor cursor = mTransactionManager.read(request2)) {
-            Pair<List<RecordInternal<?>>, Long> page2 =
+            Pair<List<RecordInternal<?>>, PageTokenWrapper> page2 =
                     helper.getNextInternalRecordsPageAndToken(cursor, pageSize, expectedPageToken);
             assertThat(page2.first).hasSize(pageSize);
             assertThat(page2.first.get(0).getClientRecordId()).isEqualTo("client.id1");
-            assertThat(page2.second).isEqualTo(DEFAULT_LONG);
+            assertThat(page2.second).isEqualTo(EMPTY_PAGE_TOKEN);
         }
     }
 
@@ -251,32 +250,32 @@
         ReadTableRequest request1 =
                 getReadTableRequest(helper, readRequest1.toReadRecordsRequestParcel());
         try (Cursor cursor = mTransactionManager.read(request1)) {
-            Pair<List<RecordInternal<?>>, Long> page1 =
+            Pair<List<RecordInternal<?>>, PageTokenWrapper> page1 =
                     helper.getNextInternalRecordsPageAndToken(
                             cursor, pageSize, PageTokenWrapper.ofAscending(isAscending));
             assertThat(page1.first).hasSize(3);
             assertThat(page1.first.get(0).getClientRecordId()).isEqualTo("id1");
             assertThat(page1.first.get(1).getClientRecordId()).isEqualTo("id2");
             assertThat(page1.first.get(2).getClientRecordId()).isEqualTo("id3");
-            assertThat(page1.second).isEqualTo(PageTokenUtil.encode(expectedPageToken));
+            assertThat(page1.second).isEqualTo(expectedPageToken);
         }
 
         ReadRecordsRequestUsingFilters<StepsRecord> readRequest2 =
                 new ReadRecordsRequestUsingFilters.Builder<>(StepsRecord.class)
                         .setTimeRangeFilter(filter)
                         .setPageSize(pageSize)
-                        .setPageToken(PageTokenUtil.encode(expectedPageToken))
+                        .setPageToken(expectedPageToken.encode())
                         .build();
         ReadTableRequest request2 =
                 getReadTableRequest(helper, readRequest2.toReadRecordsRequestParcel());
         try (Cursor cursor = mTransactionManager.read(request2)) {
-            Pair<List<RecordInternal<?>>, Long> page2 =
+            Pair<List<RecordInternal<?>>, PageTokenWrapper> page2 =
                     helper.getNextInternalRecordsPageAndToken(cursor, pageSize, expectedPageToken);
             assertThat(page2.first).hasSize(pageSize);
             assertThat(page2.first.get(0).getClientRecordId()).isEqualTo("id4");
             assertThat(page2.first.get(1).getClientRecordId()).isEqualTo("id5");
             assertThat(page2.first.get(2).getClientRecordId()).isEqualTo("id6");
-            assertThat(page2.second).isEqualTo(DEFAULT_LONG);
+            assertThat(page2.second).isEqualTo(EMPTY_PAGE_TOKEN);
         }
     }
 
@@ -290,13 +289,13 @@
         PageTokenWrapper incorrectToken = PageTokenWrapper.of(true, 4000, 2);
         ReadTableRequest request = new ReadTableRequest(STEPS_TABLE_NAME);
         try (Cursor cursor = mTransactionManager.read(request)) {
-            Pair<List<RecordInternal<?>>, Long> result =
+            Pair<List<RecordInternal<?>>, PageTokenWrapper> result =
                     helper.getNextInternalRecordsPageAndToken(
                             cursor, /* requestSize= */ 2, incorrectToken);
             // skip the first record, but preserve the second because start time is different
             assertThat(result.first).hasSize(1);
             assertThat(result.first.get(0).getClientRecordId()).isEqualTo("id2");
-            assertThat(result.second).isEqualTo(DEFAULT_LONG);
+            assertThat(result.second).isEqualTo(EMPTY_PAGE_TOKEN);
         }
     }
 
diff --git a/tests/unittests/src/com/android/server/healthconnect/storage/request/ReadTransactionRequestTest.java b/tests/unittests/src/com/android/server/healthconnect/storage/request/ReadTransactionRequestTest.java
index dc0053b..cb464b8 100644
--- a/tests/unittests/src/com/android/server/healthconnect/storage/request/ReadTransactionRequestTest.java
+++ b/tests/unittests/src/com/android/server/healthconnect/storage/request/ReadTransactionRequestTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.health.connect.PageTokenWrapper;
 import android.health.connect.ReadRecordsRequestUsingFilters;
 import android.health.connect.ReadRecordsRequestUsingIds;
 import android.health.connect.datatypes.RecordTypeIdentifier;
@@ -30,8 +31,6 @@
 import com.android.server.healthconnect.HealthConnectUserContext;
 import com.android.server.healthconnect.storage.TransactionManager;
 import com.android.server.healthconnect.storage.datatypehelpers.HealthConnectDatabaseTestRule;
-import com.android.server.healthconnect.storage.utils.PageTokenUtil;
-import com.android.server.healthconnect.storage.utils.PageTokenWrapper;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -77,7 +76,7 @@
         PageTokenWrapper expectedToken = PageTokenWrapper.of(true, 9876, 2);
         ReadRecordsRequestUsingFilters<StepsRecord> readRecordsRequest =
                 new ReadRecordsRequestUsingFilters.Builder<>(StepsRecord.class)
-                        .setPageToken(PageTokenUtil.encode(expectedToken))
+                        .setPageToken(expectedToken.encode())
                         .setPageSize(500)
                         .build();
         ReadTransactionRequest request =
diff --git a/tests/unittests/src/com/android/server/healthconnect/storage/utils/PageTokenUtilTest.java b/tests/unittests/src/com/android/server/healthconnect/storage/utils/PageTokenUtilTest.java
deleted file mode 100644
index 8ca7951..0000000
--- a/tests/unittests/src/com/android/server/healthconnect/storage/utils/PageTokenUtilTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.healthconnect.storage.utils;
-
-import static android.health.connect.Constants.DEFAULT_LONG;
-
-import static com.android.server.healthconnect.storage.utils.PageTokenUtil.MAX_ALLOWED_TIME_MILLIS;
-import static com.android.server.healthconnect.storage.utils.PageTokenUtil.encode;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import static java.time.temporal.ChronoUnit.DAYS;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.time.Instant;
-
-@RunWith(AndroidJUnit4.class)
-public class PageTokenUtilTest {
-
-    @Test
-    public void encodeAndRetrieveIsAscending_expectCorrectResult() {
-        PageTokenWrapper wrapper =
-                PageTokenWrapper.of(/* isAscending= */ true, 1234, /* offset= */ 0);
-        long token = encode(wrapper);
-        assertThat(wrapper.isTimestampSet()).isTrue();
-        assertThat(decode(token, /* defaultIsAscending= */ false).isAscending()).isTrue();
-
-        wrapper = PageTokenWrapper.of(/* isAscending= */ false, 5678, /* offset= */ 0);
-        token = encode(wrapper);
-        assertThat(wrapper.isTimestampSet()).isTrue();
-        assertThat(decode(token, /* defaultIsAscending= */ true).isAscending()).isFalse();
-    }
-
-    @Test
-    public void encodeAndRetrieveTimestamp_expectCorrectResult() {
-        long nowTimeMillis = Instant.now().toEpochMilli();
-        PageTokenWrapper wrapper =
-                PageTokenWrapper.of(/* isAscending= */ false, nowTimeMillis, /* offset= */ 0);
-        long token = encode(wrapper);
-        assertThat(wrapper.isTimestampSet()).isTrue();
-        assertThat(decode(token).timeMillis()).isEqualTo(nowTimeMillis);
-
-        long futureTimeMillis = Instant.now().plus(36500, DAYS).toEpochMilli();
-        wrapper = PageTokenWrapper.of(/* isAscending= */ true, futureTimeMillis, /* offset= */ 0);
-        token = encode(wrapper);
-        assertThat(wrapper.isTimestampSet()).isTrue();
-        assertThat(decode(token).timeMillis()).isEqualTo(futureTimeMillis);
-
-        long pastTimeMillis = Instant.now().minus(3650, DAYS).toEpochMilli();
-        wrapper = PageTokenWrapper.of(/* isAscending= */ true, pastTimeMillis, /* offset= */ 0);
-        token = encode(wrapper);
-        assertThat(wrapper.isTimestampSet()).isTrue();
-        assertThat(decode(token).timeMillis()).isEqualTo(pastTimeMillis);
-
-        wrapper =
-                PageTokenWrapper.of(/* isAscending= */ true, /* timeMillis= */ 0, /* offset= */ 0);
-        token = encode(wrapper);
-        assertThat(wrapper.isTimestampSet()).isTrue();
-        assertThat(decode(token).timeMillis()).isEqualTo(0);
-
-        wrapper =
-                PageTokenWrapper.of(
-                        /* isAscending= */ true, MAX_ALLOWED_TIME_MILLIS, /* offset= */ 0);
-        token = encode(wrapper);
-        assertThat(wrapper.isTimestampSet()).isTrue();
-        assertThat(decode(token).timeMillis()).isEqualTo(MAX_ALLOWED_TIME_MILLIS);
-    }
-
-    @Test
-    public void encodeAndRetrieveOffset_expectCorrectResult() {
-        int maxOffset = (1 << 18) - 1;
-        int minOffset = 0;
-        long timestamp = Instant.now().toEpochMilli();
-
-        PageTokenWrapper wrapper =
-                PageTokenWrapper.of(/* isAscending= */ false, timestamp, maxOffset);
-        long token = encode(wrapper);
-        assertThat(wrapper.isTimestampSet()).isTrue();
-        assertThat(decode(token).offset()).isEqualTo(maxOffset);
-
-        wrapper = PageTokenWrapper.of(/* isAscending= */ true, timestamp, minOffset);
-        token = encode(wrapper);
-        assertThat(wrapper.isTimestampSet()).isTrue();
-        assertThat(decode(token).offset()).isEqualTo(minOffset);
-    }
-
-    @Test
-    public void decode_pageTokenNotSet_defaultIsAscendingUsed() {
-        PageTokenWrapper wrapper = decode(DEFAULT_LONG, /* defaultIsAscending= */ true);
-        assertThat(wrapper.isTimestampSet()).isFalse();
-        assertThat(wrapper.isAscending()).isTrue();
-
-        wrapper = decode(DEFAULT_LONG, /* defaultIsAscending= */ false);
-        assertThat(wrapper.isTimestampSet()).isFalse();
-        assertThat(wrapper.isAscending()).isFalse();
-    }
-
-    @Test
-    public void decode_invalidPageToken_throws() {
-        Throwable thrown =
-                assertThrows(
-                        IllegalArgumentException.class,
-                        () -> decode(-123, /* defaultIsAscending= */ true));
-        assertThat(thrown.getMessage()).isEqualTo("pageToken cannot be negative");
-    }
-
-    private static PageTokenWrapper decode(long pageToken) {
-        return decode(pageToken, /* defaultIsAscending= */ true);
-    }
-
-    private static PageTokenWrapper decode(long pageToken, boolean defaultIsAscending) {
-        return PageTokenUtil.decode(pageToken, defaultIsAscending);
-    }
-}
diff --git a/tests/unittests/src/com/android/server/healthconnect/storage/utils/PageTokenWrapperTest.java b/tests/unittests/src/com/android/server/healthconnect/storage/utils/PageTokenWrapperTest.java
deleted file mode 100644
index 9581c35..0000000
--- a/tests/unittests/src/com/android/server/healthconnect/storage/utils/PageTokenWrapperTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.healthconnect.storage.utils;
-
-import static com.android.server.healthconnect.storage.utils.PageTokenUtil.MAX_ALLOWED_OFFSET;
-import static com.android.server.healthconnect.storage.utils.PageTokenUtil.MAX_ALLOWED_TIME_MILLIS;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class PageTokenWrapperTest {
-    @Test
-    public void of_createInstance() {
-        boolean isAscending = false;
-        long timeMillis = 123;
-        int offset = 456;
-        PageTokenWrapper wrapper = PageTokenWrapper.of(isAscending, timeMillis, offset);
-
-        assertThat(wrapper.isAscending()).isEqualTo(isAscending);
-        assertThat(wrapper.timeMillis()).isEqualTo(timeMillis);
-        assertThat(wrapper.offset()).isEqualTo(offset);
-        assertThat(wrapper.isTimestampSet()).isTrue();
-    }
-
-    @Test
-    public void of_isAscending_timestampNotSet() {
-        PageTokenWrapper wrapper = PageTokenWrapper.ofAscending(/* isAscending= */ true);
-        assertThat(wrapper.isAscending()).isTrue();
-        assertThat(wrapper.timeMillis()).isEqualTo(0);
-        assertThat(wrapper.offset()).isEqualTo(0);
-        assertThat(wrapper.isTimestampSet()).isFalse();
-    }
-
-    @Test
-    public void of_offsetTooLarge_setToMax() {
-        boolean isAscending = true;
-        PageTokenWrapper wrapper =
-                PageTokenWrapper.of(isAscending, /* timeMillis= */ 0, (int) MAX_ALLOWED_OFFSET + 1);
-        assertThat(wrapper.offset()).isEqualTo(MAX_ALLOWED_OFFSET);
-    }
-
-    @Test
-    public void of_invalidArgument_throws() {
-        boolean isAscending = true;
-        Throwable thrown;
-
-        thrown =
-                assertThrows(
-                        IllegalArgumentException.class,
-                        () ->
-                                PageTokenWrapper.of(
-                                        isAscending, /* timeMillis= */ -1, /* offset= */ 0));
-        assertThat(thrown.getMessage()).isEqualTo("timestamp can not be negative");
-
-        thrown =
-                assertThrows(
-                        IllegalArgumentException.class,
-                        () ->
-                                PageTokenWrapper.of(
-                                        isAscending, /* timeMillis= */ 0, /* offset= */ -1));
-        assertThat(thrown.getMessage()).isEqualTo("offset can not be negative");
-
-        thrown =
-                assertThrows(
-                        IllegalArgumentException.class,
-                        () ->
-                                PageTokenWrapper.of(
-                                        isAscending, MAX_ALLOWED_TIME_MILLIS + 1, /* offset= */ 0));
-        assertThat(thrown.getMessage()).isEqualTo("timestamp too large");
-    }
-
-    @Test
-    public void equals_sameValue_expectTrue() {
-        PageTokenWrapper wrapper1 =
-                PageTokenWrapper.of(
-                        /* isAscending= */ false, /* timeMillis= */ 1234, /* offset= */ 567);
-        PageTokenWrapper wrapper2 =
-                PageTokenWrapper.of(
-                        /* isAscending= */ false, /* timeMillis= */ 1234, /* offset= */ 567);
-
-        assertThat(wrapper1.equals(wrapper2)).isTrue();
-    }
-
-    @Test
-    public void equals_differentValue_expectFalse() {
-        PageTokenWrapper wrapper =
-                PageTokenWrapper.of(
-                        /* isAscending= */ false, /* timeMillis= */ 1234, /* offset= */ 567);
-        PageTokenWrapper differentIsAscending =
-                PageTokenWrapper.of(
-                        /* isAscending= */ true, /* timeMillis= */ 1234, /* offset= */ 567);
-        PageTokenWrapper differentTime =
-                PageTokenWrapper.of(
-                        /* isAscending= */ false, /* timeMillis= */ 123, /* offset= */ 567);
-        PageTokenWrapper differentOffset =
-                PageTokenWrapper.of(
-                        /* isAscending= */ false, /* timeMillis= */ 1234, /* offset= */ 5678);
-
-        assertThat(wrapper.equals(differentIsAscending)).isFalse();
-        assertThat(wrapper.equals(differentTime)).isFalse();
-        assertThat(wrapper.equals(differentOffset)).isFalse();
-    }
-}