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