• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.healthconnect.storage.datatypehelpers;
18 
19 import static android.health.connect.Constants.DEFAULT_PAGE_SIZE;
20 import static android.health.connect.accesslog.AccessLog.OperationType.OPERATION_TYPE_DELETE;
21 import static android.health.connect.accesslog.AccessLog.OperationType.OPERATION_TYPE_READ;
22 import static android.health.connect.accesslog.AccessLog.OperationType.OPERATION_TYPE_UPSERT;
23 import static android.health.connect.datatypes.FhirResource.FHIR_RESOURCE_TYPE_IMMUNIZATION;
24 import static android.health.connect.datatypes.MedicalResource.MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES;
25 import static android.health.connect.datatypes.MedicalResource.MEDICAL_RESOURCE_TYPE_VACCINES;
26 import static android.healthconnect.cts.phr.utils.PhrDataFactory.DATA_SOURCE_ID;
27 import static android.healthconnect.cts.phr.utils.PhrDataFactory.DATA_SOURCE_PACKAGE_NAME;
28 import static android.healthconnect.cts.phr.utils.PhrDataFactory.DIFFERENT_DATA_SOURCE_ID;
29 import static android.healthconnect.cts.phr.utils.PhrDataFactory.DIFFERENT_DATA_SOURCE_PACKAGE_NAME;
30 import static android.healthconnect.cts.phr.utils.PhrDataFactory.FHIR_RESOURCE_ID_IMMUNIZATION;
31 import static android.healthconnect.cts.phr.utils.PhrDataFactory.FHIR_VERSION_R4;
32 import static android.healthconnect.cts.phr.utils.PhrDataFactory.FHIR_VERSION_R4B;
33 import static android.healthconnect.cts.phr.utils.PhrDataFactory.createAllergyMedicalResource;
34 import static android.healthconnect.cts.phr.utils.PhrDataFactory.createDifferentVaccineMedicalResource;
35 import static android.healthconnect.cts.phr.utils.PhrDataFactory.createUpdatedAllergyMedicalResource;
36 import static android.healthconnect.cts.phr.utils.PhrDataFactory.createUpdatedVaccineMedicalResource;
37 import static android.healthconnect.cts.phr.utils.PhrDataFactory.createVaccineMedicalResource;
38 import static android.healthconnect.cts.phr.utils.PhrDataFactory.createVaccineMedicalResources;
39 import static android.healthconnect.cts.phr.utils.PhrDataFactory.getFhirResource;
40 import static android.healthconnect.cts.phr.utils.PhrDataFactory.getMedicalResourceId;
41 
42 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceHelper.DATA_SOURCE_ID_COLUMN_NAME;
43 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceHelper.FHIR_DATA_COLUMN_NAME;
44 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceHelper.FHIR_RESOURCE_ID_COLUMN_NAME;
45 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceHelper.FHIR_RESOURCE_TYPE_COLUMN_NAME;
46 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceHelper.MEDICAL_RESOURCE_TABLE_NAME;
47 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceHelper.getCreateTableRequest;
48 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceHelper.getPrimaryColumn;
49 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceHelper.getReadQueryForMedicalResourceTypeToDataSourceIdsMap;
50 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceIndicesHelper.getMedicalResourceTypeColumnName;
51 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceIndicesHelper.getParentColumnReference;
52 import static com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceIndicesHelper.getTableName;
53 import static com.android.server.healthconnect.storage.datatypehelpers.RecordHelper.LAST_MODIFIED_TIME_COLUMN_NAME;
54 import static com.android.server.healthconnect.storage.utils.StorageUtils.INTEGER_NOT_NULL;
55 import static com.android.server.healthconnect.storage.utils.StorageUtils.PRIMARY_AUTOINCREMENT;
56 import static com.android.server.healthconnect.storage.utils.StorageUtils.TEXT_NOT_NULL;
57 import static com.android.server.healthconnect.storage.utils.StorageUtils.generateMedicalResourceUUID;
58 import static com.android.server.healthconnect.storage.utils.StorageUtils.getCursorInt;
59 import static com.android.server.healthconnect.storage.utils.StorageUtils.getHexString;
60 import static com.android.server.healthconnect.testing.storage.PhrTestUtils.ACCESS_LOG_EQUIVALENCE;
61 import static com.android.server.healthconnect.testing.storage.PhrTestUtils.makeUpsertRequest;
62 
63 import static com.google.common.truth.Truth.assertThat;
64 
65 import static org.junit.Assert.assertThrows;
66 import static org.mockito.Mockito.mock;
67 
68 import android.content.ContentValues;
69 import android.content.Context;
70 import android.database.Cursor;
71 import android.health.connect.DeleteMedicalResourcesRequest;
72 import android.health.connect.MedicalResourceId;
73 import android.health.connect.ReadMedicalResourcesInitialRequest;
74 import android.health.connect.ReadMedicalResourcesPageRequest;
75 import android.health.connect.accesslog.AccessLog;
76 import android.health.connect.datatypes.FhirResource;
77 import android.health.connect.datatypes.MedicalDataSource;
78 import android.health.connect.datatypes.MedicalResource;
79 import android.healthconnect.cts.phr.utils.PhrDataFactory;
80 import android.os.UserHandle;
81 import android.platform.test.annotations.EnableFlags;
82 import android.platform.test.flag.junit.SetFlagsRule;
83 import android.util.Pair;
84 
85 import androidx.test.core.app.ApplicationProvider;
86 import androidx.test.ext.junit.runners.AndroidJUnit4;
87 
88 import com.android.healthfitness.flags.Flags;
89 import com.android.server.healthconnect.injector.HealthConnectInjector;
90 import com.android.server.healthconnect.injector.HealthConnectInjectorImpl;
91 import com.android.server.healthconnect.permission.FirstGrantTimeManager;
92 import com.android.server.healthconnect.permission.HealthPermissionIntentAppsTracker;
93 import com.android.server.healthconnect.phr.PhrPageTokenWrapper;
94 import com.android.server.healthconnect.phr.ReadMedicalResourcesInternalResponse;
95 import com.android.server.healthconnect.storage.TransactionManager;
96 import com.android.server.healthconnect.storage.request.CreateTableRequest;
97 import com.android.server.healthconnect.storage.request.ReadTableRequest;
98 import com.android.server.healthconnect.storage.request.UpsertMedicalResourceInternalRequest;
99 import com.android.server.healthconnect.storage.utils.StorageUtils;
100 import com.android.server.healthconnect.testing.fakes.FakeTimeSource;
101 import com.android.server.healthconnect.testing.storage.PhrTestUtils;
102 import com.android.server.healthconnect.testing.storage.TransactionTestUtils;
103 
104 import org.json.JSONException;
105 import org.junit.Before;
106 import org.junit.Rule;
107 import org.junit.Test;
108 import org.junit.rules.TemporaryFolder;
109 import org.junit.runner.RunWith;
110 import org.mockito.Mock;
111 import org.mockito.junit.MockitoJUnit;
112 import org.mockito.junit.MockitoRule;
113 
114 import java.time.Instant;
115 import java.util.ArrayList;
116 import java.util.Collection;
117 import java.util.Collections;
118 import java.util.Comparator;
119 import java.util.HashSet;
120 import java.util.List;
121 import java.util.Map;
122 import java.util.Set;
123 import java.util.UUID;
124 import java.util.stream.Collectors;
125 import java.util.stream.Stream;
126 
127 @RunWith(AndroidJUnit4.class)
128 public class MedicalResourceHelperTest {
129 
130     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
131     @Rule public final TemporaryFolder mEnvironmentDataDir = new TemporaryFolder();
132     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
133 
134     private static final long DATA_SOURCE_ROW_ID = 1234;
135     private static final String INVALID_PAGE_TOKEN = "aw==";
136     private static final Instant INSTANT_NOW = Instant.now();
137 
138     @Mock private AppOpLogsHelper mAppOpLogsHelper;
139 
140     private MedicalResourceHelper mMedicalResourceHelper;
141     private TransactionManager mTransactionManager;
142     private AccessLogsHelper mAccessLogsHelper;
143     private PhrTestUtils mUtil;
144     private FakeTimeSource mFakeTimeSource;
145     private UserHandle mUserHandle;
146 
147     @Before
setup()148     public void setup() {
149         Context context = ApplicationProvider.getApplicationContext();
150         mFakeTimeSource = new FakeTimeSource(INSTANT_NOW);
151         HealthConnectInjector healthConnectInjector =
152                 HealthConnectInjectorImpl.newBuilderForTest(context)
153                         .setFirstGrantTimeManager(mock(FirstGrantTimeManager.class))
154                         .setHealthPermissionIntentAppsTracker(
155                                 mock(HealthPermissionIntentAppsTracker.class))
156                         .setAppOpLogsHelper(mAppOpLogsHelper)
157                         .setTimeSource(mFakeTimeSource)
158                         .setEnvironmentDataDirectory(mEnvironmentDataDir.getRoot())
159                         .build();
160         mTransactionManager = healthConnectInjector.getTransactionManager();
161         mAccessLogsHelper = healthConnectInjector.getAccessLogsHelper();
162         mMedicalResourceHelper = healthConnectInjector.getMedicalResourceHelper();
163         mUtil = new PhrTestUtils(healthConnectInjector);
164         mUserHandle = context.getUser();
165 
166         TransactionTestUtils transactionTestUtils = new TransactionTestUtils(healthConnectInjector);
167         transactionTestUtils.insertApp(DATA_SOURCE_PACKAGE_NAME);
168         transactionTestUtils.insertApp(DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
169     }
170 
171     @Test
getCreateTableRequest_correctResult()172     public void getCreateTableRequest_correctResult() {
173         List<Pair<String, String>> columnInfoMedicalResource =
174                 List.of(
175                         Pair.create(getPrimaryColumn(), PRIMARY_AUTOINCREMENT),
176                         Pair.create(FHIR_RESOURCE_TYPE_COLUMN_NAME, INTEGER_NOT_NULL),
177                         Pair.create(FHIR_RESOURCE_ID_COLUMN_NAME, TEXT_NOT_NULL),
178                         Pair.create(FHIR_DATA_COLUMN_NAME, TEXT_NOT_NULL),
179                         Pair.create(DATA_SOURCE_ID_COLUMN_NAME, INTEGER_NOT_NULL),
180                         Pair.create(LAST_MODIFIED_TIME_COLUMN_NAME, INTEGER_NOT_NULL));
181         List<Pair<String, String>> columnInfoMedicalResourceIndices =
182                 List.of(
183                         Pair.create(getParentColumnReference(), INTEGER_NOT_NULL),
184                         Pair.create(getMedicalResourceTypeColumnName(), INTEGER_NOT_NULL));
185         CreateTableRequest childTableRequest =
186                 new CreateTableRequest(getTableName(), columnInfoMedicalResourceIndices)
187                         .addForeignKey(
188                                 MEDICAL_RESOURCE_TABLE_NAME,
189                                 Collections.singletonList(getParentColumnReference()),
190                                 Collections.singletonList(getPrimaryColumn()));
191         CreateTableRequest expected =
192                 new CreateTableRequest(MEDICAL_RESOURCE_TABLE_NAME, columnInfoMedicalResource)
193                         .addForeignKey(
194                                 MedicalDataSourceHelper.getMainTableName(),
195                                 Collections.singletonList(DATA_SOURCE_ID_COLUMN_NAME),
196                                 Collections.singletonList(
197                                         MedicalDataSourceHelper.getPrimaryColumnName()))
198                         .createIndexOn(LAST_MODIFIED_TIME_COLUMN_NAME)
199                         .setChildTableRequests(List.of(childTableRequest));
200 
201         CreateTableRequest result = getCreateTableRequest();
202         assertThat(result).isEqualTo(expected);
203     }
204 
205     @Test
getUpsertContentValues_correctResult()206     public void getUpsertContentValues_correctResult() {
207         FhirResource fhirResource = getFhirResource();
208         UpsertMedicalResourceInternalRequest upsertMedicalResourceInternalRequest =
209                 makeUpsertRequest(
210                         fhirResource,
211                         MEDICAL_RESOURCE_TYPE_VACCINES,
212                         FHIR_VERSION_R4,
213                         DATA_SOURCE_ID);
214 
215         ContentValues contentValues =
216                 MedicalResourceHelper.getContentValues(
217                         DATA_SOURCE_ROW_ID, upsertMedicalResourceInternalRequest, INSTANT_NOW);
218 
219         assertThat(contentValues.size()).isEqualTo(5);
220         assertThat(contentValues.get(FHIR_RESOURCE_TYPE_COLUMN_NAME))
221                 .isEqualTo(fhirResource.getType());
222         assertThat(contentValues.get(DATA_SOURCE_ID_COLUMN_NAME)).isEqualTo(DATA_SOURCE_ROW_ID);
223         assertThat(contentValues.get(FHIR_DATA_COLUMN_NAME)).isEqualTo(fhirResource.getData());
224         assertThat(contentValues.get(LAST_MODIFIED_TIME_COLUMN_NAME))
225                 .isEqualTo(INSTANT_NOW.toEpochMilli());
226     }
227 
228     @Test
getReadTableRequest_distinctResourceTypesUsingAppIdAndDataSourceIds_correctQuery()229     public void getReadTableRequest_distinctResourceTypesUsingAppIdAndDataSourceIds_correctQuery() {
230         List<UUID> dataSourceIds = List.of(UUID.fromString("a6194e35-698c-4706-918f-00bf959f123b"));
231         long appId = 123L;
232         List<String> hexValues = StorageUtils.getListOfHexStrings(dataSourceIds);
233 
234         ReadTableRequest request =
235                 MedicalResourceHelper.getFilteredReadRequestForDistinctResourceTypes(
236                         dataSourceIds, new HashSet<>(), appId);
237 
238         assertThat(request.getReadCommand())
239                 .isEqualTo(
240                         "SELECT DISTINCT medical_resource_type FROM ( SELECT * FROM"
241                                 + " medical_resource_table ) AS inner_query_result"
242                                 + "  INNER JOIN ( SELECT"
243                                 + " * FROM medical_data_source_table WHERE app_info_id = '"
244                                 + appId
245                                 + "'"
246                                 + " AND data_source_uuid IN ("
247                                 + String.join(", ", hexValues)
248                                 + ")) medical_data_source_table ON"
249                                 + " inner_query_result.data_source_id ="
250                                 + " medical_data_source_table.medical_data_source_row_id"
251                                 + "  INNER JOIN ( SELECT * FROM medical_resource_indices_table)"
252                                 + " medical_resource_indices_table ON"
253                                 + " inner_query_result.medical_resource_row_id ="
254                                 + " medical_resource_indices_table.medical_resource_id");
255     }
256 
257     @Test
getReadTableRequest_distinctResourceTypesUsingAppIdAndResourceTypes_correctQuery()258     public void getReadTableRequest_distinctResourceTypesUsingAppIdAndResourceTypes_correctQuery() {
259         long appId = 123L;
260 
261         ReadTableRequest request =
262                 MedicalResourceHelper.getFilteredReadRequestForDistinctResourceTypes(
263                         List.of(), Set.of(MEDICAL_RESOURCE_TYPE_VACCINES), appId);
264 
265         assertThat(request.getReadCommand())
266                 .isEqualTo(
267                         "SELECT DISTINCT medical_resource_type FROM ( SELECT * FROM"
268                                 + " medical_resource_table ) AS inner_query_result"
269                                 + "  INNER JOIN ( SELECT"
270                                 + " * FROM medical_data_source_table WHERE app_info_id = '"
271                                 + appId
272                                 + "'"
273                                 + ") medical_data_source_table ON"
274                                 + " inner_query_result.data_source_id ="
275                                 + " medical_data_source_table.medical_data_source_row_id"
276                                 + "  INNER JOIN ( SELECT * FROM medical_resource_indices_table"
277                                 + " WHERE medical_resource_type IN (1))"
278                                 + " medical_resource_indices_table ON"
279                                 + " inner_query_result.medical_resource_row_id ="
280                                 + " medical_resource_indices_table.medical_resource_id");
281     }
282 
283     @Test
getReadTableRequest_distinctResourceTypesUsingAppIdResourceTypesAndDataSourceIds()284     public void getReadTableRequest_distinctResourceTypesUsingAppIdResourceTypesAndDataSourceIds() {
285         List<UUID> dataSourceIds = List.of(UUID.fromString("a6194e35-698c-4706-918f-00bf959f123b"));
286         long appId = 123L;
287         List<String> hexValues = StorageUtils.getListOfHexStrings(dataSourceIds);
288 
289         ReadTableRequest request =
290                 MedicalResourceHelper.getFilteredReadRequestForDistinctResourceTypes(
291                         dataSourceIds, Set.of(MEDICAL_RESOURCE_TYPE_VACCINES), appId);
292 
293         assertThat(request.getReadCommand())
294                 .isEqualTo(
295                         "SELECT DISTINCT medical_resource_type FROM ( SELECT * FROM"
296                                 + " medical_resource_table ) AS inner_query_result"
297                                 + "  INNER JOIN ( SELECT"
298                                 + " * FROM medical_data_source_table WHERE app_info_id = '"
299                                 + appId
300                                 + "'"
301                                 + " AND data_source_uuid IN ("
302                                 + String.join(", ", hexValues)
303                                 + ")) medical_data_source_table ON"
304                                 + " inner_query_result.data_source_id ="
305                                 + " medical_data_source_table.medical_data_source_row_id"
306                                 + "  INNER JOIN ( SELECT * FROM medical_resource_indices_table"
307                                 + " WHERE medical_resource_type IN (1))"
308                                 + " medical_resource_indices_table ON"
309                                 + " inner_query_result.medical_resource_row_id ="
310                                 + " medical_resource_indices_table.medical_resource_id");
311     }
312 
313     @Test
314     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
insertAndUpdateResource_lastModifiedTimeIsUpdated()315     public void insertAndUpdateResource_lastModifiedTimeIsUpdated() throws JSONException {
316         MedicalDataSource dataSource =
317                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
318         MedicalResource resource = createVaccineMedicalResource(dataSource.getId());
319         UpsertMedicalResourceInternalRequest insertRequest = makeUpsertRequest(resource);
320         MedicalResource updatedResource = createUpdatedVaccineMedicalResource(dataSource.getId());
321         UpsertMedicalResourceInternalRequest updateRequest = makeUpsertRequest(updatedResource);
322 
323         mMedicalResourceHelper.upsertMedicalResources(
324                 DATA_SOURCE_PACKAGE_NAME, List.of(insertRequest));
325         long lastModifiedTimeOriginal =
326                 mUtil.readLastModifiedTimestamp(MEDICAL_RESOURCE_TABLE_NAME);
327         assertThat(lastModifiedTimeOriginal).isEqualTo(INSTANT_NOW.toEpochMilli());
328 
329         Instant upadatedInstant = Instant.now();
330         mFakeTimeSource.setInstant(upadatedInstant);
331 
332         mMedicalResourceHelper.upsertMedicalResources(
333                 DATA_SOURCE_PACKAGE_NAME, List.of(updateRequest));
334         long lastModifiedTimeUpdated = mUtil.readLastModifiedTimestamp(MEDICAL_RESOURCE_TABLE_NAME);
335 
336         assertThat(lastModifiedTimeUpdated).isEqualTo(upadatedInstant.toEpochMilli());
337     }
338 
339     @Test
340     @EnableFlags({Flags.FLAG_PHR_READ_MEDICAL_RESOURCES_FIX_QUERY_LIMIT})
getReadTableRequest_usingRequest_correctQuery()341     public void getReadTableRequest_usingRequest_correctQuery() {
342         ReadMedicalResourcesInitialRequest request =
343                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
344                         .build();
345         ReadTableRequest readRequest =
346                 MedicalResourceHelper.getReadTableRequestUsingRequestFilters(
347                         PhrPageTokenWrapper.from(request.toParcel()), request.getPageSize());
348 
349         // TODO(b/352546342): Explore improving the query building logic, so the query below
350         // is simpler to read, for context: http://shortn/_2YCniY49K6
351         assertThat(readRequest.getTableName()).isEqualTo(MEDICAL_RESOURCE_TABLE_NAME);
352         assertThat(readRequest.getReadCommand())
353                 .isEqualTo(
354                         "SELECT medical_resource_row_id,fhir_resource_type,fhir_resource_id,"
355                                 + "fhir_data,fhir_version,medical_resource_type,"
356                                 + "data_source_uuid,inner_query_result.last_modified_time"
357                                 + " AS medical_resource_last_modified_time FROM ( SELECT * FROM"
358                                 + " medical_resource_table ) AS"
359                                 + " inner_query_result  INNER JOIN ( SELECT * FROM"
360                                 + " medical_resource_indices_table WHERE medical_resource_type IN "
361                                 + "("
362                                 + MEDICAL_RESOURCE_TYPE_VACCINES
363                                 + ")"
364                                 + ") medical_resource_indices_table ON"
365                                 + " inner_query_result.medical_resource_row_id ="
366                                 + " medical_resource_indices_table.medical_resource_id  INNER JOIN"
367                                 + " medical_data_source_table ON inner_query_result.data_source_id"
368                                 + " = medical_data_source_table.medical_data_source_row_id"
369                                 + " ORDER BY medical_resource_row_id"
370                                 + " LIMIT "
371                                 + (DEFAULT_PAGE_SIZE + 1));
372     }
373 
374     @Test
375     @EnableFlags({
376         Flags.FLAG_PERSONAL_HEALTH_RECORD,
377         Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE,
378         Flags.FLAG_PHR_READ_MEDICAL_RESOURCES_FIX_QUERY_LIMIT
379     })
getReadTableRequest_usingRequestWithDataSourceIds_correctQuery()380     public void getReadTableRequest_usingRequestWithDataSourceIds_correctQuery() {
381         MedicalDataSource dataSource1 =
382                 mUtil.insertR4MedicalDataSource("id1", DATA_SOURCE_PACKAGE_NAME);
383         MedicalDataSource dataSource2 =
384                 mUtil.insertR4MedicalDataSource("id2", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
385         ReadMedicalResourcesInitialRequest request =
386                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
387                         .addDataSourceId(dataSource1.getId())
388                         .addDataSourceId(dataSource2.getId())
389                         .build();
390         ReadTableRequest readRequest =
391                 MedicalResourceHelper.getReadTableRequestUsingRequestFilters(
392                         PhrPageTokenWrapper.from(request.toParcel()), request.getPageSize());
393         List<String> dataSourceIdHexValues =
394                 StorageUtils.toUuids(request.getDataSourceIds()).stream()
395                         .map(StorageUtils::getHexString)
396                         .toList();
397 
398         assertThat(readRequest.getTableName()).isEqualTo(MEDICAL_RESOURCE_TABLE_NAME);
399         assertThat(readRequest.getReadCommand())
400                 .isEqualTo(
401                         "SELECT medical_resource_row_id,fhir_resource_type,fhir_resource_id,"
402                                 + "fhir_data,fhir_version,medical_resource_type,data_source_uuid,"
403                                 + "inner_query_result.last_modified_time"
404                                 + " AS medical_resource_last_modified_time FROM ( SELECT * FROM"
405                                 + " medical_resource_table ) AS"
406                                 + " inner_query_result  INNER JOIN ( SELECT * FROM"
407                                 + " medical_resource_indices_table WHERE medical_resource_type IN "
408                                 + "("
409                                 + MEDICAL_RESOURCE_TYPE_VACCINES
410                                 + ")) medical_resource_indices_table ON"
411                                 + " inner_query_result.medical_resource_row_id ="
412                                 + " medical_resource_indices_table.medical_resource_id  INNER JOIN"
413                                 + " ( SELECT * FROM medical_data_source_table WHERE"
414                                 + " data_source_uuid IN ("
415                                 + String.join(", ", dataSourceIdHexValues)
416                                 + ")) medical_data_source_table ON"
417                                 + " inner_query_result.data_source_id ="
418                                 + " medical_data_source_table.medical_data_source_row_id"
419                                 + " ORDER BY medical_resource_row_id"
420                                 + " LIMIT "
421                                 + (DEFAULT_PAGE_SIZE + 1));
422     }
423 
424     @Test
getReadQuery_forMedicalResourceTypeToDataSourceIdsMap_correctQuery()425     public void getReadQuery_forMedicalResourceTypeToDataSourceIdsMap_correctQuery() {
426         String readQuery = getReadQueryForMedicalResourceTypeToDataSourceIdsMap();
427 
428         assertThat(readQuery)
429                 .isEqualTo(
430                         "SELECT medical_resource_type, "
431                                 + "GROUP_CONCAT(data_source_id, ',') AS data_source_id "
432                                 + "FROM ("
433                                 + "SELECT DISTINCT medical_resource_type,data_source_id "
434                                 + "FROM ( SELECT * FROM medical_resource_table )"
435                                 + " AS inner_query_result "
436                                 + " INNER JOIN medical_resource_indices_table"
437                                 + " ON inner_query_result.medical_resource_row_id"
438                                 + " = medical_resource_indices_table.medical_resource_id)"
439                                 + " GROUP BY medical_resource_type");
440     }
441 
442     @Test
443     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readMedicalResourcesByIds_dbEmpty_returnsEmpty()444     public void readMedicalResourcesByIds_dbEmpty_returnsEmpty() {
445         List<MedicalResourceId> medicalResourceIds = List.of(getMedicalResourceId());
446 
447         List<MedicalResource> resources =
448                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
449                         medicalResourceIds);
450 
451         assertThat(resources).isEmpty();
452     }
453 
454     @Test
455     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readMedicalResourcesByRequest_dbEmpty_returnsEmpty()456     public void readMedicalResourcesByRequest_dbEmpty_returnsEmpty() {
457         ReadMedicalResourcesInitialRequest request =
458                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
459                         .build();
460 
461         ReadMedicalResourcesInternalResponse result =
462                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
463                         PhrPageTokenWrapper.from(request.toParcel()), request.getPageSize());
464 
465         assertThat(result.getMedicalResources()).isEmpty();
466         assertThat(result.getPageToken()).isEqualTo(null);
467         assertThat(result.getRemainingCount()).isEqualTo(0);
468     }
469 
470     @Test
471     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
upsertAndReadMedicalResourcesByRequest_MedicalResourceTypeDoesNotExist_success()472     public void upsertAndReadMedicalResourcesByRequest_MedicalResourceTypeDoesNotExist_success() {
473         MedicalDataSource dataSource =
474                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
475         MedicalResource vaccine = createVaccineMedicalResource(dataSource.getId());
476 
477         List<MedicalResource> upsertedResources =
478                 mMedicalResourceHelper.upsertMedicalResources(
479                         DATA_SOURCE_PACKAGE_NAME, List.of(makeUpsertRequest(vaccine)));
480         ReadMedicalResourcesInitialRequest readAllAllergiesRequest =
481                 new ReadMedicalResourcesInitialRequest.Builder(
482                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES)
483                         .build();
484         ReadMedicalResourcesInternalResponse result =
485                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
486                         PhrPageTokenWrapper.from(readAllAllergiesRequest.toParcel()),
487                         readAllAllergiesRequest.getPageSize());
488 
489         assertThat(upsertedResources).containsExactly(vaccine);
490         assertThat(result.getMedicalResources()).isEmpty();
491         assertThat(result.getPageToken()).isEqualTo(null);
492         assertThat(result.getRemainingCount()).isEqualTo(0);
493     }
494 
495     @Test
496     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
upsertMedicalResourcesSameDataSource_readMedicalResourcesByRequest_success()497     public void upsertMedicalResourcesSameDataSource_readMedicalResourcesByRequest_success() {
498         // Upsert 3 resources in this test: vaccine, differentVaccine and allergy, all
499         // with the same data source.
500         MedicalDataSource dataSource =
501                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
502         MedicalResource vaccine = createVaccineMedicalResource(dataSource.getId());
503         MedicalResource differentVaccine =
504                 createDifferentVaccineMedicalResource(dataSource.getId());
505         MedicalResource allergy = createAllergyMedicalResource(dataSource.getId());
506         List<MedicalResource> upsertedResources =
507                 mMedicalResourceHelper.upsertMedicalResources(
508                         DATA_SOURCE_PACKAGE_NAME,
509                         List.of(
510                                 makeUpsertRequest(vaccine),
511                                 makeUpsertRequest(differentVaccine),
512                                 makeUpsertRequest(allergy)));
513 
514         ReadMedicalResourcesInitialRequest readAllAllergiesRequest =
515                 new ReadMedicalResourcesInitialRequest.Builder(
516                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES)
517                         .build();
518         ReadMedicalResourcesInitialRequest readAllVaccinesRequest =
519                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
520                         .build();
521         ReadMedicalResourcesInitialRequest readVaccinesFromSameDataSourceRequest =
522                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
523                         .addDataSourceId(dataSource.getId())
524                         .build();
525         ReadMedicalResourcesInitialRequest readVaccinesFromDifferentDataSourceRequest =
526                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
527                         .addDataSourceId(DIFFERENT_DATA_SOURCE_ID)
528                         .build();
529         ReadMedicalResourcesInternalResponse allAllergiesResult =
530                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
531                         PhrPageTokenWrapper.from(readAllAllergiesRequest.toParcel()),
532                         readAllAllergiesRequest.getPageSize());
533         ReadMedicalResourcesInternalResponse allVaccinesResult =
534                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
535                         PhrPageTokenWrapper.from(readAllVaccinesRequest.toParcel()),
536                         readAllVaccinesRequest.getPageSize());
537         ReadMedicalResourcesInternalResponse vaccinesFromSameDataSourceResult =
538                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
539                         PhrPageTokenWrapper.from(readVaccinesFromSameDataSourceRequest.toParcel()),
540                         readVaccinesFromSameDataSourceRequest.getPageSize());
541         ReadMedicalResourcesInternalResponse vaccinesFromDifferentDataSourceResult =
542                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
543                         PhrPageTokenWrapper.from(
544                                 readVaccinesFromDifferentDataSourceRequest.toParcel()),
545                         readVaccinesFromDifferentDataSourceRequest.getPageSize());
546 
547         assertThat(upsertedResources).containsExactly(vaccine, differentVaccine, allergy);
548         assertThat(allAllergiesResult.getMedicalResources()).containsExactly(allergy);
549         assertThat(allAllergiesResult.getPageToken()).isEqualTo(null);
550         assertThat(allAllergiesResult.getRemainingCount()).isEqualTo(0);
551         assertThat(allVaccinesResult.getMedicalResources())
552                 .containsExactly(vaccine, differentVaccine);
553         assertThat(allVaccinesResult.getPageToken()).isEqualTo(null);
554         assertThat(allVaccinesResult.getRemainingCount()).isEqualTo(0);
555         assertThat(vaccinesFromSameDataSourceResult.getMedicalResources())
556                 .containsExactly(vaccine, differentVaccine);
557         assertThat(vaccinesFromSameDataSourceResult.getPageToken()).isEqualTo(null);
558         assertThat(vaccinesFromDifferentDataSourceResult.getMedicalResources()).isEmpty();
559         assertThat(vaccinesFromDifferentDataSourceResult.getPageToken()).isEqualTo(null);
560         assertThat(vaccinesFromDifferentDataSourceResult.getRemainingCount()).isEqualTo(0);
561     }
562 
563     @Test
564     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
upsertMedicalResourcesDifferentDataSources_readMedicalResourcesByRequest_success()565     public void upsertMedicalResourcesDifferentDataSources_readMedicalResourcesByRequest_success() {
566         // Upsert 3 resources in this test: vaccine, differentVaccine and allergy. Among
567         // which vaccine and allergy are from data source 1 and the differentVaccine is
568         // from data source 2.
569         MedicalDataSource dataSource1 =
570                 mUtil.insertR4MedicalDataSource("id1", DATA_SOURCE_PACKAGE_NAME);
571         MedicalDataSource dataSource2 =
572                 mUtil.insertR4MedicalDataSource("id2", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
573         MedicalResource vaccineDS1 = createVaccineMedicalResource(dataSource1.getId());
574         MedicalResource differentVaccineDS2 =
575                 createDifferentVaccineMedicalResource(dataSource2.getId());
576         MedicalResource allergyDS1 = createAllergyMedicalResource(dataSource1.getId());
577         List<MedicalResource> upsertedResources =
578                 mMedicalResourceHelper.upsertMedicalResources(
579                         DATA_SOURCE_PACKAGE_NAME,
580                         List.of(makeUpsertRequest(vaccineDS1), makeUpsertRequest(allergyDS1)));
581         upsertedResources.addAll(
582                 mMedicalResourceHelper.upsertMedicalResources(
583                         DIFFERENT_DATA_SOURCE_PACKAGE_NAME,
584                         List.of(makeUpsertRequest(differentVaccineDS2))));
585 
586         ReadMedicalResourcesInitialRequest readAllAllergiesRequest =
587                 new ReadMedicalResourcesInitialRequest.Builder(
588                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES)
589                         .build();
590         ReadMedicalResourcesInitialRequest readAllVaccinesRequest =
591                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
592                         .build();
593         ReadMedicalResourcesInitialRequest readVaccinesFromDataSource1Request =
594                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
595                         .addDataSourceId(dataSource1.getId())
596                         .build();
597         ReadMedicalResourcesInitialRequest readVaccinesFromDataSource2Request =
598                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
599                         .addDataSourceId(dataSource2.getId())
600                         .build();
601         ReadMedicalResourcesInternalResponse allAllergiesResult =
602                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
603                         PhrPageTokenWrapper.from(readAllAllergiesRequest.toParcel()),
604                         readAllAllergiesRequest.getPageSize());
605         ReadMedicalResourcesInternalResponse allVaccinesResult =
606                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
607                         PhrPageTokenWrapper.from(readAllVaccinesRequest.toParcel()),
608                         readAllVaccinesRequest.getPageSize());
609         ReadMedicalResourcesInternalResponse vaccinesFromDataSource1Result =
610                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
611                         PhrPageTokenWrapper.from(readVaccinesFromDataSource1Request.toParcel()),
612                         readVaccinesFromDataSource1Request.getPageSize());
613         ReadMedicalResourcesInternalResponse vaccinesFromDataSource2Result =
614                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
615                         PhrPageTokenWrapper.from(readVaccinesFromDataSource2Request.toParcel()),
616                         readVaccinesFromDataSource2Request.getPageSize());
617 
618         assertThat(upsertedResources).containsExactly(vaccineDS1, differentVaccineDS2, allergyDS1);
619         assertThat(allAllergiesResult.getMedicalResources()).containsExactly(allergyDS1);
620         assertThat(allAllergiesResult.getPageToken()).isEqualTo(null);
621         assertThat(allAllergiesResult.getRemainingCount()).isEqualTo(0);
622         assertThat(allVaccinesResult.getMedicalResources())
623                 .containsExactly(vaccineDS1, differentVaccineDS2);
624         assertThat(allVaccinesResult.getPageToken()).isEqualTo(null);
625         assertThat(allVaccinesResult.getRemainingCount()).isEqualTo(0);
626         assertThat(vaccinesFromDataSource1Result.getMedicalResources()).containsExactly(vaccineDS1);
627         assertThat(vaccinesFromDataSource1Result.getPageToken()).isEqualTo(null);
628         assertThat(vaccinesFromDataSource1Result.getRemainingCount()).isEqualTo(0);
629         assertThat(vaccinesFromDataSource2Result.getMedicalResources())
630                 .containsExactly(differentVaccineDS2);
631         assertThat(vaccinesFromDataSource2Result.getPageToken()).isEqualTo(null);
632         assertThat(vaccinesFromDataSource2Result.getRemainingCount()).isEqualTo(0);
633     }
634 
635     @Test
636     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
insertMedicalResources_dataSourceNotInserted_exceptionThrown()637     public void insertMedicalResources_dataSourceNotInserted_exceptionThrown() {
638         FhirResource fhirResource = getFhirResource();
639         String datasourceId = "acc6c726-b7ea-42f1-a063-e34f5b4e6247";
640         UpsertMedicalResourceInternalRequest upsertMedicalResourceInternalRequest =
641                 makeUpsertRequest(
642                         fhirResource,
643                         MEDICAL_RESOURCE_TYPE_VACCINES,
644                         FHIR_VERSION_R4,
645                         datasourceId);
646 
647         Throwable thrown =
648                 assertThrows(
649                         IllegalArgumentException.class,
650                         () ->
651                                 mMedicalResourceHelper.upsertMedicalResources(
652                                         DATA_SOURCE_PACKAGE_NAME,
653                                         List.of(upsertMedicalResourceInternalRequest)));
654         assertThat(thrown)
655                 .hasMessageThat()
656                 .contains(
657                         "Invalid data source id: "
658                                 + upsertMedicalResourceInternalRequest.getDataSourceId());
659     }
660 
661     @Test
662     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
insertMedicalResources_dataSourceBelongsToDifferentApp_exceptionThrownNoWrite()663     public void insertMedicalResources_dataSourceBelongsToDifferentApp_exceptionThrownNoWrite() {
664         MedicalDataSource ownDataSource =
665                 mUtil.insertR4MedicalDataSource("id1", DATA_SOURCE_PACKAGE_NAME);
666         MedicalDataSource differentDataSource =
667                 mUtil.insertR4MedicalDataSource("id2", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
668         UpsertMedicalResourceInternalRequest upsertRequestOwnDataSource =
669                 makeUpsertRequest(
670                         getFhirResource(),
671                         MEDICAL_RESOURCE_TYPE_VACCINES,
672                         FHIR_VERSION_R4,
673                         ownDataSource.getId());
674         UpsertMedicalResourceInternalRequest upsertRequestDifferentDataSource =
675                 makeUpsertRequest(
676                         getFhirResource(),
677                         MEDICAL_RESOURCE_TYPE_VACCINES,
678                         FHIR_VERSION_R4,
679                         differentDataSource.getId());
680 
681         Throwable thrown =
682                 assertThrows(
683                         IllegalArgumentException.class,
684                         () ->
685                                 mMedicalResourceHelper.upsertMedicalResources(
686                                         DATA_SOURCE_PACKAGE_NAME,
687                                         List.of(
688                                                 upsertRequestOwnDataSource,
689                                                 upsertRequestDifferentDataSource)));
690         assertThat(thrown)
691                 .hasMessageThat()
692                 .contains("Invalid data source id: " + differentDataSource.getId());
693         assertThat(getMedicalResourcesTableRowCount()).isEqualTo(0);
694     }
695 
696     @Test
697     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
upsertMedicalResources_fhirVersionDoesNotMatchDataSource_exceptionThrown()698     public void upsertMedicalResources_fhirVersionDoesNotMatchDataSource_exceptionThrown() {
699         MedicalDataSource dataSource =
700                 mUtil.insertR4MedicalDataSource("id1", DATA_SOURCE_PACKAGE_NAME);
701         FhirResource fhirResource = getFhirResource();
702         UpsertMedicalResourceInternalRequest upsertMedicalResourceInternalRequest =
703                 makeUpsertRequest(
704                         fhirResource,
705                         MEDICAL_RESOURCE_TYPE_VACCINES,
706                         FHIR_VERSION_R4B,
707                         dataSource.getId());
708 
709         Throwable thrown =
710                 assertThrows(
711                         IllegalArgumentException.class,
712                         () ->
713                                 mMedicalResourceHelper.upsertMedicalResources(
714                                         DATA_SOURCE_PACKAGE_NAME,
715                                         List.of(upsertMedicalResourceInternalRequest)));
716         assertThat(thrown)
717                 .hasMessageThat()
718                 .contains(
719                         String.format(
720                                 "Invalid fhir version: %s. It did not match the data source's fhir"
721                                         + " version",
722                                 FHIR_VERSION_R4B));
723     }
724 
725     @Test
726     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readSubsetOfResourcesByIds_multipleResourcesUpserted_success()727     public void readSubsetOfResourcesByIds_multipleResourcesUpserted_success() {
728         MedicalDataSource dataSource =
729                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
730         MedicalResource resource1 = createVaccineMedicalResource(dataSource.getId());
731         MedicalResource resource2 = createAllergyMedicalResource(dataSource.getId());
732         List<MedicalResource> upsertedMedicalResources =
733                 mMedicalResourceHelper.upsertMedicalResources(
734                         DATA_SOURCE_PACKAGE_NAME,
735                         List.of(makeUpsertRequest(resource1), makeUpsertRequest(resource2)));
736         List<MedicalResource> readResource1Result =
737                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
738                         List.of(resource1.getId()));
739         List<Integer> indicesResult = readEntriesInMedicalResourceIndicesTable();
740 
741         assertThat(upsertedMedicalResources).containsExactly(resource1, resource2);
742         assertThat(indicesResult)
743                 .containsExactly(
744                         MEDICAL_RESOURCE_TYPE_VACCINES,
745                         MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES);
746         assertThat(readResource1Result).containsExactly(resource1);
747     }
748 
749     @Test
750     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
insertMedicalResources_returnsMedicalResources()751     public void insertMedicalResources_returnsMedicalResources() {
752         MedicalDataSource dataSource =
753                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
754         MedicalResource expectedResource = createVaccineMedicalResource(dataSource.getId());
755         UpsertMedicalResourceInternalRequest upsertMedicalResourceInternalRequest =
756                 makeUpsertRequest(expectedResource);
757 
758         List<MedicalResource> result =
759                 mMedicalResourceHelper.upsertMedicalResources(
760                         DATA_SOURCE_PACKAGE_NAME, List.of(upsertMedicalResourceInternalRequest));
761 
762         assertThat(result).containsExactly(expectedResource);
763     }
764 
765     @Test
766     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
insertSingleMedicalResource_readSingleResource()767     public void insertSingleMedicalResource_readSingleResource() {
768         MedicalDataSource dataSource =
769                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
770         MedicalResource expectedResource = createVaccineMedicalResource(dataSource.getId());
771         UpsertMedicalResourceInternalRequest upsertMedicalResourceInternalRequest =
772                 makeUpsertRequest(expectedResource);
773         List<MedicalResourceId> ids = List.of(expectedResource.getId());
774 
775         List<MedicalResource> upsertedMedicalResources =
776                 mMedicalResourceHelper.upsertMedicalResources(
777                         DATA_SOURCE_PACKAGE_NAME, List.of(upsertMedicalResourceInternalRequest));
778         List<MedicalResource> result =
779                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(ids);
780         List<Integer> indicesResult = readEntriesInMedicalResourceIndicesTable();
781 
782         assertThat(result).isEqualTo(upsertedMedicalResources);
783         assertThat(result).containsExactly(expectedResource);
784         assertThat(indicesResult).containsExactly(MEDICAL_RESOURCE_TYPE_VACCINES);
785     }
786 
787     @Test
788     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_noReadOrWritePermissions_throws()789     public void readById_noReadOrWritePermissions_throws() {
790         assertThrows(
791                 IllegalStateException.class,
792                 () ->
793                         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
794                                 List.of(),
795                                 /* grantedReadMedicalResourceTypes= */ Set.of(),
796                                 DATA_SOURCE_PACKAGE_NAME,
797                                 /* hasWritePermission= */ false,
798                                 /* isCalledFromBgWithoutBgRead= */ false));
799     }
800 
801     @Test
802     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readById_inBgWithoutBgPerm_hasWritePerm_noAccessLog()803     public void readById_inBgWithoutBgPerm_hasWritePerm_noAccessLog() {
804         List<MedicalResourceId> ids = List.of(getMedicalResourceId());
805         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
806                 ids,
807                 Set.of(),
808                 DATA_SOURCE_PACKAGE_NAME,
809                 /* hasWritePermission= */ true,
810                 /* isCalledFromBgWithoutBgRead= */ true);
811 
812         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle)).isEmpty();
813     }
814 
815     @Test
816     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readById_inBgWithoutBgPerm_hasWritePerm_hasReadPermForResourceTypes_noAccessLog()817     public void readById_inBgWithoutBgPerm_hasWritePerm_hasReadPermForResourceTypes_noAccessLog() {
818         List<MedicalResourceId> ids = List.of(getMedicalResourceId());
819         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
820                 ids,
821                 Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
822                 DATA_SOURCE_PACKAGE_NAME,
823                 /* hasWritePermission= */ true,
824                 /* isCalledFromBgWithoutBgRead= */ true);
825 
826         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle)).isEmpty();
827     }
828 
829     @Test
830     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readById_inBgWithoutBgPerm_noWritePerm_vaccineReadPermOnly_noAccessLog()831     public void readById_inBgWithoutBgPerm_noWritePerm_vaccineReadPermOnly_noAccessLog() {
832         List<MedicalResourceId> ids = List.of(getMedicalResourceId());
833         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
834                 ids,
835                 Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
836                 DATA_SOURCE_PACKAGE_NAME,
837                 /* hasWritePermission= */ false,
838                 /* isCalledFromBgWithoutBgRead= */ true);
839 
840         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle)).isEmpty();
841     }
842 
843     @Test
844     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readById_expectAccessLogsOnlyContainsNonSelfRead()845     public void readById_expectAccessLogsOnlyContainsNonSelfRead() {
846         MedicalDataSource dataSource1 =
847                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
848         MedicalDataSource dataSource2 =
849                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
850         MedicalResource vaccinePackage1 = createVaccineMedicalResource(dataSource1.getId());
851         MedicalResource vaccinePackage2 = createVaccineMedicalResource(dataSource2.getId());
852         MedicalResource allergyResourcePackage2 = createAllergyMedicalResource(dataSource2.getId());
853         mMedicalResourceHelper.upsertMedicalResources(
854                 DATA_SOURCE_PACKAGE_NAME, List.of(makeUpsertRequest(vaccinePackage1)));
855         mMedicalResourceHelper.upsertMedicalResources(
856                 DIFFERENT_DATA_SOURCE_PACKAGE_NAME,
857                 List.of(
858                         makeUpsertRequest(vaccinePackage2),
859                         makeUpsertRequest(allergyResourcePackage2)));
860         // Clear access logs table, so that only the access logs from read will be present
861         mAccessLogsHelper.clearData(mTransactionManager);
862 
863         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
864                 List.of(
865                         vaccinePackage1.getId(),
866                         vaccinePackage2.getId(),
867                         allergyResourcePackage2.getId()),
868                 Set.of(MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES),
869                 DATA_SOURCE_PACKAGE_NAME,
870                 /* hasWritePermission= */ true,
871                 /* isCalledFromBgWithoutBgRead= */ false);
872 
873         // Testing the case where calling app:
874         // is calling from foreground or background with permission.
875         // has MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES read permission.
876         // has write permission.
877         // The data that the calling app can read: vaccinePackage1 (through selfRead)
878         // allergyResourcePackage2 (through read permission)
879         // In this case, read access log is only created for non self read data:
880         // MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES.
881         AccessLog expected =
882                 new AccessLog(
883                         DATA_SOURCE_PACKAGE_NAME,
884                         INSTANT_NOW.toEpochMilli(),
885                         OPERATION_TYPE_READ,
886                         /* medicalResourceTypes= */ Set.of(
887                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES),
888                         /* isMedicalDataSourceAccessed= */ false);
889         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
890                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
891                 .containsExactly(expected);
892     }
893 
894     @Test
895     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readById_expectAccessLogsWhenAppHasNoWritePermHasReadPermButReadOnlySelfData()896     public void readById_expectAccessLogsWhenAppHasNoWritePermHasReadPermButReadOnlySelfData() {
897         MedicalDataSource dataSource1 =
898                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
899         MedicalDataSource dataSource2 =
900                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
901         MedicalResource vaccinePackage1 = createVaccineMedicalResource(dataSource1.getId());
902         MedicalResource allergyResourcePackage2 = createAllergyMedicalResource(dataSource2.getId());
903         mMedicalResourceHelper.upsertMedicalResources(
904                 DATA_SOURCE_PACKAGE_NAME, List.of(makeUpsertRequest(vaccinePackage1)));
905         mMedicalResourceHelper.upsertMedicalResources(
906                 DIFFERENT_DATA_SOURCE_PACKAGE_NAME,
907                 List.of(makeUpsertRequest(allergyResourcePackage2)));
908         // Clear access logs table, so that only the access logs from read will be present
909         mAccessLogsHelper.clearData(mTransactionManager);
910 
911         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
912                 List.of(vaccinePackage1.getId(), allergyResourcePackage2.getId()),
913                 Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
914                 DATA_SOURCE_PACKAGE_NAME,
915                 /* hasWritePermission= */ false,
916                 /* isCalledFromBgWithoutBgRead= */ false);
917 
918         // Testing the case where calling app:
919         // is calling from foreground or background with permission.
920         // has MEDICAL_RESOURCE_TYPE_VACCINES read permission.
921         // no write permission.
922         // The data that the calling app can read: vaccinePackage1 (through read permission)
923         // In this case, read access log is created based on the intention of the app
924         // even though the actual data accessed is self data: MEDICAL_RESOURCE_TYPE_VACCINES.
925         AccessLog expected =
926                 new AccessLog(
927                         DATA_SOURCE_PACKAGE_NAME,
928                         INSTANT_NOW.toEpochMilli(),
929                         OPERATION_TYPE_READ,
930                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
931                         /* isMedicalDataSourceAccessed= */ false);
932         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
933                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
934                 .containsExactly(expected);
935     }
936 
937     @Test
938     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readById_expectAccessLogsWhenAppHasNoWritePermHasReadPermReadNonSelfData()939     public void readById_expectAccessLogsWhenAppHasNoWritePermHasReadPermReadNonSelfData() {
940         String dataSource =
941                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME).getId();
942         MedicalResource vaccineDifferentPackage = createVaccineMedicalResource(dataSource);
943         mMedicalResourceHelper.upsertMedicalResources(
944                 DIFFERENT_DATA_SOURCE_PACKAGE_NAME,
945                 List.of(makeUpsertRequest(vaccineDifferentPackage)));
946         // Clear access logs table, so that only the access logs from read will be present
947         mAccessLogsHelper.clearData(mTransactionManager);
948 
949         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
950                 List.of(vaccineDifferentPackage.getId()),
951                 Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
952                 DATA_SOURCE_PACKAGE_NAME,
953                 /* hasWritePermission= */ false,
954                 /* isCalledFromBgWithoutBgRead= */ false);
955 
956         // Testing the case where calling app:
957         // is calling from foreground or background with permission.
958         // has MEDICAL_RESOURCE_TYPE_VACCINES read permission.
959         // no write permission.
960         // The data that the calling app can read: vaccine (through read permission)
961         // In this case, read access log is created: MEDICAL_RESOURCE_TYPE_VACCINES.
962         AccessLog expected =
963                 new AccessLog(
964                         DATA_SOURCE_PACKAGE_NAME,
965                         INSTANT_NOW.toEpochMilli(),
966                         OPERATION_TYPE_READ,
967                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
968                         /* isMedicalDataSourceAccessed= */ false);
969         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
970                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
971                 .containsExactly(expected);
972     }
973 
974     @Test
975     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readById_inForegroundOrBgWithPerm_hasReadVaccine_noResourceRead_noAccessLog()976     public void readById_inForegroundOrBgWithPerm_hasReadVaccine_noResourceRead_noAccessLog() {
977         List<MedicalResourceId> ids = List.of(getMedicalResourceId());
978         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
979                 ids,
980                 Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
981                 DATA_SOURCE_PACKAGE_NAME,
982                 /* hasWritePermission= */ true,
983                 /* isCalledFromBgWithoutBgRead= */ false);
984 
985         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle)).isEmpty();
986     }
987 
988     @Test
989     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readById_inForegroundOrBgWithPerm_hasWritePerm_noReadPerm_noAccessLog()990     public void readById_inForegroundOrBgWithPerm_hasWritePerm_noReadPerm_noAccessLog() {
991         List<MedicalResourceId> ids = List.of(getMedicalResourceId());
992         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
993                 ids,
994                 Set.of(),
995                 DATA_SOURCE_PACKAGE_NAME,
996                 /* hasWritePermission= */ true,
997                 /* isCalledFromBgWithoutBgRead= */ false);
998 
999         // No access log should be created since app is intending to access self data as it has
1000         // no read permissions.
1001         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle)).isEmpty();
1002     }
1003 
1004     @Test
1005     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readById_expectAccessLogsWhenAppHasWritePermHasReadPermReadSelfData()1006     public void readById_expectAccessLogsWhenAppHasWritePermHasReadPermReadSelfData() {
1007         MedicalDataSource dataSource =
1008                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1009         MedicalResource vaccine =
1010                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource);
1011         // Clear access logs table, so that only the access logs from read will be present
1012         mAccessLogsHelper.clearData(mTransactionManager);
1013 
1014         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1015                 List.of(vaccine.getId()),
1016                 Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1017                 DATA_SOURCE_PACKAGE_NAME,
1018                 /* hasWritePermission= */ true,
1019                 /* isCalledFromBgWithoutBgRead= */ false);
1020 
1021         // Testing the case where calling app:
1022         // is calling from foreground or background with permission.
1023         // has MEDICAL_RESOURCE_TYPE_VACCINES read permission.
1024         // has write permission.
1025         // The data that the calling app can read: vaccine (through read permission)
1026         // In this case, read access log is created based on the intention of the app
1027         // even though the actual data accessed is self data: MEDICAL_RESOURCE_TYPE_VACCINES.
1028         AccessLog expected =
1029                 new AccessLog(
1030                         DATA_SOURCE_PACKAGE_NAME,
1031                         INSTANT_NOW.toEpochMilli(),
1032                         OPERATION_TYPE_READ,
1033                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1034                         /* isMedicalDataSourceAccessed= */ false);
1035         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
1036                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
1037                 .containsExactly(expected);
1038     }
1039 
1040     @Test
1041     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readById_expectAccessLogsForEachResourceTypeReadBasedOnReadPerm()1042     public void readById_expectAccessLogsForEachResourceTypeReadBasedOnReadPerm() {
1043         MedicalDataSource dataSource =
1044                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1045         MedicalResource vaccine =
1046                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource);
1047         // Clear access logs table, so that only the access logs from read will be present
1048         mAccessLogsHelper.clearData(mTransactionManager);
1049 
1050         mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1051                 List.of(vaccine.getId()),
1052                 Set.of(
1053                         MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES,
1054                         MEDICAL_RESOURCE_TYPE_VACCINES),
1055                 DATA_SOURCE_PACKAGE_NAME,
1056                 /* hasWritePermission= */ true,
1057                 /* isCalledFromBgWithoutBgRead= */ false);
1058 
1059         // Testing the case where calling app:
1060         // is calling from foreground or background with permission.
1061         // has MEDICAL_RESOURCE_TYPE_VACCINES and MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES
1062         // read permission.
1063         // has write permission.
1064         // The data that the calling app reads: vaccine (through read permission)
1065         // In this case, read access log is created only for: MEDICAL_RESOURCE_TYPE_VACCINES.
1066         // Even though the app has read permission for MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES,
1067         // the app did
1068         // not read any data of that type, so no access logs added for that.
1069         AccessLog expected =
1070                 new AccessLog(
1071                         DATA_SOURCE_PACKAGE_NAME,
1072                         INSTANT_NOW.toEpochMilli(),
1073                         OPERATION_TYPE_READ,
1074                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1075                         /* isMedicalDataSourceAccessed= */ false);
1076         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
1077                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
1078                 .containsExactly(expected);
1079     }
1080 
1081     @Test
1082     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_inBgWithoutBgPerm_hasWritePerm_success()1083     public void readById_inBgWithoutBgPerm_hasWritePerm_success() {
1084         MedicalDataSource dataSource1 =
1085                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1086         MedicalDataSource dataSource2 =
1087                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1088         MedicalResource vaccineDatasource1 =
1089                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1090         MedicalResource allergyDatasource1 =
1091                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
1092         MedicalResource vaccineDatasource2 =
1093                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
1094         MedicalResource allergyDatasource2 =
1095                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1096         List<MedicalResourceId> ids =
1097                 List.of(
1098                         vaccineDatasource1.getId(),
1099                         vaccineDatasource2.getId(),
1100                         allergyDatasource1.getId(),
1101                         allergyDatasource2.getId());
1102 
1103         List<MedicalResource> result =
1104                 mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1105                         ids,
1106                         Set.of(),
1107                         DATA_SOURCE_PACKAGE_NAME,
1108                         /* hasWritePermission= */ true,
1109                         /* isCalledFromBgWithoutBgRead= */ true);
1110 
1111         assertThat(result).containsExactly(vaccineDatasource1, allergyDatasource1);
1112     }
1113 
1114     @Test
1115     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_inBgWithoutBgPerm_hasWritePerm_hasReadPermForResourceTypes_success()1116     public void readById_inBgWithoutBgPerm_hasWritePerm_hasReadPermForResourceTypes_success() {
1117         MedicalDataSource dataSource1 =
1118                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1119         MedicalDataSource dataSource2 =
1120                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1121         MedicalResource vaccineDatasource1 =
1122                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1123         MedicalResource allergyDatasource1 =
1124                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
1125         MedicalResource vaccineDatasource2 =
1126                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
1127         MedicalResource allergyDatasource2 =
1128                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1129         List<MedicalResourceId> ids =
1130                 List.of(
1131                         vaccineDatasource1.getId(),
1132                         vaccineDatasource2.getId(),
1133                         allergyDatasource1.getId(),
1134                         allergyDatasource2.getId());
1135 
1136         List<MedicalResource> result =
1137                 mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1138                         ids,
1139                         Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1140                         DATA_SOURCE_PACKAGE_NAME,
1141                         /* hasWritePermission= */ true,
1142                         /* isCalledFromBgWithoutBgRead= */ true);
1143 
1144         assertThat(result).containsExactly(vaccineDatasource1, allergyDatasource1);
1145     }
1146 
1147     @Test
1148     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_inBgWithoutBgPerm_noWritePerm_vaccineReadPermOnly_success()1149     public void readById_inBgWithoutBgPerm_noWritePerm_vaccineReadPermOnly_success() {
1150         MedicalDataSource dataSource1 =
1151                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1152         MedicalDataSource dataSource2 =
1153                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1154         MedicalResource vaccineDatasource1 =
1155                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1156         MedicalResource allergyDatasource1 =
1157                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
1158         MedicalResource vaccineDatasource2 =
1159                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
1160         MedicalResource allergyDatasource2 =
1161                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1162         List<MedicalResourceId> ids =
1163                 List.of(
1164                         vaccineDatasource1.getId(),
1165                         vaccineDatasource2.getId(),
1166                         allergyDatasource1.getId(),
1167                         allergyDatasource2.getId());
1168 
1169         List<MedicalResource> result =
1170                 mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1171                         ids,
1172                         Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1173                         DATA_SOURCE_PACKAGE_NAME,
1174                         /* hasWritePermission= */ false,
1175                         /* isCalledFromBgWithoutBgRead= */ true);
1176 
1177         assertThat(result).containsExactly(vaccineDatasource1);
1178     }
1179 
1180     @Test
1181     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_inBgWithoutBgPerm_noWritePerm_bothAllergyAndVaccineReadPerm_success()1182     public void readById_inBgWithoutBgPerm_noWritePerm_bothAllergyAndVaccineReadPerm_success() {
1183         MedicalDataSource dataSource1 =
1184                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1185         MedicalDataSource dataSource2 =
1186                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1187         MedicalResource vaccineDatasource1 =
1188                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1189         MedicalResource allergyDatasource1 =
1190                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
1191         MedicalResource vaccineDatasource2 =
1192                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
1193         MedicalResource allergyDatasource2 =
1194                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1195         List<MedicalResourceId> ids =
1196                 List.of(
1197                         vaccineDatasource1.getId(),
1198                         vaccineDatasource2.getId(),
1199                         allergyDatasource1.getId(),
1200                         allergyDatasource2.getId());
1201 
1202         List<MedicalResource> result =
1203                 mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1204                         ids,
1205                         Set.of(
1206                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES,
1207                                 MEDICAL_RESOURCE_TYPE_VACCINES),
1208                         DATA_SOURCE_PACKAGE_NAME,
1209                         /* hasWritePermission= */ false,
1210                         /* isCalledFromBgWithoutBgRead= */ true);
1211 
1212         assertThat(result).containsExactly(vaccineDatasource1, allergyDatasource1);
1213     }
1214 
1215     @Test
1216     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_inForegroundOrinBgWithBgPerm_noWritePerm_success()1217     public void readById_inForegroundOrinBgWithBgPerm_noWritePerm_success() {
1218         MedicalDataSource dataSource1 =
1219                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1220         MedicalDataSource dataSource2 =
1221                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1222         MedicalResource vaccineDatasource1 =
1223                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1224         MedicalResource allergyDatasource1 =
1225                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
1226         MedicalResource vaccineDatasource2 =
1227                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
1228         MedicalResource allergyDatasource2 =
1229                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1230         List<MedicalResourceId> ids =
1231                 List.of(
1232                         vaccineDatasource1.getId(),
1233                         vaccineDatasource2.getId(),
1234                         allergyDatasource1.getId(),
1235                         allergyDatasource2.getId());
1236 
1237         List<MedicalResource> result =
1238                 mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1239                         ids,
1240                         Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1241                         DIFFERENT_DATA_SOURCE_PACKAGE_NAME,
1242                         /* hasWritePermission= */ false,
1243                         /* isCalledFromBgWithoutBgRead= */ false);
1244 
1245         assertThat(result).containsExactly(vaccineDatasource1, vaccineDatasource2);
1246     }
1247 
1248     @Test
1249     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_inForeground_hasWritePerm_noReadResourceTypesPerm_canOnlyReadSelfData()1250     public void readById_inForeground_hasWritePerm_noReadResourceTypesPerm_canOnlyReadSelfData() {
1251         MedicalDataSource dataSource1 =
1252                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1253         MedicalDataSource dataSource2 =
1254                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1255         MedicalResource vaccineDatasource1 =
1256                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1257         MedicalResource allergyDatasource1 =
1258                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
1259         MedicalResource vaccineDatasource2 =
1260                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
1261         MedicalResource allergyDatasource2 =
1262                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1263         List<MedicalResourceId> ids =
1264                 List.of(
1265                         vaccineDatasource1.getId(),
1266                         vaccineDatasource2.getId(),
1267                         allergyDatasource1.getId(),
1268                         allergyDatasource2.getId());
1269 
1270         List<MedicalResource> result =
1271                 mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1272                         ids,
1273                         Set.of(),
1274                         DATA_SOURCE_PACKAGE_NAME,
1275                         /* hasWritePermission= */ true,
1276                         /* isCalledFromBgWithoutBgRead= */ false);
1277 
1278         assertThat(result).containsExactly(vaccineDatasource1, allergyDatasource1);
1279     }
1280 
1281     @Test
1282     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_inForeground_noWritePerm_readVaccinePerm_canOnlyReadVaccine()1283     public void readById_inForeground_noWritePerm_readVaccinePerm_canOnlyReadVaccine() {
1284         MedicalDataSource dataSource1 =
1285                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1286         MedicalDataSource dataSource2 =
1287                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1288         MedicalResource vaccineDatasource1 =
1289                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1290         MedicalResource allergyDatasource1 =
1291                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
1292         MedicalResource vaccineDatasource2 =
1293                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
1294         MedicalResource allergyDatasource2 =
1295                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1296         List<MedicalResourceId> ids =
1297                 List.of(
1298                         vaccineDatasource1.getId(),
1299                         vaccineDatasource2.getId(),
1300                         allergyDatasource1.getId(),
1301                         allergyDatasource2.getId());
1302 
1303         List<MedicalResource> result =
1304                 mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1305                         ids,
1306                         Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1307                         DATA_SOURCE_PACKAGE_NAME,
1308                         /* hasWritePermission= */ false,
1309                         /* isCalledFromBgWithoutBgRead= */ false);
1310 
1311         assertThat(result).containsExactly(vaccineDatasource1, vaccineDatasource2);
1312     }
1313 
1314     @Test
1315     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_inForeground_noWritePerm_readAllergyPerm_canOnlyReadAllergy()1316     public void readById_inForeground_noWritePerm_readAllergyPerm_canOnlyReadAllergy() {
1317         MedicalDataSource dataSource1 =
1318                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1319         MedicalDataSource dataSource2 =
1320                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1321         MedicalResource vaccineDatasource1 =
1322                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1323         MedicalResource allergyDatasource1 =
1324                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
1325         MedicalResource vaccineDatasource2 =
1326                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
1327         MedicalResource allergyDatasource2 =
1328                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1329         List<MedicalResourceId> ids =
1330                 List.of(
1331                         vaccineDatasource1.getId(),
1332                         vaccineDatasource2.getId(),
1333                         allergyDatasource1.getId(),
1334                         allergyDatasource2.getId());
1335 
1336         List<MedicalResource> result =
1337                 mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1338                         ids,
1339                         Set.of(MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES),
1340                         DATA_SOURCE_PACKAGE_NAME,
1341                         /* hasWritePermission= */ false,
1342                         /* isCalledFromBgWithoutBgRead= */ false);
1343 
1344         assertThat(result).containsExactly(allergyDatasource1, allergyDatasource2);
1345     }
1346 
1347     @Test
1348     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_inForeground_noWritePerm_readVaccineAndAllergyPerm_canReadBoth()1349     public void readById_inForeground_noWritePerm_readVaccineAndAllergyPerm_canReadBoth() {
1350         MedicalDataSource dataSource1 =
1351                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1352         MedicalDataSource dataSource2 =
1353                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1354         MedicalResource vaccineDatasource1 =
1355                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1356         MedicalResource allergyDatasource1 =
1357                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
1358         MedicalResource vaccineDatasource2 =
1359                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
1360         MedicalResource allergyDatasource2 =
1361                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1362         List<MedicalResourceId> ids =
1363                 List.of(
1364                         vaccineDatasource1.getId(),
1365                         vaccineDatasource2.getId(),
1366                         allergyDatasource1.getId(),
1367                         allergyDatasource2.getId());
1368 
1369         List<MedicalResource> result =
1370                 mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1371                         ids,
1372                         Set.of(
1373                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES,
1374                                 MEDICAL_RESOURCE_TYPE_VACCINES),
1375                         DATA_SOURCE_PACKAGE_NAME,
1376                         /* hasWritePermission= */ false,
1377                         /* isCalledFromBgWithoutBgRead= */ false);
1378 
1379         assertThat(result)
1380                 .containsExactly(
1381                         vaccineDatasource1,
1382                         vaccineDatasource2,
1383                         allergyDatasource1,
1384                         allergyDatasource2);
1385     }
1386 
1387     @Test
1388     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readById_inForeground_hasWritePermAndReadVaccine_readsSelfDataAndVaccines()1389     public void readById_inForeground_hasWritePermAndReadVaccine_readsSelfDataAndVaccines() {
1390         MedicalDataSource dataSource1 =
1391                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1392         MedicalDataSource dataSource2 =
1393                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1394         MedicalResource vaccineDatasource1 =
1395                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1396         MedicalResource allergyDatasource1 =
1397                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
1398         MedicalResource vaccineDatasource2 =
1399                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
1400         MedicalResource allergyDatasource2 =
1401                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1402         List<MedicalResourceId> ids =
1403                 List.of(
1404                         vaccineDatasource1.getId(),
1405                         vaccineDatasource2.getId(),
1406                         allergyDatasource1.getId(),
1407                         allergyDatasource2.getId());
1408 
1409         List<MedicalResource> result =
1410                 mMedicalResourceHelper.readMedicalResourcesByIdsWithPermissionChecks(
1411                         ids,
1412                         Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1413                         DATA_SOURCE_PACKAGE_NAME,
1414                         /* hasWritePermission= */ true,
1415                         /* isCalledFromBgWithoutBgRead= */ false);
1416 
1417         assertThat(result)
1418                 .containsExactly(vaccineDatasource1, vaccineDatasource2, allergyDatasource1);
1419     }
1420 
1421     @Test
1422     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readByRequest_isNotEnforceSelfRead_createsAccessLog()1423     public void readByRequest_isNotEnforceSelfRead_createsAccessLog() {
1424         ReadMedicalResourcesInitialRequest readRequest =
1425                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
1426                         .build();
1427         mMedicalResourceHelper.readMedicalResourcesByRequestWithPermissionChecks(
1428                 PhrPageTokenWrapper.from(readRequest.toParcel()),
1429                 readRequest.getPageSize(),
1430                 DATA_SOURCE_PACKAGE_NAME,
1431                 /* enforceSelfRead= */ false);
1432 
1433         AccessLog expected =
1434                 new AccessLog(
1435                         DATA_SOURCE_PACKAGE_NAME,
1436                         INSTANT_NOW.toEpochMilli(),
1437                         OPERATION_TYPE_READ,
1438                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1439                         /* isMedicalDataSourceAccessed= */ false);
1440 
1441         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
1442                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
1443                 .containsExactly(expected);
1444     }
1445 
1446     @Test
1447     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readByRequest_isEnforceSelfRead_doesNotCreateAccessLog()1448     public void readByRequest_isEnforceSelfRead_doesNotCreateAccessLog() {
1449         ReadMedicalResourcesInitialRequest readRequest =
1450                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
1451                         .build();
1452         mMedicalResourceHelper.readMedicalResourcesByRequestWithPermissionChecks(
1453                 PhrPageTokenWrapper.from(readRequest.toParcel()),
1454                 readRequest.getPageSize(),
1455                 DATA_SOURCE_PACKAGE_NAME,
1456                 /* enforceSelfRead= */ true);
1457 
1458         List<AccessLog> accessLogs = mAccessLogsHelper.queryAccessLogs(mUserHandle);
1459 
1460         assertThat(accessLogs).isEmpty();
1461     }
1462 
1463     @Test
1464     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readByRequest_isNotEnforceSelfRead_vaccineFilter_canReadAllVaccines()1465     public void readByRequest_isNotEnforceSelfRead_vaccineFilter_canReadAllVaccines() {
1466         MedicalDataSource dataSource1 =
1467                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1468         MedicalDataSource dataSource2 =
1469                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1470         // In total inserts 8 resources, among which 6 are vaccines to be read in 3 pages.
1471         List<MedicalResource> vaccinesDataSource1 =
1472                 mUtil.upsertResources(
1473                         PhrDataFactory::createVaccineMedicalResources,
1474                         /* numOfResources= */ 4,
1475                         dataSource1);
1476         List<MedicalResource> vaccinesDataSource2 =
1477                 mUtil.upsertResources(
1478                         PhrDataFactory::createVaccineMedicalResources,
1479                         /* numOfResources= */ 2,
1480                         dataSource2);
1481         List<MedicalResource> allergyDatasource1 =
1482                 mUtil.upsertResources(
1483                         PhrDataFactory::createAllergyMedicalResources,
1484                         /* numOfResources= */ 2,
1485                         dataSource1);
1486         List<MedicalResource> resources =
1487                 joinLists(vaccinesDataSource1, vaccinesDataSource2, allergyDatasource1);
1488 
1489         ReadMedicalResourcesInitialRequest initialRequest =
1490                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
1491                         .setPageSize(2)
1492                         .build();
1493         PhrPageTokenWrapper initialPageTokenWrapper =
1494                 PhrPageTokenWrapper.from(initialRequest.toParcel());
1495         ReadMedicalResourcesInternalResponse initialResult =
1496                 mMedicalResourceHelper.readMedicalResourcesByRequestWithPermissionChecks(
1497                         initialPageTokenWrapper,
1498                         initialRequest.getPageSize(),
1499                         DATA_SOURCE_PACKAGE_NAME,
1500                         /* enforceSelfRead= */ false);
1501         String pageToken1 = initialResult.getPageToken();
1502         assertThat(initialResult.getMedicalResources())
1503                 .containsExactlyElementsIn(List.of(resources.get(0), resources.get(1)));
1504         assertThat(pageToken1).isNotEmpty();
1505         assertThat(pageToken1)
1506                 .isEqualTo(
1507                         initialPageTokenWrapper.cloneWithNewLastRowId(/* lastRowId= */ 2).encode());
1508         assertThat(initialResult.getRemainingCount()).isEqualTo(4);
1509 
1510         ReadMedicalResourcesPageRequest pageRequest1 =
1511                 new ReadMedicalResourcesPageRequest.Builder(pageToken1).setPageSize(2).build();
1512         PhrPageTokenWrapper pageTokenWrapper1 = PhrPageTokenWrapper.from(pageRequest1.toParcel());
1513         ReadMedicalResourcesInternalResponse pageResult =
1514                 mMedicalResourceHelper.readMedicalResourcesByRequestWithPermissionChecks(
1515                         pageTokenWrapper1,
1516                         pageRequest1.getPageSize(),
1517                         DATA_SOURCE_PACKAGE_NAME,
1518                         /* enforceSelfRead= */ false);
1519         String pageToken2 = pageResult.getPageToken();
1520         assertThat(pageResult.getMedicalResources())
1521                 .containsExactlyElementsIn(List.of(resources.get(2), resources.get(3)));
1522         assertThat(pageToken2).isNotEmpty();
1523         assertThat(pageToken2)
1524                 .isEqualTo(pageTokenWrapper1.cloneWithNewLastRowId(/* lastRowId= */ 4).encode());
1525         assertThat(pageResult.getRemainingCount()).isEqualTo(2);
1526 
1527         ReadMedicalResourcesPageRequest pageRequest2 =
1528                 new ReadMedicalResourcesPageRequest.Builder(pageToken2).setPageSize(2).build();
1529         PhrPageTokenWrapper pageTokenWrapper2 = PhrPageTokenWrapper.from(pageRequest2.toParcel());
1530         ReadMedicalResourcesInternalResponse pageResult2 =
1531                 mMedicalResourceHelper.readMedicalResourcesByRequestWithPermissionChecks(
1532                         pageTokenWrapper2,
1533                         pageRequest2.getPageSize(),
1534                         DATA_SOURCE_PACKAGE_NAME,
1535                         /* enforceSelfRead= */ false);
1536         String pageToken3 = pageResult2.getPageToken();
1537         assertThat(pageResult2.getMedicalResources())
1538                 .containsExactlyElementsIn(List.of(resources.get(4), resources.get(5)));
1539         assertThat(pageToken3).isNull();
1540         assertThat(pageResult2.getRemainingCount()).isEqualTo(0);
1541     }
1542 
1543     @Test
1544     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
readByRequest_enforceSelfRead_vaccineFilter_canReadOnlySelfVaccines()1545     public void readByRequest_enforceSelfRead_vaccineFilter_canReadOnlySelfVaccines() {
1546         MedicalDataSource dataSource1 =
1547                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1548         MedicalDataSource dataSource2 =
1549                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1550         // In total inserts 8 resources, among which 4 are vaccines from data source 1 to be
1551         // read in 2 pages.
1552         List<MedicalResource> vaccinesDataSource1 =
1553                 mUtil.upsertResources(
1554                         PhrDataFactory::createVaccineMedicalResources,
1555                         /* numOfResources= */ 4,
1556                         dataSource1);
1557         List<MedicalResource> vaccinesDataSource2 =
1558                 mUtil.upsertResources(
1559                         PhrDataFactory::createVaccineMedicalResources,
1560                         /* numOfResources= */ 2,
1561                         dataSource2);
1562         List<MedicalResource> allergyDatasource1 =
1563                 mUtil.upsertResources(
1564                         PhrDataFactory::createAllergyMedicalResources,
1565                         /* numOfResources= */ 2,
1566                         dataSource1);
1567         List<MedicalResource> resources =
1568                 joinLists(vaccinesDataSource1, vaccinesDataSource2, allergyDatasource1);
1569 
1570         ReadMedicalResourcesInitialRequest initialRequest =
1571                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
1572                         .setPageSize(2)
1573                         .build();
1574         PhrPageTokenWrapper initialPageTokenWrapper =
1575                 PhrPageTokenWrapper.from(initialRequest.toParcel());
1576         ReadMedicalResourcesInternalResponse initialResult =
1577                 mMedicalResourceHelper.readMedicalResourcesByRequestWithPermissionChecks(
1578                         initialPageTokenWrapper,
1579                         initialRequest.getPageSize(),
1580                         DATA_SOURCE_PACKAGE_NAME,
1581                         /* enforceSelfRead= */ true);
1582         String pageToken1 = initialResult.getPageToken();
1583         assertThat(initialResult.getMedicalResources())
1584                 .containsExactlyElementsIn(List.of(resources.get(0), resources.get(1)));
1585         assertThat(pageToken1).isNotEmpty();
1586         assertThat(pageToken1)
1587                 .isEqualTo(
1588                         initialPageTokenWrapper.cloneWithNewLastRowId(/* lastRowId= */ 2).encode());
1589         assertThat(initialResult.getRemainingCount()).isEqualTo(2);
1590 
1591         ReadMedicalResourcesPageRequest pageRequest =
1592                 new ReadMedicalResourcesPageRequest.Builder(pageToken1).setPageSize(2).build();
1593         PhrPageTokenWrapper pageTokenWrapper = PhrPageTokenWrapper.from(pageRequest.toParcel());
1594         ReadMedicalResourcesInternalResponse pageResult =
1595                 mMedicalResourceHelper.readMedicalResourcesByRequestWithPermissionChecks(
1596                         pageTokenWrapper,
1597                         pageRequest.getPageSize(),
1598                         DATA_SOURCE_PACKAGE_NAME,
1599                         /* enforceSelfRead= */ true);
1600         String pageToken2 = pageResult.getPageToken();
1601         assertThat(pageResult.getMedicalResources())
1602                 .containsExactlyElementsIn(List.of(resources.get(2), resources.get(3)));
1603         assertThat(pageToken2).isNull();
1604         assertThat(pageResult.getRemainingCount()).isEqualTo(0);
1605     }
1606 
1607     @Test
1608     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
insertMultipleMedicalResourcesWithSameDataSource_readMultipleResources()1609     public void insertMultipleMedicalResourcesWithSameDataSource_readMultipleResources() {
1610         // TODO(b/351992434): Create test utilities to make these large repeated code blocks
1611         // clearer.
1612         MedicalDataSource dataSource =
1613                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1614         MedicalResource resource1 = createVaccineMedicalResource(dataSource.getId());
1615         MedicalResource resource2 = createAllergyMedicalResource(dataSource.getId());
1616 
1617         List<MedicalResource> upsertedMedicalResources =
1618                 mMedicalResourceHelper.upsertMedicalResources(
1619                         DATA_SOURCE_PACKAGE_NAME,
1620                         List.of(makeUpsertRequest(resource1), makeUpsertRequest(resource2)));
1621         List<MedicalResource> result =
1622                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
1623                         List.of(resource1.getId(), resource2.getId()));
1624         List<Integer> indicesResult = readEntriesInMedicalResourceIndicesTable();
1625 
1626         assertThat(result).containsExactly(resource1, resource2);
1627         assertThat(upsertedMedicalResources).containsExactly(resource1, resource2);
1628         assertThat(indicesResult)
1629                 .containsExactly(
1630                         MEDICAL_RESOURCE_TYPE_VACCINES,
1631                         MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES);
1632     }
1633 
1634     @Test
1635     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
insertMedicalResourcesOfSameType_createsAccessLog_success()1636     public void insertMedicalResourcesOfSameType_createsAccessLog_success() {
1637         MedicalDataSource dataSource =
1638                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1639         mUtil.upsertResources(
1640                 PhrDataFactory::createVaccineMedicalResources, /* numOfResources= */ 6, dataSource);
1641 
1642         AccessLog expected =
1643                 new AccessLog(
1644                         DATA_SOURCE_PACKAGE_NAME,
1645                         INSTANT_NOW.toEpochMilli(),
1646                         OPERATION_TYPE_UPSERT,
1647                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1648                         /* isMedicalDataSourceAccessed= */ false);
1649 
1650         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
1651                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
1652                 .contains(expected);
1653     }
1654 
1655     @Test
1656     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
insertMedicalResourcesOfDifferentTypes_createsAccessLog_success()1657     public void insertMedicalResourcesOfDifferentTypes_createsAccessLog_success() {
1658         String dataSource = mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME).getId();
1659         MedicalResource vaccine = createVaccineMedicalResource(dataSource);
1660         MedicalResource allergy = createAllergyMedicalResource(dataSource);
1661         mMedicalResourceHelper.upsertMedicalResources(
1662                 DATA_SOURCE_PACKAGE_NAME,
1663                 createUpsertMedicalResourceRequests(List.of(vaccine, allergy), dataSource));
1664 
1665         AccessLog expected =
1666                 new AccessLog(
1667                         DATA_SOURCE_PACKAGE_NAME,
1668                         INSTANT_NOW.toEpochMilli(),
1669                         OPERATION_TYPE_UPSERT,
1670                         /* medicalResourceTypes= */ Set.of(
1671                                 MEDICAL_RESOURCE_TYPE_VACCINES,
1672                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES),
1673                         /* isMedicalDataSourceAccessed= */ false);
1674 
1675         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
1676                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
1677                 .contains(expected);
1678     }
1679 
1680     @Test
1681     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
insertAndUpdateMedicalResources_createsAccessLog_success()1682     public void insertAndUpdateMedicalResources_createsAccessLog_success() throws JSONException {
1683         String dataSource = mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME).getId();
1684         MedicalResource vaccine = createVaccineMedicalResource(dataSource);
1685         MedicalResource allergy = createAllergyMedicalResource(dataSource);
1686         MedicalResource updatedVaccine = createUpdatedVaccineMedicalResource(dataSource);
1687         // initial insert
1688         mMedicalResourceHelper.upsertMedicalResources(
1689                 DATA_SOURCE_PACKAGE_NAME,
1690                 createUpsertMedicalResourceRequests(List.of(vaccine, allergy), dataSource));
1691         // update the vaccine resource
1692         mMedicalResourceHelper.upsertMedicalResources(
1693                 DATA_SOURCE_PACKAGE_NAME,
1694                 createUpsertMedicalResourceRequests(List.of(updatedVaccine), dataSource));
1695 
1696         AccessLog insertAccessLog =
1697                 new AccessLog(
1698                         DATA_SOURCE_PACKAGE_NAME,
1699                         INSTANT_NOW.toEpochMilli(),
1700                         OPERATION_TYPE_UPSERT,
1701                         /* medicalResourceTypes= */ Set.of(
1702                                 MEDICAL_RESOURCE_TYPE_VACCINES,
1703                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES),
1704                         /* isMedicalDataSourceAccessed= */ false);
1705         AccessLog updateAccessLog =
1706                 new AccessLog(
1707                         DATA_SOURCE_PACKAGE_NAME,
1708                         INSTANT_NOW.toEpochMilli(),
1709                         OPERATION_TYPE_UPSERT,
1710                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1711                         /* isMedicalDataSourceAccessed= */ false);
1712 
1713         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
1714                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
1715                 .containsAtLeast(insertAccessLog, updateAccessLog);
1716     }
1717 
1718     @Test
1719     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD, Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE})
insertMultipleMedicalResourcesWithDifferentDataSources_readMultipleResources()1720     public void insertMultipleMedicalResourcesWithDifferentDataSources_readMultipleResources() {
1721         MedicalDataSource dataSource1 =
1722                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
1723         MedicalDataSource dataSource2 =
1724                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
1725         MedicalResource resource1 = createVaccineMedicalResource(dataSource1.getId());
1726         MedicalResource resource2 = createDifferentVaccineMedicalResource(dataSource2.getId());
1727 
1728         List<MedicalResource> upsertedMedicalResources =
1729                 mMedicalResourceHelper.upsertMedicalResources(
1730                         DATA_SOURCE_PACKAGE_NAME,
1731                         List.of(makeUpsertRequest(resource1), makeUpsertRequest(resource2)));
1732         List<MedicalResource> result =
1733                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
1734                         List.of(resource1.getId(), resource2.getId()));
1735         List<Integer> indicesResult = readEntriesInMedicalResourceIndicesTable();
1736 
1737         assertThat(result).containsExactly(resource1, resource2);
1738         assertThat(upsertedMedicalResources).containsExactly(resource1, resource2);
1739         assertThat(indicesResult)
1740                 .containsExactly(MEDICAL_RESOURCE_TYPE_VACCINES, MEDICAL_RESOURCE_TYPE_VACCINES);
1741     }
1742 
1743     @Test
1744     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
updateSingleMedicalResource_success()1745     public void updateSingleMedicalResource_success() throws JSONException {
1746         MedicalDataSource dataSource =
1747                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1748         MedicalResource originalResource = createVaccineMedicalResource(dataSource.getId());
1749         MedicalResource updatedResource = createUpdatedVaccineMedicalResource(dataSource.getId());
1750 
1751         mMedicalResourceHelper.upsertMedicalResources(
1752                 DATA_SOURCE_PACKAGE_NAME, List.of(makeUpsertRequest(originalResource)));
1753         List<MedicalResource> updatedMedicalResource =
1754                 mMedicalResourceHelper.upsertMedicalResources(
1755                         DATA_SOURCE_PACKAGE_NAME, List.of(makeUpsertRequest(updatedResource)));
1756         List<MedicalResource> result =
1757                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
1758                         List.of(originalResource.getId()));
1759         List<Integer> indicesResult = readEntriesInMedicalResourceIndicesTable();
1760 
1761         assertThat(result).containsExactly(updatedResource);
1762         assertThat(result).isEqualTo(updatedMedicalResource);
1763         assertThat(indicesResult).containsExactly(MEDICAL_RESOURCE_TYPE_VACCINES);
1764     }
1765 
1766     @Test
1767     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
insertMultipleMedicalResources_updateSingleMedicalResource_success()1768     public void insertMultipleMedicalResources_updateSingleMedicalResource_success()
1769             throws JSONException {
1770         MedicalDataSource dataSource =
1771                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1772         MedicalResource originalResource1 = createVaccineMedicalResource(dataSource.getId());
1773         MedicalResource updatedResource1 = createUpdatedVaccineMedicalResource(dataSource.getId());
1774         MedicalResource resource2 = createAllergyMedicalResource(dataSource.getId());
1775 
1776         List<MedicalResource> upsertedMedicalResources =
1777                 mMedicalResourceHelper.upsertMedicalResources(
1778                         DATA_SOURCE_PACKAGE_NAME,
1779                         List.of(
1780                                 makeUpsertRequest(originalResource1),
1781                                 makeUpsertRequest(resource2)));
1782         List<MedicalResource> updatedMedicalResource =
1783                 mMedicalResourceHelper.upsertMedicalResources(
1784                         DATA_SOURCE_PACKAGE_NAME, List.of(makeUpsertRequest(updatedResource1)));
1785         List<MedicalResource> readResults =
1786                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
1787                         List.of(updatedResource1.getId(), resource2.getId()));
1788         List<Integer> indicesResult = readEntriesInMedicalResourceIndicesTable();
1789 
1790         assertThat(readResults).containsExactly(updatedResource1, resource2);
1791         assertThat(upsertedMedicalResources).containsExactly(originalResource1, resource2);
1792         assertThat(updatedMedicalResource).containsExactly(updatedResource1);
1793         assertThat(indicesResult)
1794                 .containsExactly(
1795                         MEDICAL_RESOURCE_TYPE_VACCINES,
1796                         MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES);
1797     }
1798 
1799     @Test
1800     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
insertMultipleMedicalResources_updateMultipleMedicalResources_success()1801     public void insertMultipleMedicalResources_updateMultipleMedicalResources_success()
1802             throws JSONException {
1803         MedicalDataSource dataSource =
1804                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1805         MedicalResource resource1 = createVaccineMedicalResource(dataSource.getId());
1806         MedicalResource updatedResource1 = createUpdatedVaccineMedicalResource(dataSource.getId());
1807         MedicalResource resource2 = createAllergyMedicalResource(dataSource.getId());
1808         MedicalResource updatedResource2 = createUpdatedAllergyMedicalResource(dataSource.getId());
1809 
1810         List<MedicalResource> insertedMedicalResources =
1811                 mMedicalResourceHelper.upsertMedicalResources(
1812                         DATA_SOURCE_PACKAGE_NAME,
1813                         List.of(makeUpsertRequest(resource1), makeUpsertRequest(resource2)));
1814         List<MedicalResource> updatedMedicalResource =
1815                 mMedicalResourceHelper.upsertMedicalResources(
1816                         DATA_SOURCE_PACKAGE_NAME,
1817                         List.of(
1818                                 makeUpsertRequest(updatedResource1),
1819                                 makeUpsertRequest(updatedResource2)));
1820         List<MedicalResourceId> medicalIdFilters = List.of(resource1.getId(), resource2.getId());
1821         List<MedicalResource> result =
1822                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
1823                         medicalIdFilters);
1824         List<Integer> indicesResult = readEntriesInMedicalResourceIndicesTable();
1825 
1826         assertThat(insertedMedicalResources).containsExactly(resource1, resource2);
1827         assertThat(updatedMedicalResource)
1828                 .containsExactly(updatedResource1, updatedResource2)
1829                 .inOrder();
1830         assertThat(result).containsExactly(updatedResource1, updatedResource2);
1831         assertThat(indicesResult)
1832                 .containsExactly(
1833                         MEDICAL_RESOURCE_TYPE_VACCINES,
1834                         MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES);
1835     }
1836 
1837     @Test
1838     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
insertMultipleMedicalResources_readByRequest_success()1839     public void insertMultipleMedicalResources_readByRequest_success() {
1840         MedicalDataSource dataSource =
1841                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1842         List<MedicalResource> resources =
1843                 createVaccineMedicalResources(/* numOfResources= */ 6, dataSource.getId());
1844         List<UpsertMedicalResourceInternalRequest> upsertRequests =
1845                 createUpsertMedicalResourceRequests(resources, dataSource.getId());
1846 
1847         List<MedicalResource> upsertedMedicalResources =
1848                 mMedicalResourceHelper.upsertMedicalResources(
1849                         DATA_SOURCE_PACKAGE_NAME, upsertRequests);
1850         assertThat(upsertedMedicalResources).containsExactlyElementsIn(resources);
1851 
1852         ReadMedicalResourcesInitialRequest initialRequest =
1853                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
1854                         .setPageSize(2)
1855                         .build();
1856         PhrPageTokenWrapper pageTokenWrapper = PhrPageTokenWrapper.from(initialRequest.toParcel());
1857         ReadMedicalResourcesInternalResponse initialResult =
1858                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
1859                         pageTokenWrapper, initialRequest.getPageSize());
1860         String pageToken1 = initialResult.getPageToken();
1861         assertThat(initialResult.getMedicalResources())
1862                 .containsExactlyElementsIn(List.of(resources.get(0), resources.get(1)));
1863         assertThat(pageToken1).isNotEmpty();
1864         assertThat(pageToken1)
1865                 .isEqualTo(pageTokenWrapper.cloneWithNewLastRowId(/* lastRowId= */ 2).encode());
1866         assertThat(initialResult.getRemainingCount()).isEqualTo(4);
1867 
1868         ReadMedicalResourcesPageRequest pageRequest1 =
1869                 new ReadMedicalResourcesPageRequest.Builder(pageToken1).setPageSize(2).build();
1870         PhrPageTokenWrapper pageTokenWrapper1 = PhrPageTokenWrapper.from(pageRequest1.toParcel());
1871         ReadMedicalResourcesInternalResponse pageResult1 =
1872                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
1873                         pageTokenWrapper1, pageRequest1.getPageSize());
1874         String pageToken2 = pageResult1.getPageToken();
1875         assertThat(pageResult1.getMedicalResources())
1876                 .containsExactlyElementsIn(List.of(resources.get(2), resources.get(3)));
1877         assertThat(pageToken2).isNotEmpty();
1878         assertThat(pageToken2)
1879                 .isEqualTo(pageTokenWrapper1.cloneWithNewLastRowId(/* lastRowId= */ 4).encode());
1880         assertThat(pageResult1.getRemainingCount()).isEqualTo(2);
1881 
1882         ReadMedicalResourcesPageRequest pageRequest2 =
1883                 new ReadMedicalResourcesPageRequest.Builder(pageToken2).setPageSize(2).build();
1884         PhrPageTokenWrapper pageTokenWrapper2 = PhrPageTokenWrapper.from(pageRequest2.toParcel());
1885         ReadMedicalResourcesInternalResponse result2 =
1886                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
1887                         pageTokenWrapper2, pageRequest2.getPageSize());
1888         String pageToken3 = result2.getPageToken();
1889         assertThat(result2.getMedicalResources())
1890                 .containsExactlyElementsIn(List.of(resources.get(4), resources.get(5)));
1891         assertThat(pageToken3).isNull();
1892         assertThat(result2.getRemainingCount()).isEqualTo(0);
1893     }
1894 
1895     @Test
1896     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
insertMultipleMedicalResourcesReadByRequest_pageSizeLargerThanResources_success()1897     public void insertMultipleMedicalResourcesReadByRequest_pageSizeLargerThanResources_success() {
1898         MedicalDataSource dataSource =
1899                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1900         List<MedicalResource> resources =
1901                 createVaccineMedicalResources(/* numOfResources= */ 6, dataSource.getId());
1902         List<UpsertMedicalResourceInternalRequest> requests =
1903                 createUpsertMedicalResourceRequests(resources, dataSource.getId());
1904 
1905         List<MedicalResource> upsertedMedicalResources =
1906                 mMedicalResourceHelper.upsertMedicalResources(DATA_SOURCE_PACKAGE_NAME, requests);
1907         ReadMedicalResourcesInitialRequest readRequest =
1908                 new ReadMedicalResourcesInitialRequest.Builder(MEDICAL_RESOURCE_TYPE_VACCINES)
1909                         .setPageSize(10)
1910                         .build();
1911         ReadMedicalResourcesInternalResponse result =
1912                 mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
1913                         PhrPageTokenWrapper.from(readRequest.toParcel()),
1914                         readRequest.getPageSize());
1915 
1916         assertThat(upsertedMedicalResources).containsExactlyElementsIn(resources);
1917         assertThat(result.getMedicalResources()).containsExactlyElementsIn(resources);
1918         assertThat(result.getPageToken()).isEqualTo(null);
1919         assertThat(result.getRemainingCount()).isEqualTo(0);
1920     }
1921 
1922     @Test
1923     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
readMedicalResourcedByRequest_invalidPageToken_throws()1924     public void readMedicalResourcedByRequest_invalidPageToken_throws() {
1925         ReadMedicalResourcesPageRequest request =
1926                 new ReadMedicalResourcesPageRequest.Builder(INVALID_PAGE_TOKEN).build();
1927         assertThrows(
1928                 IllegalArgumentException.class,
1929                 () ->
1930                         mMedicalResourceHelper.readMedicalResourcesByRequestWithoutPermissionChecks(
1931                                 PhrPageTokenWrapper.from(request.toParcel()),
1932                                 request.getPageSize()));
1933     }
1934 
1935     @Test
1936     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByIds_appIdDoesNotExist_throws()1937     public void deleteMedicalResourcesByIds_appIdDoesNotExist_throws() {
1938         assertThrows(
1939                 IllegalArgumentException.class,
1940                 () ->
1941                         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithPermissionChecks(
1942                                 List.of(getMedicalResourceId()), "fake.package.com"));
1943     }
1944 
1945     @Test
1946     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByIds_withPackageName_noDataDeleted_noDeleteAccessLogs()1947     public void deleteMedicalResourcesByIds_withPackageName_noDataDeleted_noDeleteAccessLogs() {
1948         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithPermissionChecks(
1949                 List.of(getMedicalResourceId()), DATA_SOURCE_PACKAGE_NAME);
1950 
1951         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle)).isEmpty();
1952     }
1953 
1954     @Test
1955     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByIdsWithoutPermissionChecks_noDeleteAccessLogs()1956     public void deleteMedicalResourcesByIdsWithoutPermissionChecks_noDeleteAccessLogs() {
1957         MedicalDataSource dataSource1 =
1958                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1959         MedicalResource resource1 =
1960                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1961         // Clear access logs table, so that only the access logs from delete will be present
1962         mAccessLogsHelper.clearData(mTransactionManager);
1963 
1964         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithoutPermissionChecks(
1965                 List.of(resource1.getId()));
1966 
1967         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle)).isEmpty();
1968     }
1969 
1970     @Test
1971     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteByIdsWithPermissionChecks_resourcesWithDifferentPackages_correctAccessLogs()1972     public void deleteByIdsWithPermissionChecks_resourcesWithDifferentPackages_correctAccessLogs() {
1973         MedicalDataSource dataSource1 =
1974                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
1975         MedicalDataSource dataSource2 =
1976                 mUtil.insertR4MedicalDataSource("ds", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
1977         MedicalResource vaccinePackage1 =
1978                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
1979         MedicalResource unknownResourcePackage2 =
1980                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
1981         // Clear access logs table, so that only the access logs from delete will be present
1982         mAccessLogsHelper.clearData(mTransactionManager);
1983 
1984         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithPermissionChecks(
1985                 List.of(vaccinePackage1.getId(), unknownResourcePackage2.getId()),
1986                 /* packageName= */ DATA_SOURCE_PACKAGE_NAME);
1987 
1988         // In this test, we have inserted two different resource types from different packages.
1989         // When the calling app, calls the delete API, we expect access log to be created only
1990         // for the deleted resource type. In this case it would be: vaccinePackage1
1991         AccessLog deleteAccessLog =
1992                 new AccessLog(
1993                         DATA_SOURCE_PACKAGE_NAME,
1994                         INSTANT_NOW.toEpochMilli(),
1995                         OPERATION_TYPE_DELETE,
1996                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
1997                         /* isMedicalDataSourceAccessed= */ false);
1998         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
1999                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
2000                 .containsExactly(deleteAccessLog);
2001     }
2002 
2003     @Test
2004     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteByIds_withPackageName_resourcesWithSamePackages_correctAccessLogs()2005     public void deleteByIds_withPackageName_resourcesWithSamePackages_correctAccessLogs() {
2006         MedicalDataSource dataSource1 =
2007                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2008         MedicalDataSource dataSource2 =
2009                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2010         MedicalResource vaccinePackage1 =
2011                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2012         MedicalResource unknownResourcePackage1 =
2013                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
2014         // Clear access logs table, so that only the access logs from delete will be present
2015         mAccessLogsHelper.clearData(mTransactionManager);
2016 
2017         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithPermissionChecks(
2018                 List.of(vaccinePackage1.getId(), unknownResourcePackage1.getId()),
2019                 /* packageName= */ DATA_SOURCE_PACKAGE_NAME);
2020 
2021         // In this test, we have inserted two different resource types from the same package.
2022         // When the calling app, calls the delete API, we expect access log to be created
2023         // for the deleted resource types. In this case it would be: vaccinePackage1,
2024         // allergyResourcePackage1
2025         AccessLog deleteAccessLog =
2026                 new AccessLog(
2027                         DATA_SOURCE_PACKAGE_NAME,
2028                         INSTANT_NOW.toEpochMilli(),
2029                         OPERATION_TYPE_DELETE,
2030                         /* medicalResourceTypes= */ Set.of(
2031                                 MEDICAL_RESOURCE_TYPE_VACCINES,
2032                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES),
2033                         /* isMedicalDataSourceAccessed= */ false);
2034         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
2035                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
2036                 .containsExactly(deleteAccessLog);
2037     }
2038 
2039     @Test
2040     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByIds_noId_fails()2041     public void deleteMedicalResourcesByIds_noId_fails() {
2042         assertThrows(
2043                 IllegalArgumentException.class,
2044                 () ->
2045                         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithoutPermissionChecks(
2046                                 List.of()));
2047     }
2048 
2049     @Test
2050     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByIds_oneIdNotPresent_succeeds()2051     public void deleteMedicalResourcesByIds_oneIdNotPresent_succeeds() {
2052         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithoutPermissionChecks(
2053                 List.of(
2054                         new MedicalResourceId(
2055                                 DATA_SOURCE_ID,
2056                                 FHIR_RESOURCE_TYPE_IMMUNIZATION,
2057                                 FHIR_RESOURCE_ID_IMMUNIZATION)));
2058     }
2059 
2060     @Test
2061     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByIds_oneIdPresent_succeedsDeleting()2062     public void deleteMedicalResourcesByIds_oneIdPresent_succeedsDeleting() {
2063         MedicalDataSource dataSource =
2064                 mUtil.insertR4MedicalDataSource("ds", DATA_SOURCE_PACKAGE_NAME);
2065         MedicalResource medicalResource1 =
2066                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource);
2067 
2068         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithoutPermissionChecks(
2069                 List.of(medicalResource1.getId()));
2070 
2071         List<MedicalResource> result =
2072                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2073                         List.of(medicalResource1.getId()));
2074         List<Integer> indicesResult = readEntriesInMedicalResourceIndicesTable();
2075 
2076         assertThat(result).isEmpty();
2077         assertThat(indicesResult).isEmpty();
2078     }
2079 
2080     @Test
2081     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByIds_oneOfTwoSpecified_onlySpecifiedDeleted()2082     public void deleteMedicalResourcesByIds_oneOfTwoSpecified_onlySpecifiedDeleted() {
2083         MedicalDataSource dataSource =
2084                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2085         MedicalResource medicalResource1 =
2086                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource);
2087         MedicalResource medicalResource2 =
2088                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource);
2089 
2090         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithoutPermissionChecks(
2091                 List.of(medicalResource1.getId()));
2092         List<Integer> indicesResult = readEntriesInMedicalResourceIndicesTable();
2093 
2094         List<MedicalResource> result =
2095                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2096                         List.of(medicalResource1.getId(), medicalResource2.getId()));
2097         assertThat(result).containsExactly(medicalResource2);
2098         assertThat(indicesResult).containsExactly(MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES);
2099     }
2100 
2101     @Test
2102     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByIds_oneOfTwoSpecifiedWrongPackage_nothingDeleted()2103     public void deleteMedicalResourcesByIds_oneOfTwoSpecifiedWrongPackage_nothingDeleted() {
2104         MedicalDataSource dataSource =
2105                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2106         MedicalResource medicalResource1 =
2107                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource);
2108         MedicalResource medicalResource2 =
2109                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource);
2110 
2111         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithPermissionChecks(
2112                 List.of(medicalResource1.getId()), DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2113 
2114         List<MedicalResource> result =
2115                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2116                         List.of(medicalResource1.getId(), medicalResource2.getId()));
2117         assertThat(result).containsExactly(medicalResource1, medicalResource2);
2118     }
2119 
2120     @Test
2121     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByIds_oneOfTwoSpecifiedRightPackage_oneOfTwo()2122     public void deleteMedicalResourcesByIds_oneOfTwoSpecifiedRightPackage_oneOfTwo() {
2123         MedicalDataSource dataSource =
2124                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2125         MedicalResource medicalResource1 =
2126                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource);
2127         MedicalResource medicalResource2 =
2128                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource);
2129 
2130         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithPermissionChecks(
2131                 List.of(medicalResource1.getId()), DATA_SOURCE_PACKAGE_NAME);
2132 
2133         List<MedicalResource> result =
2134                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2135                         List.of(medicalResource1.getId(), medicalResource2.getId()));
2136         assertThat(result).containsExactly(medicalResource2);
2137     }
2138 
2139     @Test
2140     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByIds_multipleIdsFromDifferentPackages_succeeds()2141     public void deleteMedicalResourcesByIds_multipleIdsFromDifferentPackages_succeeds() {
2142         MedicalDataSource dataSource1 =
2143                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2144         MedicalDataSource dataSource2 =
2145                 mUtil.insertR4MedicalDataSource("ds2", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2146         MedicalResource expectedResource1Source1 =
2147                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2148         MedicalResource expectedResource1Source2 =
2149                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
2150         MedicalResource expectedResource2Source1 =
2151                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2152         MedicalResource expectedResource2Source2 =
2153                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
2154 
2155         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithoutPermissionChecks(
2156                 List.of(expectedResource1Source1.getId(), expectedResource2Source2.getId()));
2157 
2158         List<MedicalResource> result =
2159                 mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2160                         List.of(
2161                                 expectedResource1Source1.getId(),
2162                                 expectedResource1Source2.getId(),
2163                                 expectedResource2Source1.getId(),
2164                                 expectedResource2Source2.getId()));
2165         assertThat(result).containsExactly(expectedResource1Source2, expectedResource2Source1);
2166     }
2167 
2168     @Test
2169     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByRequestWithPermChecks_appHasNotInsertedData_throws()2170     public void deleteMedicalResourcesByRequestWithPermChecks_appHasNotInsertedData_throws() {
2171         DeleteMedicalResourcesRequest request =
2172                 new DeleteMedicalResourcesRequest.Builder().addDataSourceId(DATA_SOURCE_ID).build();
2173 
2174         assertThrows(
2175                 IllegalArgumentException.class,
2176                 () ->
2177                         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2178                                 request, "fake.package.com"));
2179     }
2180 
2181     @Test
2182     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByRequestWithPermChecks_singleDataSource_succeeds()2183     public void deleteMedicalResourcesByRequestWithPermChecks_singleDataSource_succeeds() {
2184         // Create two datasources, with one resource each.
2185         MedicalDataSource dataSource1 =
2186                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2187         MedicalDataSource dataSource2 =
2188                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2189         MedicalResource dataSource1Resource1 =
2190                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2191         MedicalResource dataSource2Resource1 =
2192                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
2193         MedicalResource dataSource1Resource2 =
2194                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2195 
2196         // Delete all of the data for just the first datasource
2197         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2198                 new DeleteMedicalResourcesRequest.Builder()
2199                         .addDataSourceId(dataSource1.getId())
2200                         .build(),
2201                 DATA_SOURCE_PACKAGE_NAME);
2202 
2203         // Test that the data for data source 1 is gone, but 2 is still present
2204         assertThat(
2205                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2206                                 List.of(
2207                                         dataSource1Resource1.getId(),
2208                                         dataSource1Resource2.getId())))
2209                 .hasSize(0);
2210         assertThat(
2211                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2212                                 List.of(dataSource2Resource1.getId())))
2213                 .hasSize(1);
2214     }
2215 
2216     @Test
2217     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByRequestWithPermChecks_multipleDataSources_succeeds()2218     public void deleteMedicalResourcesByRequestWithPermChecks_multipleDataSources_succeeds() {
2219         // Create three datasources, with one resource each.
2220         MedicalDataSource dataSource1 =
2221                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2222         MedicalDataSource dataSource2 =
2223                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2224         MedicalDataSource dataSource3 =
2225                 mUtil.insertR4MedicalDataSource("ds3", DATA_SOURCE_PACKAGE_NAME);
2226         MedicalResource vaccineDS1 =
2227                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2228         MedicalResource vaccineDS2 =
2229                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
2230         MedicalResource vaccineDS3 =
2231                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource3);
2232 
2233         // Delete all of the data for the first and second datasource
2234         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2235                 new DeleteMedicalResourcesRequest.Builder()
2236                         .addDataSourceId(dataSource1.getId())
2237                         .addDataSourceId(dataSource2.getId())
2238                         .build(),
2239                 DATA_SOURCE_PACKAGE_NAME);
2240 
2241         // Test that the data for data source 1 and 2 is gone, but 3 is still present
2242         assertThat(
2243                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2244                                 List.of(vaccineDS1.getId(), vaccineDS2.getId())))
2245                 .hasSize(0);
2246         assertThat(
2247                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2248                                 List.of(vaccineDS3.getId())))
2249                 .hasSize(1);
2250     }
2251 
2252     @Test
2253     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByRequestWithPermChecks_singleType_succeeds()2254     public void deleteMedicalResourcesByRequestWithPermChecks_singleType_succeeds() {
2255         // Create two data sources, with one vaccine each and one extra allergy.
2256         MedicalDataSource dataSource1 =
2257                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2258         MedicalDataSource dataSource2 =
2259                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2260         MedicalResource vaccine1 =
2261                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2262         MedicalResource vaccine2 =
2263                 mUtil.upsertResource(
2264                         PhrDataFactory::createDifferentVaccineMedicalResource, dataSource2);
2265         MedicalResource allergy =
2266                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2267 
2268         // Delete all vaccines.
2269         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2270                 new DeleteMedicalResourcesRequest.Builder()
2271                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2272                         .build(),
2273                 DATA_SOURCE_PACKAGE_NAME);
2274 
2275         // Test that only the allergy remains
2276         assertThat(
2277                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2278                                 List.of(vaccine1.getId(), vaccine2.getId())))
2279                 .hasSize(0);
2280         assertThat(
2281                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2282                                 List.of(allergy.getId())))
2283                 .hasSize(1);
2284     }
2285 
2286     @Test
2287     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByRequestWithPermChecks_multipleTypes_succeeds()2288     public void deleteMedicalResourcesByRequestWithPermChecks_multipleTypes_succeeds() {
2289         // Create two data sources, with one allergy and one vaccine each.
2290         MedicalDataSource dataSource1 =
2291                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2292         MedicalDataSource dataSource2 =
2293                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2294         MedicalResource vaccineDS1 =
2295                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2296         MedicalResource vaccineDS2 =
2297                 mUtil.upsertResource(
2298                         PhrDataFactory::createDifferentVaccineMedicalResource, dataSource2);
2299         MedicalResource allergyDS1 =
2300                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2301         MedicalResource allergyDS2 =
2302                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
2303 
2304         // Delete all vaccines and allergies.
2305         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2306                 new DeleteMedicalResourcesRequest.Builder()
2307                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2308                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES)
2309                         .build(),
2310                 DATA_SOURCE_PACKAGE_NAME);
2311 
2312         // Test that nothing remains
2313         assertThat(
2314                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2315                                 List.of(
2316                                         vaccineDS1.getId(),
2317                                         vaccineDS2.getId(),
2318                                         allergyDS1.getId(),
2319                                         allergyDS2.getId())))
2320                 .isEmpty();
2321     }
2322 
2323     @Test
2324     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByRequestWithPermChecks_byTypeAndDataSource_succeeds()2325     public void deleteMedicalResourcesByRequestWithPermChecks_byTypeAndDataSource_succeeds() {
2326         // Create two data sources, with one allergy and one vaccine each.
2327         MedicalDataSource dataSource1 =
2328                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2329         MedicalDataSource dataSource2 =
2330                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2331         MedicalResource vaccineDS1 =
2332                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2333         MedicalResource vaccineDS2 =
2334                 mUtil.upsertResource(
2335                         PhrDataFactory::createDifferentVaccineMedicalResource, dataSource2);
2336         MedicalResource allergyDS1 =
2337                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2338         MedicalResource allergyDS2 =
2339                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
2340 
2341         // Delete data by vaccine and data source 1.
2342         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2343                 new DeleteMedicalResourcesRequest.Builder()
2344                         .addDataSourceId(dataSource1.getId())
2345                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2346                         .build(),
2347                 DATA_SOURCE_PACKAGE_NAME);
2348 
2349         // Test that only one vaccine was deleted.
2350         assertThat(
2351                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2352                                 List.of(
2353                                         vaccineDS2.getId(),
2354                                         allergyDS1.getId(),
2355                                         allergyDS2.getId())))
2356                 .hasSize(3);
2357         assertThat(
2358                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2359                                 List.of(vaccineDS1.getId())))
2360                 .hasSize(0);
2361     }
2362 
2363     @Test
2364     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByRequestWithPermChecks_byDataSourceNoData_succeeds()2365     public void deleteMedicalResourcesByRequestWithPermChecks_byDataSourceNoData_succeeds() {
2366         MedicalDataSource dataSource1 =
2367                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2368 
2369         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2370                 new DeleteMedicalResourcesRequest.Builder()
2371                         .addDataSourceId(dataSource1.getId())
2372                         .addDataSourceId(DIFFERENT_DATA_SOURCE_ID)
2373                         .build(),
2374                 DATA_SOURCE_PACKAGE_NAME);
2375     }
2376 
2377     @Test
2378     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByRequestWithPermChecks_byTypeNoData_succeeds()2379     public void deleteMedicalResourcesByRequestWithPermChecks_byTypeNoData_succeeds() {
2380         MedicalDataSource dataSource1 =
2381                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2382 
2383         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2384                 new DeleteMedicalResourcesRequest.Builder()
2385                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2386                         .build(),
2387                 DATA_SOURCE_PACKAGE_NAME);
2388     }
2389 
2390     @Test
2391     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
2392     public void
deleteMedicalResourcesByRequestWithPermChecks_byTypeDataBelongsToOtherApp_noDelete()2393             deleteMedicalResourcesByRequestWithPermChecks_byTypeDataBelongsToOtherApp_noDelete() {
2394         MedicalDataSource dataSource =
2395                 mUtil.insertR4MedicalDataSource("ds1", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2396         MedicalResource vaccine =
2397                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource);
2398 
2399         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2400                 new DeleteMedicalResourcesRequest.Builder()
2401                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2402                         .build(),
2403                 DATA_SOURCE_PACKAGE_NAME);
2404 
2405         // Test that nothing was deleted
2406         assertThat(
2407                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2408                                 List.of(vaccine.getId())))
2409                 .hasSize(1);
2410     }
2411 
2412     @Test
2413     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
2414     public void
deleteMedicalResourcesByRequestWithPermChecks_bySourceDataBelongsToOtherApp_noDelete()2415             deleteMedicalResourcesByRequestWithPermChecks_bySourceDataBelongsToOtherApp_noDelete() {
2416         MedicalDataSource dataSource =
2417                 mUtil.insertR4MedicalDataSource("ds1", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2418         MedicalResource vaccine =
2419                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource);
2420 
2421         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2422                 new DeleteMedicalResourcesRequest.Builder()
2423                         .addDataSourceId(dataSource.getId())
2424                         .build(),
2425                 DATA_SOURCE_PACKAGE_NAME);
2426 
2427         // Test that nothing was deleted
2428         assertThat(
2429                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2430                                 List.of(vaccine.getId())))
2431                 .hasSize(1);
2432     }
2433 
2434     @Test
2435     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
2436     public void
deleteMedicalResourcesByRequestWithPermChecks_bySourceAndTypeOtherPackage_noDelete()2437             deleteMedicalResourcesByRequestWithPermChecks_bySourceAndTypeOtherPackage_noDelete() {
2438         MedicalDataSource dataSource =
2439                 mUtil.insertR4MedicalDataSource("ds1", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2440         MedicalResource vaccine =
2441                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource);
2442 
2443         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2444                 new DeleteMedicalResourcesRequest.Builder()
2445                         .addDataSourceId(dataSource.getId())
2446                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2447                         .build(),
2448                 DATA_SOURCE_PACKAGE_NAME);
2449 
2450         // Test that nothing was deleted
2451         assertThat(
2452                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2453                                 List.of(vaccine.getId())))
2454                 .hasSize(1);
2455     }
2456 
2457     @Test
2458     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteByRequestWithPermChecks_bothSourcesFromCallingPackage_expectAccessLogs()2459     public void deleteByRequestWithPermChecks_bothSourcesFromCallingPackage_expectAccessLogs() {
2460         MedicalDataSource dataSource1 =
2461                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2462         MedicalDataSource dataSource2 =
2463                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2464         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2465         mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
2466         // Clear access logs table, so that only the access logs from delete will be present
2467         mAccessLogsHelper.clearData(mTransactionManager);
2468 
2469         // Both created dataSources are from the same calling app.
2470         // So when the calling app deletes medicalResources from both those dataSources,
2471         // resourceTypes for both should be included in the accessLogs.
2472         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2473                 new DeleteMedicalResourcesRequest.Builder()
2474                         .addDataSourceId(dataSource1.getId())
2475                         .addDataSourceId(dataSource2.getId())
2476                         .build(),
2477                 /* callingPackageName= */ DATA_SOURCE_PACKAGE_NAME);
2478 
2479         AccessLog deleteAccessLog =
2480                 new AccessLog(
2481                         DATA_SOURCE_PACKAGE_NAME,
2482                         INSTANT_NOW.toEpochMilli(),
2483                         OPERATION_TYPE_DELETE,
2484                         /* medicalResourceTypes= */ Set.of(
2485                                 MEDICAL_RESOURCE_TYPE_VACCINES,
2486                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES),
2487                         /* isMedicalDataSourceAccessed= */ false);
2488         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
2489                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
2490                 .containsExactly(deleteAccessLog);
2491     }
2492 
2493     @Test
2494     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteByRequestWithPermChecks_dataSourcesFromDifferentPackages_expectAccessLogs()2495     public void deleteByRequestWithPermChecks_dataSourcesFromDifferentPackages_expectAccessLogs() {
2496         MedicalDataSource dataSource1 =
2497                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2498         MedicalDataSource dataSource2 =
2499                 mUtil.insertR4MedicalDataSource("ds2", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2500         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2501         mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
2502         // Clear access logs table, so that only the access logs from delete will be present
2503         mAccessLogsHelper.clearData(mTransactionManager);
2504 
2505         // The created dataSources are from different calling apps.
2506         // When the first calling app tries to delete resources given both dataSources,
2507         // only the resources belonging to the dataSource of the calling app will
2508         // be deleted. So accessLogs are added only for the deleted resourceTypes which would
2509         // be resourceTypes belonging to dataSource1.
2510         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2511                 new DeleteMedicalResourcesRequest.Builder()
2512                         .addDataSourceId(dataSource1.getId())
2513                         .addDataSourceId(dataSource2.getId())
2514                         .build(),
2515                 /* callingPackageName= */ DATA_SOURCE_PACKAGE_NAME);
2516 
2517         AccessLog deleteAccessLog =
2518                 new AccessLog(
2519                         DATA_SOURCE_PACKAGE_NAME,
2520                         INSTANT_NOW.toEpochMilli(),
2521                         OPERATION_TYPE_DELETE,
2522                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
2523                         /* isMedicalDataSourceAccessed= */ false);
2524         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
2525                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
2526                 .containsExactly(deleteAccessLog);
2527     }
2528 
2529     @Test
2530     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
2531     public void
deleteByRequestWithPermChecks_resourceTypesFromDifferentPackages_expectAccessLogs()2532             deleteByRequestWithPermChecks_resourceTypesFromDifferentPackages_expectAccessLogs() {
2533         MedicalDataSource dataSource1 =
2534                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2535         MedicalDataSource dataSource2 =
2536                 mUtil.insertR4MedicalDataSource("ds2", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2537         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2538         mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
2539         // Clear access logs table, so that only the access logs from delete will be present
2540         mAccessLogsHelper.clearData(mTransactionManager);
2541 
2542         // The created resources are in data sources from different calling apps.
2543         // When the first calling app tries to delete resources given both resource types,
2544         // only the resources belonging to the calling app will be deleted. So accessLogs are added
2545         // only for the deleted resourceType.
2546         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2547                 new DeleteMedicalResourcesRequest.Builder()
2548                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2549                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES)
2550                         .build(),
2551                 /* callingPackageName= */ DATA_SOURCE_PACKAGE_NAME);
2552 
2553         AccessLog deleteAccessLog =
2554                 new AccessLog(
2555                         DATA_SOURCE_PACKAGE_NAME,
2556                         INSTANT_NOW.toEpochMilli(),
2557                         OPERATION_TYPE_DELETE,
2558                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
2559                         /* isMedicalDataSourceAccessed= */ false);
2560         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
2561                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
2562                 .containsExactly(deleteAccessLog);
2563     }
2564 
2565     @Test
2566     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteByRequestWithPermChecks_resourcesTypesFromSamePackage_logsForAccessedTypes()2567     public void deleteByRequestWithPermChecks_resourcesTypesFromSamePackage_logsForAccessedTypes() {
2568         MedicalDataSource dataSource1 =
2569                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2570         MedicalDataSource dataSource2 =
2571                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2572         mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2573         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2574         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
2575         // Clear access logs table, so that only the access logs from delete will be present
2576         mAccessLogsHelper.clearData(mTransactionManager);
2577 
2578         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2579                 new DeleteMedicalResourcesRequest.Builder()
2580                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES)
2581                         .build(),
2582                 /* callingPackageName= */ DATA_SOURCE_PACKAGE_NAME);
2583 
2584         AccessLog deleteAccessLog =
2585                 new AccessLog(
2586                         DATA_SOURCE_PACKAGE_NAME,
2587                         INSTANT_NOW.toEpochMilli(),
2588                         OPERATION_TYPE_DELETE,
2589                         /* medicalResourceTypes= */ Set.of(
2590                                 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES),
2591                         /* isMedicalDataSourceAccessed= */ false);
2592         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
2593                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
2594                 .containsExactly(deleteAccessLog);
2595     }
2596 
2597     @Test
2598     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteByRequestWithPermChecks_expectAccessLogsForAccessedTypesAndSources_noMatch()2599     public void deleteByRequestWithPermChecks_expectAccessLogsForAccessedTypesAndSources_noMatch() {
2600         MedicalDataSource dataSource1 =
2601                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2602         MedicalDataSource dataSource2 =
2603                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2604         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2605         mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource2);
2606         // Clear access logs table, so that only the access logs from delete will be present
2607         mAccessLogsHelper.clearData(mTransactionManager);
2608 
2609         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2610                 new DeleteMedicalResourcesRequest.Builder()
2611                         .addDataSourceId(dataSource2.getId())
2612                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2613                         .build(),
2614                 /* callingPackageName= */ DATA_SOURCE_PACKAGE_NAME);
2615 
2616         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle)).isEmpty();
2617     }
2618 
2619     @Test
2620     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteByRequestWithPermChecks_expectAccessLogsForAccessedTypesAndSources_logs()2621     public void deleteByRequestWithPermChecks_expectAccessLogsForAccessedTypesAndSources_logs() {
2622         MedicalDataSource dataSource1 =
2623                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2624         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2625         mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2626         // Clear access logs table, so that only the access logs from delete will be present
2627         mAccessLogsHelper.clearData(mTransactionManager);
2628 
2629         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2630                 new DeleteMedicalResourcesRequest.Builder()
2631                         .addDataSourceId(dataSource1.getId())
2632                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2633                         .build(),
2634                 /* callingPackageName= */ DATA_SOURCE_PACKAGE_NAME);
2635 
2636         AccessLog deleteAccessLog =
2637                 new AccessLog(
2638                         DATA_SOURCE_PACKAGE_NAME,
2639                         INSTANT_NOW.toEpochMilli(),
2640                         OPERATION_TYPE_DELETE,
2641                         /* medicalResourceTypes= */ Set.of(MEDICAL_RESOURCE_TYPE_VACCINES),
2642                         /* isMedicalDataSourceAccessed= */ false);
2643         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle))
2644                 .comparingElementsUsing(ACCESS_LOG_EQUIVALENCE)
2645                 .containsExactly(deleteAccessLog);
2646     }
2647 
2648     @Test
2649     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteByRequestWithPermChecks_noMatchingDataSources_noAccessLogsCreated()2650     public void deleteByRequestWithPermChecks_noMatchingDataSources_noAccessLogsCreated() {
2651         MedicalDataSource dataSource1 =
2652                 mUtil.insertR4MedicalDataSource("ds1", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2653         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2654         // Clear access logs table, so that only the access logs from delete will be present
2655         mAccessLogsHelper.clearData(mTransactionManager);
2656 
2657         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
2658                 new DeleteMedicalResourcesRequest.Builder()
2659                         .addDataSourceId(dataSource1.getId())
2660                         .build(),
2661                 /* callingPackageName= */ DATA_SOURCE_PACKAGE_NAME);
2662 
2663         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle)).isEmpty();
2664     }
2665 
2666     @Test
2667     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteByRequestWithoutPermChecks_withoutPackageRestriction_noAccessLogsCreated()2668     public void deleteByRequestWithoutPermChecks_withoutPackageRestriction_noAccessLogsCreated() {
2669         MedicalDataSource dataSource1 =
2670                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2671         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2672         // Clear access logs table, so that only the access logs from delete will be present
2673         mAccessLogsHelper.clearData(mTransactionManager);
2674 
2675         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithoutPermissionChecks(
2676                 new DeleteMedicalResourcesRequest.Builder()
2677                         .addDataSourceId(dataSource1.getId())
2678                         .build());
2679 
2680         assertThat(mAccessLogsHelper.queryAccessLogs(mUserHandle)).isEmpty();
2681     }
2682 
2683     @Test
2684     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByRequestWithoutPermsCheck_singleResourceType_succeeds()2685     public void deleteMedicalResourcesByRequestWithoutPermsCheck_singleResourceType_succeeds() {
2686         // Create two datasources, with one resource each.
2687         MedicalDataSource dataSource1 =
2688                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2689         MedicalDataSource dataSource2 =
2690                 mUtil.insertR4MedicalDataSource("ds2", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2691         MedicalResource vaccineDS1 =
2692                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2693         MedicalResource vaccineDS2 =
2694                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
2695         MedicalResource allergyResource =
2696                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2697 
2698         // Delete all of the data for just the first datasource
2699         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithoutPermissionChecks(
2700                 new DeleteMedicalResourcesRequest.Builder()
2701                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2702                         .build());
2703 
2704         // Test that the data for the vaccines are gone, but the allergy is still present
2705         assertThat(
2706                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2707                                 List.of(vaccineDS1.getId(), vaccineDS2.getId())))
2708                 .hasSize(0);
2709         assertThat(
2710                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2711                                 List.of(allergyResource.getId())))
2712                 .hasSize(1);
2713     }
2714 
2715     @Test
2716     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteMedicalResourcesByRequestWithoutPermsCheck_multipleSources_succeeds()2717     public void deleteMedicalResourcesByRequestWithoutPermsCheck_multipleSources_succeeds() {
2718         MedicalDataSource dataSource1 =
2719                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2720         MedicalDataSource dataSource2 =
2721                 mUtil.insertR4MedicalDataSource("ds2", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2722         MedicalDataSource dataSource3 =
2723                 mUtil.insertR4MedicalDataSource("ds3", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2724         MedicalResource vaccineDS1 =
2725                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2726         MedicalResource vaccineDS2 =
2727                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
2728         MedicalResource vaccineDS3 =
2729                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource3);
2730         MedicalResource allergyDS1 =
2731                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2732 
2733         // Delete data for data sources 1 and 2.
2734         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithoutPermissionChecks(
2735                 new DeleteMedicalResourcesRequest.Builder()
2736                         .addDataSourceId(dataSource1.getId())
2737                         .addDataSourceId(dataSource2.getId())
2738                         .build());
2739 
2740         // Test that only data for data source 3 remains.
2741         assertThat(
2742                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2743                                 List.of(
2744                                         vaccineDS1.getId(),
2745                                         vaccineDS2.getId(),
2746                                         allergyDS1.getId())))
2747                 .hasSize(0);
2748         assertThat(
2749                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2750                                 List.of(vaccineDS3.getId())))
2751                 .hasSize(1);
2752     }
2753 
2754     @Test
2755     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
deleteByRequestWithoutPermChecks_oneResourceTypeAndDataSource_succeeds()2756     public void deleteByRequestWithoutPermChecks_oneResourceTypeAndDataSource_succeeds() {
2757         // Create two data sources, with one resource each.
2758         MedicalDataSource dataSource1 =
2759                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2760         MedicalDataSource dataSource2 =
2761                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2762         MedicalResource vaccineDS1 =
2763                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2764         MedicalResource vaccineDS2 =
2765                 mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
2766         MedicalResource allergyDS1 =
2767                 mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2768 
2769         // Delete all of the data for just data source 1.
2770         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithoutPermissionChecks(
2771                 new DeleteMedicalResourcesRequest.Builder()
2772                         .addDataSourceId(dataSource1.getId())
2773                         .addMedicalResourceType(MEDICAL_RESOURCE_TYPE_VACCINES)
2774                         .build());
2775 
2776         // Test that the data for the vaccines are gone, but the allergy is still present.
2777         assertThat(
2778                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2779                                 List.of(vaccineDS1.getId())))
2780                 .hasSize(0);
2781         assertThat(
2782                         mMedicalResourceHelper.readMedicalResourcesByIdsWithoutPermissionChecks(
2783                                 List.of(allergyDS1.getId(), vaccineDS2.getId())))
2784                 .hasSize(2);
2785     }
2786 
2787     @Test
2788     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
testGetMedicalResourceTypeToContributingDataSourcesMap_success()2789     public void testGetMedicalResourceTypeToContributingDataSourcesMap_success() {
2790         // Create some data sources with data: ds1 contains [vaccine, differentVaccine,
2791         // allergy], ds2 contains [vaccine], and ds3 contains [allergy].
2792         MedicalDataSource dataSource1 =
2793                 mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2794         MedicalDataSource dataSource2 =
2795                 mUtil.insertR4MedicalDataSource("ds2", DATA_SOURCE_PACKAGE_NAME);
2796         MedicalDataSource dataSource3 =
2797                 mUtil.insertR4MedicalDataSource("ds3", DIFFERENT_DATA_SOURCE_PACKAGE_NAME);
2798         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource1);
2799         mUtil.upsertResource(PhrDataFactory::createDifferentVaccineMedicalResource, dataSource1);
2800         mUtil.upsertResource(PhrDataFactory::createVaccineMedicalResource, dataSource2);
2801         mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource1);
2802         mUtil.upsertResource(PhrDataFactory::createAllergyMedicalResource, dataSource3);
2803         Instant lastDataUpdateTime =
2804                 Instant.ofEpochMilli(mFakeTimeSource.getInstantNow().toEpochMilli());
2805         MedicalDataSource expectedDataSource1 =
2806                 new MedicalDataSource.Builder(dataSource1)
2807                         .setLastDataUpdateTime(lastDataUpdateTime)
2808                         .build();
2809         MedicalDataSource expectedDataSource2 =
2810                 new MedicalDataSource.Builder(dataSource2)
2811                         .setLastDataUpdateTime(lastDataUpdateTime)
2812                         .build();
2813         MedicalDataSource expectedDataSource3 =
2814                 new MedicalDataSource.Builder(dataSource3)
2815                         .setLastDataUpdateTime(lastDataUpdateTime)
2816                         .build();
2817 
2818         Map<Integer, Set<MedicalDataSource>> response =
2819                 mMedicalResourceHelper.getMedicalResourceTypeToContributingDataSourcesMap();
2820 
2821         assertThat(response).hasSize(2);
2822         assertThat(response.keySet())
2823                 .containsExactly(
2824                         MEDICAL_RESOURCE_TYPE_VACCINES,
2825                         MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES);
2826         assertThat(response.get(MEDICAL_RESOURCE_TYPE_VACCINES))
2827                 .containsExactly(expectedDataSource1, expectedDataSource2);
2828         assertThat(response.get(MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES))
2829                 .containsExactly(expectedDataSource1, expectedDataSource3);
2830     }
2831 
2832     @Test
2833     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
testGetMedicalResourceTypeToContributingDataSourcesMap_noDataSources_success()2834     public void testGetMedicalResourceTypeToContributingDataSourcesMap_noDataSources_success() {
2835         Map<Integer, Set<MedicalDataSource>> response =
2836                 mMedicalResourceHelper.getMedicalResourceTypeToContributingDataSourcesMap();
2837 
2838         assertThat(response).isEmpty();
2839     }
2840 
2841     @Test
2842     @EnableFlags({Flags.FLAG_PERSONAL_HEALTH_RECORD_DATABASE, Flags.FLAG_PERSONAL_HEALTH_RECORD})
2843     public void
testGetMedicalResourceTypeToContributingDataSourcesMap_noMedicalResources_success()2844             testGetMedicalResourceTypeToContributingDataSourcesMap_noMedicalResources_success() {
2845         mUtil.insertR4MedicalDataSource("ds1", DATA_SOURCE_PACKAGE_NAME);
2846 
2847         Map<Integer, Set<MedicalDataSource>> response =
2848                 mMedicalResourceHelper.getMedicalResourceTypeToContributingDataSourcesMap();
2849 
2850         assertThat(response).isEmpty();
2851     }
2852 
2853     /**
2854      * Returns a UUID for the given triple {@code resourceId}, {@code resourceType} and {@code
2855      * dataSourceId}.
2856      */
makeMedicalResourceHexString(MedicalResourceId medicalResourceId)2857     private static String makeMedicalResourceHexString(MedicalResourceId medicalResourceId) {
2858         return getHexString(
2859                 generateMedicalResourceUUID(
2860                         medicalResourceId.getFhirResourceId(),
2861                         medicalResourceId.getFhirResourceType(),
2862                         medicalResourceId.getDataSourceId()));
2863     }
2864 
readEntriesInMedicalResourceIndicesTable()2865     private List<Integer> readEntriesInMedicalResourceIndicesTable() {
2866         List<Integer> medicalResourceTypes = new ArrayList<>();
2867         ReadTableRequest readTableRequest = new ReadTableRequest(getTableName());
2868         try (Cursor cursor = mTransactionManager.read(readTableRequest)) {
2869             if (cursor.moveToFirst()) {
2870                 do {
2871                     medicalResourceTypes.add(
2872                             getCursorInt(cursor, getMedicalResourceTypeColumnName()));
2873                 } while (cursor.moveToNext());
2874             }
2875             return medicalResourceTypes;
2876         }
2877     }
2878 
getMedicalResourcesTableRowCount()2879     private int getMedicalResourcesTableRowCount() {
2880         ReadTableRequest readTableRequest =
2881                 new ReadTableRequest(MedicalResourceHelper.getMainTableName());
2882         try (Cursor cursor = mTransactionManager.read(readTableRequest)) {
2883             return cursor.getCount();
2884         }
2885     }
2886 
2887     /**
2888      * Creates a list of {@link UpsertMedicalResourceInternalRequest}s for the given list of {@link
2889      * MedicalResource}s and {@code dataSourceId}.
2890      */
createUpsertMedicalResourceRequests( List<MedicalResource> medicalResources, String dataSourceId)2891     private static List<UpsertMedicalResourceInternalRequest> createUpsertMedicalResourceRequests(
2892             List<MedicalResource> medicalResources, String dataSourceId) {
2893         List<UpsertMedicalResourceInternalRequest> requests = new ArrayList<>();
2894         for (MedicalResource medicalResource : medicalResources) {
2895             FhirResource fhirResource = medicalResource.getFhirResource();
2896             UpsertMedicalResourceInternalRequest request =
2897                     new UpsertMedicalResourceInternalRequest()
2898                             .setMedicalResourceType(medicalResource.getType())
2899                             .setFhirResourceId(fhirResource.getId())
2900                             .setFhirResourceType(fhirResource.getType())
2901                             .setFhirVersion(medicalResource.getFhirVersion())
2902                             .setData(fhirResource.getData())
2903                             .setDataSourceId(dataSourceId);
2904             requests.add(request);
2905         }
2906         return requests;
2907     }
2908 
2909     /**
2910      * Returns the list of {@link AccessLog}s sorted based on the {@link AccessLog#getAccessTime()}
2911      * in an ascending order.
2912      */
sortByAccessTime(List<AccessLog> accessLogs)2913     private static List<AccessLog> sortByAccessTime(List<AccessLog> accessLogs) {
2914         return accessLogs.stream()
2915                 .sorted(Comparator.comparing(AccessLog::getAccessTime))
2916                 .collect(Collectors.toList());
2917     }
2918 
joinLists(List<T>.... lists)2919     private static <T> List<T> joinLists(List<T>... lists) {
2920         return Stream.of(lists).flatMap(Collection::stream).collect(Collectors.toList());
2921     }
2922 }
2923