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 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.OfflineLocationTimeZoneDelegate.LOCATION_LISTEN_MODE_ACTIVE; 20 import static com.android.timezone.location.provider.core.OfflineLocationTimeZoneDelegate.LOCATION_LISTEN_MODE_NA; 21 import static com.android.timezone.location.provider.core.OfflineLocationTimeZoneDelegate.LOCATION_LISTEN_MODE_PASSIVE; 22 23 import android.os.SystemClock; 24 25 import androidx.annotation.IntDef; 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.provider.core.OfflineLocationTimeZoneDelegate.ListenModeEnum; 32 33 import java.util.Objects; 34 35 /** 36 * Holds state associated with the {@link OfflineLocationTimeZoneDelegate}'s mode. This class exists 37 * to make it clear during debugging what transitions the {@link OfflineLocationTimeZoneDelegate} 38 * has gone through to arrive at its current state. 39 * 40 * <p>This class is not thread safe: {@link OfflineLocationTimeZoneDelegate} handles all 41 * synchronization. 42 * 43 * <p>See the docs for each {@code MODE_} constant for an explanation of each mode. 44 * 45 * <pre> 46 * The initial mode is {@link #MODE_STOPPED}. 47 * 48 * Valid transitions: 49 * 50 * {@link #MODE_STOPPED} 51 * -> {@link #MODE_STARTED} 52 * - when the LTZP first receives a "start" request it starts listening for the current 53 * location. 54 * {@link #MODE_STARTED} 55 * -> {@link #MODE_STOPPED} 56 * - when the system server sends a "stopped" request it stops listening for the current 57 * location. 58 * 59 * {All states} 60 * -> {@link #MODE_FAILED} (terminal state) 61 * - when there is a fatal error. 62 * {Most states} 63 * -> {@link #MODE_DESTROYED} (terminal state) 64 * - when the provider's service is destroyed, perhaps as part of the current user changing 65 * </pre> 66 */ 67 class Mode implements PiiLoggable { 68 69 @IntDef({ MODE_STOPPED, MODE_STARTED, MODE_FAILED, MODE_DESTROYED }) 70 @interface ModeEnum {} 71 72 /** 73 * An inactive state. The LTZP may not have received a request yet, or it has and the LTZP has 74 * been explicitly stopped. 75 */ 76 @ModeEnum 77 static final int MODE_STOPPED = 1; 78 79 /** 80 * The LTZP has been started by the system server, and is listening for the current location. 81 * The {@link #mListenMode} records the current type of listening. 82 */ 83 @ModeEnum 84 static final int MODE_STARTED = 2; 85 86 /** 87 * The LTZP's service has been destroyed. 88 */ 89 @ModeEnum 90 static final int MODE_DESTROYED = 3; 91 92 /** 93 * The LTZP encountered a failure it cannot recover from. 94 */ 95 @ModeEnum 96 static final int MODE_FAILED = 4; 97 98 /** The current mode. */ 99 @ModeEnum 100 final int mModeEnum; 101 102 /** 103 * The current location listen mode. Only used when mModeEnum == {@link #MODE_STARTED}. 104 */ 105 final @ListenModeEnum int mListenMode; 106 107 /** 108 * Debug information: The elapsed realtime recorded when the mode was created. 109 */ 110 private final long mCreationElapsedRealtimeMillis; 111 112 /** 113 * Debug information: Information about why the mode was entered. 114 */ 115 @NonNull 116 private final PiiLoggable mEntryCause; 117 118 /** 119 * Used when mModeEnum == {@link #MODE_STARTED}. The {@link Cancellable} that can be 120 * used to stop listening for the current location. 121 */ 122 @Nullable 123 private final Cancellable mLocationListenerCancellable; 124 Mode(@odeEnum int modeEnum, @NonNull PiiLoggable entryCause)125 Mode(@ModeEnum int modeEnum, @NonNull PiiLoggable entryCause) { 126 this(modeEnum, entryCause, LOCATION_LISTEN_MODE_NA, null); 127 } 128 Mode(@odeEnum int modeEnum, @NonNull PiiLoggable entryCause, @ListenModeEnum int listenMode, @Nullable Cancellable listeningCancellable)129 Mode(@ModeEnum int modeEnum, @NonNull PiiLoggable entryCause, @ListenModeEnum int listenMode, 130 @Nullable Cancellable listeningCancellable) { 131 mModeEnum = validateModeEnum(modeEnum); 132 mListenMode = validateListenModeEnum(modeEnum, listenMode); 133 mEntryCause = Objects.requireNonNull(entryCause); 134 mLocationListenerCancellable = listeningCancellable; 135 136 // Information useful for logging / debugging. 137 mCreationElapsedRealtimeMillis = SystemClock.elapsedRealtime(); 138 } 139 140 /** Returns the stopped mode which is the starting state for a provider. */ 141 @NonNull createStoppedMode()142 static Mode createStoppedMode() { 143 return new Mode(MODE_STOPPED, PiiLoggables.fromString("init") /* entryCause */); 144 } 145 146 /** 147 * If this mode is associated with location listening, this invokes {@link 148 * Cancellable#cancel()}. If this mode is not associated with location listening, this is a 149 * no-op. 150 */ cancelLocationListening()151 void cancelLocationListening() { 152 if (mLocationListenerCancellable != null) { 153 mLocationListenerCancellable.cancel(); 154 } 155 } 156 157 @Override toPiiString()158 public String toPiiString() { 159 String template = toStringTemplate(); 160 return PiiLoggables.formatPiiString(template, mEntryCause); 161 } 162 163 @Override toString()164 public String toString() { 165 String template = toStringTemplate(); 166 return String.format(template, mEntryCause); 167 } 168 toStringTemplate()169 private String toStringTemplate() { 170 return "Mode{" 171 + "mModeEnum=" + prettyPrintModeEnum(mModeEnum) 172 + ", mListenMode=" + prettyPrintListenModeEnum(mListenMode) 173 + ", mCreationElapsedRealtimeMillis=" 174 + formatElapsedRealtimeMillis(mCreationElapsedRealtimeMillis) 175 + ", mEntryCause={%s}" 176 + ", mLocationListenerCancellable=" + mLocationListenerCancellable 177 + '}'; 178 } 179 180 /** Returns a string representation of the {@link ModeEnum} value provided. */ prettyPrintModeEnum(@odeEnum int modeEnum)181 static String prettyPrintModeEnum(@ModeEnum int modeEnum) { 182 switch (modeEnum) { 183 case MODE_STOPPED: 184 return "MODE_STOPPED"; 185 case MODE_STARTED: 186 return "MODE_STARTED"; 187 case MODE_DESTROYED: 188 return "MODE_DESTROYED"; 189 case MODE_FAILED: 190 return "MODE_FAILED"; 191 default: 192 return modeEnum + " (Unknown)"; 193 } 194 } 195 196 /** Returns a string representation of the {@link ListenModeEnum} value provided. */ prettyPrintListenModeEnum(@istenModeEnum int listenMode)197 static String prettyPrintListenModeEnum(@ListenModeEnum int listenMode) { 198 switch (listenMode) { 199 case LOCATION_LISTEN_MODE_NA: 200 return "LISTEN_MODE_NA"; 201 case LOCATION_LISTEN_MODE_ACTIVE: 202 return "LOCATION_LISTEN_MODE_ACTIVE"; 203 case LOCATION_LISTEN_MODE_PASSIVE: 204 return "LOCATION_LISTEN_MODE_PASSIVE"; 205 default: 206 return listenMode + " (Unknown)"; 207 } 208 } 209 validateModeEnum(@odeEnum int modeEnum)210 private static @ModeEnum int validateModeEnum(@ModeEnum int modeEnum) { 211 if (modeEnum < MODE_STOPPED || modeEnum > MODE_FAILED) { 212 throw new IllegalArgumentException("modeEnum=" + modeEnum); 213 } 214 return modeEnum; 215 } 216 validateListenModeEnum( @odeEnum int modeEnum, @ListenModeEnum int listenMode)217 private static @ListenModeEnum int validateListenModeEnum( 218 @ModeEnum int modeEnum, @ListenModeEnum int listenMode) { 219 if (modeEnum == MODE_STARTED) { 220 if (listenMode != LOCATION_LISTEN_MODE_ACTIVE 221 && listenMode != LOCATION_LISTEN_MODE_PASSIVE) { 222 throw new IllegalArgumentException(); 223 } 224 } else { 225 if (listenMode != LOCATION_LISTEN_MODE_NA) { 226 throw new IllegalArgumentException(); 227 } 228 } 229 return listenMode; 230 } 231 } 232