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 > [應用程式權限]"</string>
- <string name="search_breadcrumbs_data" msgid="6635428480372024040">"Health Connect > [資料和存取權]"</string>
+ <string name="search_breadcrumbs_permissions" msgid="2667471090347475796">"健康資料同步 > [應用程式權限]"</string>
+ <string name="search_breadcrumbs_data" msgid="6635428480372024040">"健康資料同步 > [資料和存取權]"</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 權限,請依序前往「設定」>「應用程式」"</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 > 應用程式權限"</string>
- <string name="search_breadcrumbs_data" msgid="6635428480372024040">"Health Connect > 資料和存取權"</string>
+ <string name="search_keywords_data" msgid="5359602744325490523">"健康資料同步, 健康資料, 健康類別, 資料存取, 活動, 身體測量資料, 經期追蹤, 營養, 睡眠, 生命徵象"</string>
+ <string name="search_breadcrumbs_permissions" msgid="2667471090347475796">"健康資料同步 > 應用程式權限"</string>
+ <string name="search_breadcrumbs_data" msgid="6635428480372024040">"健康資料同步 > 資料和存取權"</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();
- }
-}