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 android.health.connect;
18 
19 import static android.health.connect.Constants.DEFAULT_LONG;
20 import static android.health.connect.Constants.DEFAULT_PAGE_SIZE;
21 import static android.health.connect.Constants.MAXIMUM_PAGE_SIZE;
22 import static android.health.connect.Constants.MINIMUM_PAGE_SIZE;
23 
24 import android.annotation.IntRange;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.health.connect.aidl.ReadRecordsRequestParcel;
28 import android.health.connect.datatypes.DataOrigin;
29 import android.health.connect.datatypes.Record;
30 import android.os.OutcomeReceiver;
31 import android.util.ArraySet;
32 
33 import java.util.Objects;
34 import java.util.Set;
35 import java.util.concurrent.Executor;
36 
37 /**
38  * Class to represent a request based on time range and data origin filters for {@link
39  * HealthConnectManager#readRecords(ReadRecordsRequest, Executor, OutcomeReceiver)}
40  *
41  * @param <T> the type of the Record for the request
42  */
43 public final class ReadRecordsRequestUsingFilters<T extends Record> extends ReadRecordsRequest<T> {
44     private final TimeRangeFilter mTimeRangeFilter;
45     private final Set<DataOrigin> mDataOrigins;
46     private final int mPageSize;
47     private final long mPageToken;
48     private final boolean mAscending;
49 
50     /**
51      * @see Builder
52      */
ReadRecordsRequestUsingFilters( @onNull TimeRangeFilter timeRangeFilter, @NonNull Class<T> recordType, @NonNull Set<DataOrigin> dataOrigins, int pageSize, long pageToken, boolean ascending)53     private ReadRecordsRequestUsingFilters(
54             @NonNull TimeRangeFilter timeRangeFilter,
55             @NonNull Class<T> recordType,
56             @NonNull Set<DataOrigin> dataOrigins,
57             int pageSize,
58             long pageToken,
59             boolean ascending) {
60         super(recordType);
61         Objects.requireNonNull(dataOrigins);
62         mTimeRangeFilter = timeRangeFilter;
63         mDataOrigins = dataOrigins;
64         mPageSize = pageSize;
65         mAscending = PageTokenWrapper.from(pageToken, ascending).isAscending();
66         mPageToken = pageToken;
67     }
68 
69     /** Returns time range b/w which the read operation is to be performed */
70     @Nullable
getTimeRangeFilter()71     public TimeRangeFilter getTimeRangeFilter() {
72         return mTimeRangeFilter;
73     }
74 
75     /**
76      * Returns the set of {@link DataOrigin data origins} to be read, or empty list for no filter
77      */
78     @NonNull
getDataOrigins()79     public Set<DataOrigin> getDataOrigins() {
80         return mDataOrigins;
81     }
82 
83     /** Returns maximum number of records to be returned by the read operation */
84     @IntRange(from = 1, to = 5000)
getPageSize()85     public int getPageSize() {
86         return mPageSize;
87     }
88 
89     /** Returns page token to read the current page of the result. -1 if none available */
getPageToken()90     public long getPageToken() {
91         return mPageToken;
92     }
93 
94     /** Returns ordering of results to be returned */
isAscending()95     public boolean isAscending() {
96         return mAscending;
97     }
98 
99     /**
100      * Returns an object of ReadRecordsRequestParcel to carry read request
101      *
102      * @hide
103      */
104     @NonNull
toReadRecordsRequestParcel()105     public ReadRecordsRequestParcel toReadRecordsRequestParcel() {
106         return new ReadRecordsRequestParcel(this);
107     }
108 
109     /** Builder class for {@link ReadRecordsRequestUsingFilters} */
110     public static final class Builder<T extends Record> {
111         private final Class<T> mRecordType;
112         private final Set<DataOrigin> mDataOrigins = new ArraySet<>();
113         private TimeRangeFilter mTimeRangeFilter;
114         private int mPageSize = DEFAULT_PAGE_SIZE;
115         private long mPageToken = DEFAULT_LONG;
116         private boolean mAscending = true;
117         private boolean mIsOrderingSet = false;
118 
119         /**
120          * @param recordType Class object of {@link Record} type that needs to be read
121          */
122         @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
Builder(@onNull Class<T> recordType)123         public Builder(@NonNull Class<T> recordType) {
124             Objects.requireNonNull(recordType);
125 
126             mRecordType = recordType;
127         }
128 
129         /**
130          * Sets the data origin filter based on which the read operation is to be performed
131          *
132          * @param dataOrigin Adds {@link DataOrigin} for which to read records.
133          *     <p>If no {@link DataOrigin} is added then records by all {@link DataOrigin}s will be
134          *     read
135          */
136         @NonNull
addDataOrigins(@onNull DataOrigin dataOrigin)137         public Builder<T> addDataOrigins(@NonNull DataOrigin dataOrigin) {
138             Objects.requireNonNull(dataOrigin);
139             mDataOrigins.add(dataOrigin);
140 
141             return this;
142         }
143 
144         /**
145          * Sets time range b/w which the read operation is to be performed
146          *
147          * @param timeRangeFilter Time range b/w which the read operation is to be performed.
148          *     <p>If not time range filter is present all the records will be read without any time
149          *     constraints.
150          */
151         @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
152         @NonNull
setTimeRangeFilter(@ullable TimeRangeFilter timeRangeFilter)153         public Builder<T> setTimeRangeFilter(@Nullable TimeRangeFilter timeRangeFilter) {
154             mTimeRangeFilter = timeRangeFilter;
155             return this;
156         }
157 
158         /**
159          * Sets maximum number of records to be returned by the read operation
160          *
161          * @param pageSize number of records to be returned by the read operation.
162          *     <p>This sets to limit number of rows returned by a read. If not set default is 1000
163          *     and maximum of 5000 records can be sent.
164          */
165         @NonNull
setPageSize(@ntRangefrom = 1, to = 5000) int pageSize)166         public Builder<T> setPageSize(@IntRange(from = 1, to = 5000) int pageSize) {
167             if (pageSize < MINIMUM_PAGE_SIZE || pageSize > MAXIMUM_PAGE_SIZE) {
168                 throw new IllegalArgumentException(
169                         "Valid pageSize range is "
170                                 + MINIMUM_PAGE_SIZE
171                                 + " - "
172                                 + MAXIMUM_PAGE_SIZE
173                                 + ", requested "
174                                 + pageSize);
175             }
176             mPageSize = pageSize;
177             return this;
178         }
179 
180         /**
181          * Sets page token to read the requested page of the result.
182          *
183          * @param pageToken to read the requested page of the result. -1 if none available
184          */
185         @NonNull
setPageToken(long pageToken)186         public Builder<T> setPageToken(long pageToken) {
187             mPageToken = pageToken;
188             return this;
189         }
190 
191         /**
192          * Sets ordering of results to be returned based on start time. Ordering cannot be set along
193          * with page token for subsequent requests. IllegalState exception is thrown when ordering
194          * is set along with the page token.
195          *
196          * @param ascending specifies sorting order of results, if set to true records are sorted on
197          *     start time in ascending fashion, else if set to false then in descending.
198          */
199         @NonNull
setAscending(boolean ascending)200         public Builder<T> setAscending(boolean ascending) {
201             mAscending = ascending;
202             mIsOrderingSet = true;
203             return this;
204         }
205 
206         /**
207          * Returns an Object of {@link ReadRecordsRequestUsingFilters}
208          *
209          * <p>For subsequent read requests, {@link ReadRecordsRequestUsingFilters} does not allow
210          * both pageToken and sort order to be set together.
211          *
212          * <p>If pageToken is set then records will be sorted in same order as the previous result
213          *
214          * <p>If both pageToken and sortOrder are not set then by default records will be sorted by
215          * start time in ascending order.
216          *
217          * @throws IllegalStateException if both pageToken and sort order is set.
218          */
219         @NonNull
build()220         public ReadRecordsRequestUsingFilters<T> build() {
221             if (mPageToken != DEFAULT_LONG && mIsOrderingSet) {
222                 throw new IllegalStateException("Cannot set both pageToken and sort order");
223             }
224             return new ReadRecordsRequestUsingFilters<>(
225                     mTimeRangeFilter, mRecordType, mDataOrigins, mPageSize, mPageToken, mAscending);
226         }
227     }
228 }
229