1 /* 2 * Copyright (C) 2024 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 android.healthconnect.cts.lib; 18 19 import static android.health.connect.datatypes.Device.DEVICE_TYPE_RING; 20 import static android.health.connect.datatypes.Device.DEVICE_TYPE_WATCH; 21 import static android.health.connect.datatypes.Metadata.RECORDING_METHOD_AUTOMATICALLY_RECORDED; 22 import static android.health.connect.datatypes.Metadata.RECORDING_METHOD_MANUAL_ENTRY; 23 24 import android.health.connect.datatypes.ActivityIntensityRecord; 25 import android.health.connect.datatypes.DataOrigin; 26 import android.health.connect.datatypes.Device; 27 import android.health.connect.datatypes.Metadata; 28 import android.health.connect.datatypes.MindfulnessSessionRecord; 29 import android.health.connect.datatypes.Record; 30 import android.os.Bundle; 31 32 import androidx.annotation.Nullable; 33 34 import java.time.Instant; 35 import java.time.LocalDate; 36 import java.time.LocalDateTime; 37 import java.time.LocalTime; 38 import java.time.ZoneId; 39 import java.time.ZoneOffset; 40 import java.time.ZonedDateTime; 41 import java.time.temporal.ChronoUnit; 42 43 public abstract class RecordFactory<T extends Record> { 44 45 public static final ZonedDateTime YESTERDAY_11AM = 46 LocalDate.now(ZoneId.systemDefault()) 47 .minusDays(1) 48 .atTime(11, 0) 49 .atZone(ZoneId.systemDefault()); 50 51 public static final ZonedDateTime MIDNIGHT_ONE_WEEK_AGO = 52 YESTERDAY_11AM.truncatedTo(ChronoUnit.DAYS).minusDays(7); 53 54 public static final LocalDateTime YESTERDAY_10AM_LOCAL = 55 LocalDate.now(ZoneId.systemDefault()).minusDays(1).atTime(LocalTime.parse("10:00")); 56 57 /** 58 * Returns a record with all possible fields of this data type set to non-default values. 59 * 60 * <p>Used to make sure all fields get successfully written and returned from Health Connect end 61 * to end. 62 */ newFullRecord(Metadata metadata, Instant startTime, Instant endTime)63 public abstract T newFullRecord(Metadata metadata, Instant startTime, Instant endTime); 64 65 /** 66 * Returns a record with all possible fields of this data type set to non-default values. 67 * 68 * <p>This is similar to {@link #newFullRecord(Metadata, Instant, Instant)} however every field 69 * should have a different value. This is used to test that all fields successfully get updated 70 * on the server side. 71 */ anotherFullRecord(Metadata metadata, Instant startTime, Instant endTime)72 public abstract T anotherFullRecord(Metadata metadata, Instant startTime, Instant endTime); 73 74 /** 75 * Returns a record with all optional fields of this data type unset or set to default values. 76 * 77 * <p>Used to make sure null/default values are handled correctly by the server side. 78 */ newEmptyRecord(Metadata metadata, Instant startTime, Instant endTime)79 public abstract T newEmptyRecord(Metadata metadata, Instant startTime, Instant endTime); 80 81 /** Returns a copy of the given record with the metadata set to the provided one. */ recordWithMetadata(T record, Metadata metadata)82 protected abstract T recordWithMetadata(T record, Metadata metadata); 83 84 /** 85 * Returns a bundle containing the values specific to this data type. 86 * 87 * <p>The returned bundle is then used by {@link #newRecordFromValuesBundle} to recreate the 88 * record. 89 * 90 * <p>Metadata, start & end time, start & end zone offset should not be populated as they are 91 * handled separately. 92 * 93 * <p>Used by multi-app tests to send the record across the test apps. 94 */ getValuesBundleForRecord(T record)95 protected abstract Bundle getValuesBundleForRecord(T record); 96 97 /** Returns a bundle containing the values specific to this data type. */ getValuesBundle(Record record)98 public final Bundle getValuesBundle(Record record) { 99 return getValuesBundleForRecord((T) record); 100 } 101 102 /** Recreates the record from the given values bundle and metadata. */ newRecordFromValuesBundle( Metadata metadata, Instant startTime, Instant endTime, ZoneOffset startZoneOffset, ZoneOffset endZoneOffset, Bundle bundle)103 public abstract T newRecordFromValuesBundle( 104 Metadata metadata, 105 Instant startTime, 106 Instant endTime, 107 ZoneOffset startZoneOffset, 108 ZoneOffset endZoneOffset, 109 Bundle bundle); 110 111 /** Returns the record with id and package name overridden by the given ones. */ recordWithIdAndPackageName(Record record, String id, String packageName)112 public final T recordWithIdAndPackageName(Record record, String id, String packageName) { 113 return recordWithMetadata( 114 (T) record, 115 toBuilder(record.getMetadata()) 116 .setId(id) 117 .setDataOrigin(new DataOrigin.Builder().setPackageName(packageName).build()) 118 .build()); 119 } 120 121 /** Returns a default instance of metadata with no fields populated. */ newEmptyMetadata()122 public static Metadata newEmptyMetadata() { 123 return new Metadata.Builder().build(); 124 } 125 126 /** Returns a default instance of metadata with no fields populated except the id. */ newEmptyMetadataWithId(String id)127 public static Metadata newEmptyMetadataWithId(String id) { 128 return new Metadata.Builder().setId(id).build(); 129 } 130 131 /** Returns a default instance of metadata with no fields populated except the client id. */ newEmptyMetadataWithClientId(String clientId)132 public static Metadata newEmptyMetadataWithClientId(String clientId) { 133 return new Metadata.Builder().setClientRecordId(clientId).build(); 134 } 135 136 /** Returns a default instance of metadata with no fields populated except id and client id. */ newEmptyMetadataWithIdClientIdAndVersion( String id, String clientId, int version)137 public static Metadata newEmptyMetadataWithIdClientIdAndVersion( 138 String id, String clientId, int version) { 139 return new Metadata.Builder() 140 .setId(id) 141 .setClientRecordId(clientId) 142 .setClientRecordVersion(version) 143 .build(); 144 } 145 146 /** 147 * Returns an instance of metadata with all possible fields populated except ids and version. 148 */ newFullMetadataWithoutIds()149 public static Metadata newFullMetadataWithoutIds() { 150 return newFullMetadataBuilderWithoutIds().build(); 151 } 152 153 /** 154 * Returns an instance of metadata builder with all possible fields populated except ids and 155 * version. 156 */ newFullMetadataBuilderWithoutIds()157 private static Metadata.Builder newFullMetadataBuilderWithoutIds() { 158 return new Metadata.Builder() 159 .setDataOrigin(new DataOrigin.Builder().setPackageName("foo.package.name").build()) 160 .setRecordingMethod(RECORDING_METHOD_MANUAL_ENTRY) 161 .setDevice( 162 new Device.Builder() 163 .setType(DEVICE_TYPE_WATCH) 164 .setManufacturer("foo-manufacturer") 165 .setModel("foo-model") 166 .build()) 167 .setLastModifiedTime(Instant.now().minusSeconds(120)); 168 } 169 170 /** 171 * Returns an instance of metadata with all possible fields populated except client id and 172 * version. 173 */ newFullMetadataWithId(String id)174 public static Metadata newFullMetadataWithId(String id) { 175 return newFullMetadataBuilderWithoutIds().setId(id).build(); 176 } 177 178 /** Returns an instance of metadata with all possible fields populated except id and version. */ newFullMetadataWithClientId(String clientId)179 public static Metadata newFullMetadataWithClientId(String clientId) { 180 return newFullMetadataBuilderWithoutIds().setClientRecordId(clientId).build(); 181 } 182 183 /** Returns an instance of metadata with all possible fields populated except id. */ newFullMetadataWithClientIdAndVersion(String clientId, int version)184 public static Metadata newFullMetadataWithClientIdAndVersion(String clientId, int version) { 185 return newFullMetadataBuilderWithoutIds() 186 .setClientRecordId(clientId) 187 .setClientRecordVersion(version) 188 .build(); 189 } 190 191 /** 192 * Same as {@link #newFullMetadataWithId(String)} but all fields are set to different values. 193 */ newAnotherFullMetadataWithId(String id)194 public static Metadata newAnotherFullMetadataWithId(String id) { 195 return newAnotherFullMetadataBuilderWithoutIds().setId(id).build(); 196 } 197 198 /** 199 * Same as {@link #newFullMetadataWithClientId(String)} but all fields are set to different 200 * values. 201 */ newAnotherFullMetadataWithClientId(String clientId)202 public static Metadata newAnotherFullMetadataWithClientId(String clientId) { 203 return newAnotherFullMetadataBuilderWithoutIds().setClientRecordId(clientId).build(); 204 } 205 206 /** 207 * Same as {@link #newFullMetadataWithClientIdAndVersion(String, int)} but all fields are set to 208 * different values. 209 */ newAnotherFullMetadataWithClientIdAndVersion( String clientId, int version)210 public static Metadata newAnotherFullMetadataWithClientIdAndVersion( 211 String clientId, int version) { 212 return newAnotherFullMetadataBuilderWithoutIds() 213 .setClientRecordId(clientId) 214 .setClientRecordVersion(version) 215 .build(); 216 } 217 218 /** 219 * Same as {@link #newFullMetadataBuilderWithoutIds()} but all fields are set to different 220 * values. 221 */ newAnotherFullMetadataBuilderWithoutIds()222 private static Metadata.Builder newAnotherFullMetadataBuilderWithoutIds() { 223 return new Metadata.Builder() 224 .setDataOrigin(new DataOrigin.Builder().setPackageName("bar.package.name").build()) 225 .setRecordingMethod(RECORDING_METHOD_AUTOMATICALLY_RECORDED) 226 .setDevice( 227 new Device.Builder() 228 .setType(DEVICE_TYPE_RING) 229 .setManufacturer("bar-manufacturer") 230 .setModel("bar-model") 231 .build()) 232 .setLastModifiedTime(Instant.now().minusSeconds(234)); 233 } 234 235 /** Converts the given metadata to the corresponding builder. */ toBuilder(Metadata metadata)236 private static Metadata.Builder toBuilder(Metadata metadata) { 237 return new Metadata.Builder() 238 .setId(metadata.getId()) 239 .setLastModifiedTime(metadata.getLastModifiedTime()) 240 .setRecordingMethod(metadata.getRecordingMethod()) 241 .setDataOrigin(metadata.getDataOrigin()) 242 .setDevice(metadata.getDevice()) 243 .setClientRecordVersion(metadata.getClientRecordVersion()) 244 .setClientRecordId(metadata.getClientRecordId()); 245 } 246 247 /** Returns a record test helper for given data type. */ 248 @Nullable forDataType(Class<? extends Record> recordClass)249 public static RecordFactory<? extends Record> forDataType(Class<? extends Record> recordClass) { 250 if (recordClass.equals(ActivityIntensityRecord.class)) { 251 return new ActivityIntensityRecordFactory(); 252 } else if (recordClass.equals(MindfulnessSessionRecord.class)) { 253 return new MindfulnessSessionRecordFactory(); 254 } 255 return null; 256 } 257 } 258