/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.health.connect.changelog; import android.annotation.NonNull; import android.health.connect.HealthConnectManager; import android.health.connect.datatypes.DataOrigin; import android.health.connect.datatypes.Record; import android.health.connect.datatypes.RecordTypeIdentifier; import android.health.connect.internal.datatypes.utils.RecordMapper; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; /** * A class to request changelog token using {@link HealthConnectManager#getChangeLogToken} * * @see HealthConnectManager#getChangeLogToken */ public final class ChangeLogTokenRequest implements Parcelable { private final Set mDataOriginFilters; private final Set> mRecordTypes; /** * @param dataOriginFilters list of package names to filter the data * @param recordTypes list of records for which change log is required */ private ChangeLogTokenRequest( @NonNull Set dataOriginFilters, @NonNull Set> recordTypes) { Objects.requireNonNull(recordTypes); verifyRecordTypes(recordTypes); Objects.requireNonNull(dataOriginFilters); mDataOriginFilters = dataOriginFilters; mRecordTypes = recordTypes; } private void verifyRecordTypes(Set> recordTypes) { if (recordTypes.isEmpty()) { throw new IllegalArgumentException("Requested record types must not be empty"); } Set invalidRecordTypes = recordTypes.stream() .filter(recordType -> !RecordMapper.getInstance().hasRecordType(recordType)) .map(Class::getName) .collect(Collectors.toSet()); if (!invalidRecordTypes.isEmpty()) { throw new IllegalArgumentException( "Requested record types must not contain any of " + invalidRecordTypes); } } private ChangeLogTokenRequest(@NonNull Parcel in) { RecordMapper recordMapper = RecordMapper.getInstance(); Set> recordTypes = new ArraySet<>(); for (@RecordTypeIdentifier.RecordType int recordType : in.createIntArray()) { recordTypes.add(recordMapper.getRecordIdToExternalRecordClassMap().get(recordType)); } mRecordTypes = recordTypes; Set dataOrigin = new ArraySet<>(); for (String packageName : in.createStringArrayList()) { dataOrigin.add(new DataOrigin.Builder().setPackageName(packageName).build()); } mDataOriginFilters = dataOrigin; } @NonNull public static final Creator CREATOR = new Creator() { @Override public ChangeLogTokenRequest createFromParcel(@NonNull Parcel in) { return new ChangeLogTokenRequest(in); } @Override public ChangeLogTokenRequest[] newArray(int size) { return new ChangeLogTokenRequest[size]; } }; /** Returns list of package names corresponding to which the logs are required */ @NonNull public Set getDataOriginFilters() { return mDataOriginFilters; } /** Returns list of record classes for which the logs are to be fetched */ @NonNull public Set> getRecordTypes() { return mRecordTypes; } /** * Returns List of Record types for which logs are to be fetched * * @hide */ @NonNull public int[] getRecordTypesArray() { return getRecordTypesAsInteger(); } /** * Returns List of Record types for which logs are to be fetched * * @hide */ @NonNull public List getRecordTypesList() { return Arrays.stream(getRecordTypesAsInteger()).boxed().collect(Collectors.toList()); } /** * Returns list of package names corresponding to which the logs are required * * @hide */ @NonNull public List getPackageNamesToFilter() { return getPackageNames(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeIntArray(getRecordTypesAsInteger()); dest.writeStringList(getPackageNames()); } @NonNull private int[] getRecordTypesAsInteger() { int[] recordTypes = new int[mRecordTypes.size()]; int index = 0; for (Class recordClass : mRecordTypes) { recordTypes[index++] = RecordMapper.getInstance().getRecordType(recordClass); } return recordTypes; } @NonNull private List getPackageNames() { List packageNamesToFilter = new ArrayList<>(mDataOriginFilters.size()); mDataOriginFilters.forEach( (dataOrigin) -> packageNamesToFilter.add(dataOrigin.getPackageName())); return packageNamesToFilter; } /** Builder for {@link ChangeLogTokenRequest} */ public static final class Builder { private final Set> mRecordTypes = new ArraySet<>(); private final Set mDataOriginFilters = new ArraySet<>(); /** * @param recordType type of record for which change log is required. At least one record * type must be set. */ @NonNull public Builder addRecordType(@NonNull Class recordType) { Objects.requireNonNull(recordType); mRecordTypes.add(recordType); return this; } /** * @param dataOriginFilter list of package names on which to filter the data. *

If not set logs from all the sources will be returned */ @NonNull public Builder addDataOriginFilter(@NonNull DataOrigin dataOriginFilter) { Objects.requireNonNull(dataOriginFilter); mDataOriginFilters.add(dataOriginFilter); return this; } /** * Returns Object of {@link ChangeLogTokenRequest} * * @throws IllegalArgumentException if record types are empty */ @NonNull public ChangeLogTokenRequest build() { return new ChangeLogTokenRequest(mDataOriginFilters, mRecordTypes); } } }