• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package com.android.timezone.location.provider.core;
17 
18 import static com.android.timezone.location.provider.core.LogUtils.formatElapsedRealtimeMillis;
19 import static com.android.timezone.location.provider.core.Mode.prettyPrintListenModeEnum;
20 
21 import static java.util.concurrent.TimeUnit.NANOSECONDS;
22 
23 import android.location.Location;
24 import android.os.SystemClock;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 
29 import com.android.timezone.location.common.PiiLoggable;
30 import com.android.timezone.location.common.PiiLoggables;
31 import com.android.timezone.location.lookup.GeoTimeZonesFinder;
32 import com.android.timezone.location.provider.core.OfflineLocationTimeZoneDelegate.ListenModeEnum;
33 import com.android.timezone.location.common.PiiLoggables.PiiLoggableValue;
34 
35 import java.io.IOException;
36 import java.time.Duration;
37 import java.util.Objects;
38 import java.util.function.Consumer;
39 
40 /**
41  * An interface that {@link OfflineLocationTimeZoneDelegate} uses to interact with its environment.
42  *
43  * <p>This interface exists to make the {@link OfflineLocationTimeZoneDelegate} more testable.
44  */
45 public interface Environment {
46 
47     /**
48      * Returns the {@link LocationListeningAccountant}. Callers may safely retain a reference.
49      */
50     @NonNull
getLocationListeningAccountant()51     LocationListeningAccountant getLocationListeningAccountant();
52 
53     /**
54      * Requests a callback to {@code callback} with {@code callbackToken} after at least
55      * {@code delayMillis}. An object is returned that can be used to cancel the callback later.
56      */
57     @NonNull
requestDelayedCallback(@onNull Consumer<T> callback, @Nullable T callbackToken, @NonNull Duration delay)58     <T> Cancellable requestDelayedCallback(@NonNull Consumer<T> callback,
59             @Nullable T callbackToken, @NonNull Duration delay);
60 
61     /**
62      * The result of a location listening attempt started with {@link
63      * Environment#startActiveGetCurrentLocation} or {@link
64      * Environment#startPassiveLocationListening}}.
65      *
66      * <p>With active listening the {@link #getLocation() location} can be {@code null} (meaning
67      * "location unknown"), with passive listening it is never {@code null}.
68      */
69     final class LocationListeningResult implements PiiLoggable {
70         /**
71          * The type of listening that produced the result. This is recorded for logging / debugging.
72          */
73         private final @ListenModeEnum int mListenMode;
74 
75         /** The duration argument passed to the listening method. */
76         @NonNull private final Duration mListeningDuration;
77 
78         /** An approximate time when listening started. */
79         private final long mStartElapsedRealtimeMillis;
80 
81         /**
82          * An approximate time when the result was received (which might be different from the
83          * time associated with any location).
84          */
85         private final long mResultElapsedRealtimeMillis;
86 
87         /** Holds the location, or {@code null} if the location is not known (active only). */
88         @NonNull private final PiiLoggableValue<Location> mPiiLoggableLocation;
89 
LocationListeningResult( @istenModeEnum int listenMode, @NonNull Duration listeningDuration, long startElapsedRealtimeMillis, long resultElapsedRealtimeMillis, @Nullable Location location)90         public LocationListeningResult(
91                 @ListenModeEnum int listenMode,
92                 @NonNull Duration listeningDuration,
93                 long startElapsedRealtimeMillis,
94                 long resultElapsedRealtimeMillis,
95                 @Nullable Location location) {
96             mListenMode = listenMode;
97             mListeningDuration = Objects.requireNonNull(listeningDuration);
98             mStartElapsedRealtimeMillis = startElapsedRealtimeMillis;
99             mResultElapsedRealtimeMillis = resultElapsedRealtimeMillis;
100             mPiiLoggableLocation = PiiLoggables.fromPiiValue(location);
101         }
102 
103         /** Returns how long listening was requested for. */
104         @NonNull
getRequestedListeningDuration()105         public Duration getRequestedListeningDuration() {
106             return mListeningDuration;
107         }
108 
109         /** Returns whether result of listening was a known location. */
isLocationKnown()110         public boolean isLocationKnown() {
111             return mPiiLoggableLocation.get() != null;
112         }
113 
114         /** Returns the location. See {@link #isLocationKnown()}. */
115         @Nullable
getLocation()116         public Location getLocation() {
117             return mPiiLoggableLocation.get();
118         }
119 
120         /** Returns (an approximation) of when listening started. */
getStartElapsedRealtimeTimeMillis()121         public long getStartElapsedRealtimeTimeMillis() {
122             return mStartElapsedRealtimeMillis;
123         }
124 
125         /** Returns (an approximation) of when the result was obtained. */
getResultElapsedRealtimeMillis()126         public long getResultElapsedRealtimeMillis() {
127             return mResultElapsedRealtimeMillis;
128         }
129 
130         /**
131          * Returns the estimated time between when the listening session started and this result was
132          * generated. This value is <em>not</em> incremental, i.e. if listening is continuous then
133          * this returns the total time listening and not the time elapsed since the last result.
134          */
135         @NonNull
getTotalEstimatedTimeListening()136         public Duration getTotalEstimatedTimeListening() {
137             Duration estimatedTimeListening =
138                     Duration.ofMillis(mResultElapsedRealtimeMillis - mStartElapsedRealtimeMillis);
139             // Guard against invalid times that could be caused if locations have an incorrect
140             // elapsed realtime associated with them.
141             return estimatedTimeListening.isNegative() ? Duration.ZERO : estimatedTimeListening;
142         }
143 
144         /** Returns the age of the result. */
145         @NonNull
getResultAge(long elapsedRealtimeMillis)146         public Duration getResultAge(long elapsedRealtimeMillis) {
147             long ageMillis = elapsedRealtimeMillis - mResultElapsedRealtimeMillis;
148             return Duration.ofMillis(ageMillis);
149         }
150 
151         /**
152          * Returns the age of the location in the result. Throws an exception if
153          * {@link #isLocationKnown()} is {@code false}.
154          */
155         @NonNull
getLocationAge(long elapsedRealtimeMillis)156         public Duration getLocationAge(long elapsedRealtimeMillis) {
157             Location location = mPiiLoggableLocation.get();
158             if (location == null) {
159                 throw new IllegalStateException();
160             }
161             long locationAgeMillis = elapsedRealtimeMillis
162                     - NANOSECONDS.toMillis(location.getElapsedRealtimeNanos());
163             return Duration.ofMillis(locationAgeMillis);
164         }
165 
166         @Override
toPiiString()167         public String toPiiString() {
168             String template = toStringTemplate();
169             return PiiLoggables.formatPiiString(template, mPiiLoggableLocation);
170         }
171 
172         @Override
toString()173         public String toString() {
174             String template = toStringTemplate();
175             return String.format(template, mPiiLoggableLocation);
176         }
177 
toStringTemplate()178         private String toStringTemplate() {
179             return "LocationListeningResult{"
180                     + "mListenMode=" + prettyPrintListenModeEnum(mListenMode)
181                     + ", mListeningDuration=" + mListeningDuration
182                     + ", mStartElapsedRealtimeMillis="
183                     + formatElapsedRealtimeMillis(mStartElapsedRealtimeMillis)
184                     + ", mResultElapsedRealtimeMillis="
185                     + formatElapsedRealtimeMillis(mResultElapsedRealtimeMillis)
186                     + ", mPiiLoggableLocation=%s"
187                     + ", getTotalEstimatedTimeListening()=" + getTotalEstimatedTimeListening()
188                     + '}';
189         }
190     }
191 
192     /**
193      * Starts a continuous passive async location lookup.
194      *
195      * <p>The location referenced by the {@link LocationListeningResult} passed to {@code
196      * locationResultConsumer} will never be {@code null}.
197      *
198      * <p>After (at least) {@code duration} has elapsed then listening will be
199      * automatically halted and {@code passiveListeningCompletedCallback} will be called with
200      * an estimate of elapsed time spent listening.
201      *
202      * <p>Returns a {@link Cancellable} that can be used to stop listening early, if called then
203      * {@code passiveListeningCompletedCallback} will not be called
204      *
205      * @param duration the duration to listen for
206      */
207     @NonNull
startPassiveLocationListening(@onNull Duration duration, @NonNull Consumer<LocationListeningResult> locationResultConsumer, @NonNull Consumer<Duration> passiveListeningCompletedCallback)208     Cancellable startPassiveLocationListening(@NonNull Duration duration,
209             @NonNull Consumer<LocationListeningResult> locationResultConsumer,
210             @NonNull Consumer<Duration> passiveListeningCompletedCallback);
211 
212     /**
213      * Starts a one-off, async, active location lookup. The location referenced by the {@link
214      * LocationListeningResult} passed to {@code locationResultConsumer} can be {@code null}.
215      * Returns a {@link Cancellable} that can be used to stop listening.
216      *
217      * @param duration the length of time to actively seek location
218      */
219     @NonNull
startActiveGetCurrentLocation(@onNull Duration duration, @NonNull Consumer<LocationListeningResult> locationResultConsumer)220     Cancellable startActiveGetCurrentLocation(@NonNull Duration duration,
221             @NonNull Consumer<LocationListeningResult> locationResultConsumer);
222 
223     /**
224      * Returns an object that can be used to lookup time zones for a location.
225      *
226      * @throws IOException if there is a problem loading the tz geolocation data files
227      */
228     @NonNull
createGeoTimeZoneFinder()229     GeoTimeZonesFinder createGeoTimeZoneFinder() throws IOException;
230 
231     /**
232      * Used to report location time zone information.
233      */
reportTimeZoneProviderResult(@onNull TimeZoneProviderResult result)234     void reportTimeZoneProviderResult(@NonNull TimeZoneProviderResult result);
235 
236     /**
237      * Acquires a (partial) wake lock. Used to ensure that calculations using
238      * {@link #elapsedRealtimeMillis()} are reasonably accurate.
239      * See also {@link #releaseWakeLock()}.
240      */
acquireWakeLock()241     void acquireWakeLock();
242 
243     /**
244      * Releases the (partial) wake lock acquired with {@link #acquireWakeLock()}.
245      */
releaseWakeLock()246     void releaseWakeLock();
247 
248     /**
249      * Returns the current elapsed time in milliseconds. See {@link
250      * SystemClock#elapsedRealtime()}.
251      *
252      * <p>If it is being used for age calculations then use {@link #acquireWakeLock()} and
253      * {@link #releaseWakeLock()} to avoid dozing that might throw off calculations.
254      */
elapsedRealtimeMillis()255     long elapsedRealtimeMillis();
256 }
257