1 /*
2  * Copyright (C) 2020 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.location;
18 
19 import android.annotation.IntRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 
25 import com.android.internal.util.Preconditions;
26 
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Objects;
31 import java.util.function.Function;
32 import java.util.function.Predicate;
33 
34 /**
35  * A location result representing a list of locations, ordered from earliest to latest.
36  *
37  * @hide
38  */
39 public final class LocationResult implements Parcelable {
40 
41     /**
42      * Creates a new LocationResult from the given locations, making a copy of each location.
43      * Locations must be ordered in the same order they were derived (earliest to latest).
44      */
create(@onNull List<Location> locations)45     public static @NonNull LocationResult create(@NonNull List<Location> locations) {
46         Preconditions.checkArgument(!locations.isEmpty());
47         ArrayList<Location> locationsCopy = new ArrayList<>(locations.size());
48         for (Location location : locations) {
49             locationsCopy.add(new Location(Objects.requireNonNull(location)));
50         }
51         return new LocationResult(locationsCopy);
52     }
53 
54     /**
55      * Creates a new LocationResult from the given locations, making a copy of each location.
56      * Locations must be ordered in the same order they were derived (earliest to latest).
57      */
create(@onNull Location... locations)58     public static @NonNull LocationResult create(@NonNull Location... locations) {
59         Preconditions.checkArgument(locations.length > 0);
60         ArrayList<Location> locationsCopy = new ArrayList<>(locations.length);
61         for (Location location : locations) {
62             locationsCopy.add(new Location(Objects.requireNonNull(location)));
63         }
64         return new LocationResult(locationsCopy);
65     }
66 
67     /**
68      * Creates a new LocationResult that takes ownership of the given locations without copying
69      * them. Callers must ensure the given locations are never mutated after this method is called.
70      * Locations must be ordered in the same order they were derived (earliest to latest).
71      */
wrap(@onNull List<Location> locations)72     public static @NonNull LocationResult wrap(@NonNull List<Location> locations) {
73         Preconditions.checkArgument(!locations.isEmpty());
74         return new LocationResult(new ArrayList<>(locations));
75     }
76 
77     /**
78      * Creates a new LocationResult that takes ownership of the given locations without copying
79      * them. Callers must ensure the given locations are never mutated after this method is called.
80      * Locations must be ordered in the same order they were derived (earliest to latest).
81      */
wrap(@onNull Location... locations)82     public static @NonNull LocationResult wrap(@NonNull Location... locations) {
83         Preconditions.checkArgument(locations.length > 0);
84         ArrayList<Location> newLocations = new ArrayList<>(locations.length);
85         for (Location location : locations) {
86             newLocations.add(Objects.requireNonNull(location));
87         }
88         return new LocationResult(newLocations);
89     }
90 
91     private final ArrayList<Location> mLocations;
92 
LocationResult(ArrayList<Location> locations)93     private LocationResult(ArrayList<Location> locations) {
94         Preconditions.checkArgument(!locations.isEmpty());
95         mLocations = locations;
96     }
97 
98     /**
99      * Throws an IllegalArgumentException if the ordering of locations does not appear to generally
100      * be from earliest to latest, or if any individual location is incomplete.
101      *
102      * @hide
103      */
validate()104     public @NonNull LocationResult validate() {
105         long prevElapsedRealtimeNs = 0;
106         final int size = mLocations.size();
107         for (int i = 0; i < size; ++i) {
108             Location location = mLocations.get(i);
109             if (!location.isComplete()) {
110                 throw new IllegalArgumentException(
111                         "incomplete location at index " + i + ": " + mLocations);
112             }
113             if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
114                 throw new IllegalArgumentException(
115                         "incorrectly ordered location at index " + i + ": " + mLocations);
116             }
117             prevElapsedRealtimeNs = location.getElapsedRealtimeNanos();
118         }
119 
120         return this;
121     }
122 
123     /**
124      * Returns the latest location in this location result, ie, the location at the highest index.
125      */
getLastLocation()126     public @NonNull Location getLastLocation() {
127         return mLocations.get(mLocations.size() - 1);
128     }
129 
130     /**
131      * Returns the number of locations in this location result.
132      */
size()133     public @IntRange(from = 1) int size() {
134         return mLocations.size();
135     }
136 
137     /**
138      * Returns the location at the given index, from 0 to {@link #size()} - 1. Locations at lower
139      * indices are from earlier in time than location at higher indices.
140      */
get(@ntRangefrom = 0) int i)141     public @NonNull Location get(@IntRange(from = 0) int i) {
142         return mLocations.get(i);
143     }
144 
145     /**
146      * Returns an unmodifiable list of locations in this location result.
147      */
asList()148     public @NonNull List<Location> asList() {
149         return Collections.unmodifiableList(mLocations);
150     }
151 
152     /**
153      * Returns a deep copy of this LocationResult.
154      *
155      * @hide
156      */
deepCopy()157     public @NonNull LocationResult deepCopy() {
158         final int size = mLocations.size();
159         ArrayList<Location> copy = new ArrayList<>(size);
160         for (int i = 0; i < size; i++) {
161             copy.add(new Location(mLocations.get(i)));
162         }
163         return new LocationResult(copy);
164     }
165 
166     /**
167      * Returns a LocationResult with only the last location from this location result.
168      *
169      * @hide
170      */
asLastLocationResult()171     public @NonNull LocationResult asLastLocationResult() {
172         if (mLocations.size() == 1) {
173             return this;
174         } else {
175             return LocationResult.wrap(getLastLocation());
176         }
177     }
178 
179     /**
180      * Returns a LocationResult with only locations that pass the given predicate. This
181      * implementation will avoid allocations when no locations are filtered out. The predicate is
182      * guaranteed to be invoked once per location, in order from earliest to latest. If all
183      * locations are filtered out a null value is returned.
184      *
185      * @hide
186      */
filter(Predicate<Location> predicate)187     public @Nullable LocationResult filter(Predicate<Location> predicate) {
188         ArrayList<Location> filtered = mLocations;
189         final int size = mLocations.size();
190         for (int i = 0; i < size; ++i) {
191             if (!predicate.test(mLocations.get(i))) {
192                 if (filtered == mLocations) {
193                     filtered = new ArrayList<>(mLocations.size() - 1);
194                     for (int j = 0; j < i; ++j) {
195                         filtered.add(mLocations.get(j));
196                     }
197                 }
198             } else if (filtered != mLocations) {
199                 filtered.add(mLocations.get(i));
200             }
201         }
202 
203         if (filtered == mLocations) {
204             return this;
205         } else if (filtered.isEmpty()) {
206             return null;
207         } else {
208             return new LocationResult(filtered);
209         }
210     }
211 
212     /**
213      * Returns a LocationResult with locations mapped to other locations. This implementation will
214      * avoid allocations when all locations are mapped to the same location. The function is
215      * guaranteed to be invoked once per location, in order from earliest to latest.
216      *
217      * @hide
218      */
map(Function<Location, Location> function)219     public @NonNull LocationResult map(Function<Location, Location> function) {
220         ArrayList<Location> mapped = mLocations;
221         final int size = mLocations.size();
222         for (int i = 0; i < size; ++i) {
223             Location location = mLocations.get(i);
224             Location newLocation = function.apply(location);
225             if (mapped != mLocations) {
226                 mapped.add(newLocation);
227             } else if (newLocation != location) {
228                 mapped = new ArrayList<>(mLocations.size());
229                 for (int j = 0; j < i; ++j) {
230                     mapped.add(mLocations.get(j));
231                 }
232                 mapped.add(newLocation);
233             }
234         }
235 
236         if (mapped == mLocations) {
237             return this;
238         } else {
239             return new LocationResult(mapped);
240         }
241     }
242 
243     public static final @NonNull Parcelable.Creator<LocationResult> CREATOR =
244             new Parcelable.Creator<LocationResult>() {
245                 @Override
246                 public LocationResult createFromParcel(Parcel in) {
247                     return new LocationResult(
248                             Objects.requireNonNull(in.createTypedArrayList(Location.CREATOR)));
249                 }
250 
251                 @Override
252                 public LocationResult[] newArray(int size) {
253                     return new LocationResult[size];
254                 }
255             };
256 
257     @Override
describeContents()258     public int describeContents() {
259         return 0;
260     }
261 
262     @Override
writeToParcel(@onNull Parcel parcel, int flags)263     public void writeToParcel(@NonNull Parcel parcel, int flags) {
264         parcel.writeTypedList(mLocations);
265     }
266 
267     @Override
equals(Object o)268     public boolean equals(Object o) {
269         if (this == o) {
270             return true;
271         }
272         if (o == null || getClass() != o.getClass()) {
273             return false;
274         }
275 
276         LocationResult that = (LocationResult) o;
277         return mLocations.equals(that.mLocations);
278     }
279 
280     @Override
hashCode()281     public int hashCode() {
282         return Objects.hash(mLocations);
283     }
284 
285     @Override
toString()286     public String toString() {
287         return mLocations.toString();
288     }
289 }
290