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 17 package com.android.timezone.location.provider.core; 18 19 import androidx.annotation.NonNull; 20 import androidx.annotation.Nullable; 21 22 import com.android.timezone.location.provider.core.Environment.LocationListeningResult; 23 import com.android.timezone.location.provider.core.OfflineLocationTimeZoneDelegate.ListenModeEnum; 24 25 import java.time.Duration; 26 import java.util.Objects; 27 28 /** 29 * A class that keeps track of the amount of active listening the provider is permitted to do and 30 * issues instructions for the {@link OfflineLocationTimeZoneDelegate} to follow. 31 * 32 * <p>This logic has been extracted from {@link OfflineLocationTimeZoneDelegate} to enable 33 * independent unit testing of the logic. 34 * 35 * <p>The goal of this class is to ensure that power usage stays below an upper bound. Passive 36 * location listening and periods in doze are treated as "free", but active location listening is 37 * treated as expensive and must be limited. The <em>actual</em> expense of active listening is 38 * dependent on factors that are hard to account for, so it is treated consistently as "worst case". 39 * 40 * <p>The accountant stores a "balance" of the amount of active listening it is allowed to do. The 41 * accountant ensures the balance stays between zero and an upper bound (cap). In order to accrue 42 * active listening time, time must be spent <em>not</em> active listening. 43 * 44 * <p>The balance is reduced by an amount when the {@link OfflineLocationTimeZoneDelegate} calls 45 * {@link #getNextListeningInstruction(long, LocationListeningResult)} and is given an instruction 46 * to perform active listening for that amount of time. 47 * 48 * <p>The active listening balance can be increased directly via {@link 49 * #depositActiveListeningAmount(Duration)} (e.g. to return previously allocated but unused 50 * active listening time), or indirectly via {@link #accrueActiveListeningBudget(Duration)}, which 51 * is called to record time elapsed while <em>not</em> active listening. 52 */ 53 public interface LocationListeningAccountant { 54 55 /** 56 * Deposit an amount of active listening. Used when budget previously allocated with 57 * {@link #getNextListeningInstruction(long, LocationListeningResult)} is not all used. The 58 * balance after this operation will not exceed the cap. 59 */ depositActiveListeningAmount(@onNull Duration amount)60 void depositActiveListeningAmount(@NonNull Duration amount); 61 62 /** 63 * Accrue an amount of active listening by reporting the amount of time spent in passive 64 * listening mode. The balance after this operation will not exceed the cap. 65 */ accrueActiveListeningBudget(@onNull Duration timeInPassiveMode)66 void accrueActiveListeningBudget(@NonNull Duration timeInPassiveMode); 67 68 /** An instruction for the {@link OfflineLocationTimeZoneDelegate} to follow. */ 69 final class ListeningInstruction { 70 71 /** The mode to listen in. */ 72 @ListenModeEnum 73 public final int listenMode; 74 75 /** How long to listen for. */ 76 @NonNull 77 public final Duration duration; 78 ListeningInstruction(@istenModeEnum int listenMode, @NonNull Duration duration)79 public ListeningInstruction(@ListenModeEnum int listenMode, @NonNull Duration duration) { 80 this.listenMode = listenMode; 81 this.duration = Objects.requireNonNull(duration); 82 } 83 84 @Override equals(Object o)85 public boolean equals(Object o) { 86 if (this == o) { 87 return true; 88 } 89 if (o == null || getClass() != o.getClass()) { 90 return false; 91 } 92 ListeningInstruction that = (ListeningInstruction) o; 93 return listenMode == that.listenMode 94 && duration.equals(that.duration); 95 } 96 97 @Override hashCode()98 public int hashCode() { 99 return Objects.hash(listenMode, duration); 100 } 101 102 @Override toString()103 public String toString() { 104 return "ListeningInstruction{" 105 + "listenMode=" + listenMode 106 + ", duration=" + duration 107 + '}'; 108 } 109 } 110 111 /** Returns the next listening instruction. */ 112 @NonNull getNextListeningInstruction(long elapsedRealtimeMillis, @Nullable LocationListeningResult lastLocationListeningResult)113 ListeningInstruction getNextListeningInstruction(long elapsedRealtimeMillis, 114 @Nullable LocationListeningResult lastLocationListeningResult); 115 116 /** 117 * Withdraws the current active listening balance, leaving it at zero. Used when transferring 118 * budget from one accountant to another. 119 */ 120 @NonNull withdrawActiveListeningBalance()121 Duration withdrawActiveListeningBalance(); 122 } 123