All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.timezonedetector.location.LocationTimeZoneProvider Maven / Gradle / Ivy

/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.timezonedetector.location;

import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;

import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;

import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.SystemClock;
import android.service.timezone.TimeZoneProviderEvent;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.ReferenceWithHistory;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * A facade used by the {@link LocationTimeZoneProviderController} to interact with a location time
 * zone provider. The provider implementation will typically have logic running in another process.
 *
 * 

The provider is supplied with a {@link ProviderListener} via {@link * #initialize(ProviderListener)}. This starts communication of asynchronous detection / error * events back to the {@link LocationTimeZoneProviderController} via the {@link * ProviderListener#onProviderStateChange} method. This call must be made on the * {@link Handler} thread from the {@link ThreadingDomain} passed to the constructor. * *

This class is also responsible for monitoring the initialization timeout for a provider. i.e. * if the provider fails to send its first suggestion within a certain time, this is the component * responsible for generating the necessary "uncertain" event. * *

All incoming calls from the controller except for {@link * LocationTimeZoneProvider#dump(android.util.IndentingPrintWriter, String[])} will be made on the * {@link Handler} thread of the {@link ThreadingDomain} passed to the constructor. */ abstract class LocationTimeZoneProvider implements Dumpable { /** * Listener interface used by the {@link LocationTimeZoneProviderController} to register an * interest in provider events. */ interface ProviderListener { /** * Indicated that a provider changed states. The {@code providerState} indicates which one */ void onProviderStateChange(@NonNull ProviderState providerState); } /** * Listener interface used to log provider events for metrics. */ interface ProviderMetricsLogger { /** Logs that a provider changed state. */ void onProviderStateChanged(@ProviderStateEnum int stateEnum); } /** * Information about the provider's current state. */ static class ProviderState { @IntDef(prefix = "PROVIDER_STATE_", value = { PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_STARTED_INITIALIZING, PROVIDER_STATE_STARTED_CERTAIN, PROVIDER_STATE_STARTED_UNCERTAIN, PROVIDER_STATE_STOPPED, PROVIDER_STATE_PERM_FAILED, PROVIDER_STATE_DESTROYED }) @Retention(RetentionPolicy.SOURCE) @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) @interface ProviderStateEnum {} /** * Uninitialized value. Must not be used afte {@link LocationTimeZoneProvider#initialize}. */ static final @ProviderStateEnum int PROVIDER_STATE_UNKNOWN = 0; /** * The provider is started and has not reported its first event. */ static final @ProviderStateEnum int PROVIDER_STATE_STARTED_INITIALIZING = 1; /** * The provider is started and most recently reported a "suggestion" event. */ static final @ProviderStateEnum int PROVIDER_STATE_STARTED_CERTAIN = 2; /** * The provider is started and most recently reported an "uncertain" event. */ static final @ProviderStateEnum int PROVIDER_STATE_STARTED_UNCERTAIN = 3; /** * The provider is stopped. * * This is the state after {@link #initialize} is called. */ static final @ProviderStateEnum int PROVIDER_STATE_STOPPED = 4; /** * The provider has failed and cannot be restarted. This is a terminated state triggered by * the provider itself. * * Providers may enter this state any time after a provider is started. */ static final @ProviderStateEnum int PROVIDER_STATE_PERM_FAILED = 5; /** * The provider has been destroyed by the controller and cannot be restarted. Similar to * {@link #PROVIDER_STATE_PERM_FAILED} except that a provider is set into this state. */ static final @ProviderStateEnum int PROVIDER_STATE_DESTROYED = 6; /** The {@link LocationTimeZoneProvider} the state is for. */ @NonNull public final LocationTimeZoneProvider provider; /** The state enum value of the current state. */ public final @ProviderStateEnum int stateEnum; /** * The last {@link TimeZoneProviderEvent} received. Only populated when {@link #stateEnum} * is either {@link #PROVIDER_STATE_STARTED_CERTAIN} or {@link * #PROVIDER_STATE_STARTED_UNCERTAIN}, but it can be {@code null} then too if no event has * yet been received. */ @Nullable public final TimeZoneProviderEvent event; /** * The user configuration associated with the current state. Only and always present when * {@link #stateEnum} is one of the started states. */ @Nullable public final ConfigurationInternal currentUserConfiguration; /** * The time according to the elapsed realtime clock when the provider entered the current * state. Included for debugging, not used for equality. */ @ElapsedRealtimeLong private final long mStateEntryTimeMillis; /** * Debug information providing context for the transition to this state. Included for * debugging, not used for equality. */ @Nullable private final String mDebugInfo; private ProviderState(@NonNull LocationTimeZoneProvider provider, @ProviderStateEnum int stateEnum, @Nullable TimeZoneProviderEvent event, @Nullable ConfigurationInternal currentUserConfiguration, @Nullable String debugInfo) { this.provider = Objects.requireNonNull(provider); this.stateEnum = stateEnum; this.event = event; this.currentUserConfiguration = currentUserConfiguration; this.mStateEntryTimeMillis = SystemClock.elapsedRealtime(); this.mDebugInfo = debugInfo; } /** Creates the bootstrap state, uses {@link #PROVIDER_STATE_UNKNOWN}. */ static ProviderState createStartingState( @NonNull LocationTimeZoneProvider provider) { return new ProviderState( provider, PROVIDER_STATE_UNKNOWN, null, null, "Initial state"); } /** * Create a new state from this state. Validates that the state transition is valid * and that the required parameters for the new state are present / absent. */ ProviderState newState(@ProviderStateEnum int newStateEnum, @Nullable TimeZoneProviderEvent event, @Nullable ConfigurationInternal currentUserConfig, @Nullable String debugInfo) { // Check valid "from" transitions. switch (this.stateEnum) { case PROVIDER_STATE_UNKNOWN: { if (newStateEnum != PROVIDER_STATE_STOPPED) { throw new IllegalArgumentException( "Must transition from " + prettyPrintStateEnum( PROVIDER_STATE_UNKNOWN) + " to " + prettyPrintStateEnum(PROVIDER_STATE_STOPPED)); } break; } case PROVIDER_STATE_STOPPED: case PROVIDER_STATE_STARTED_INITIALIZING: case PROVIDER_STATE_STARTED_CERTAIN: case PROVIDER_STATE_STARTED_UNCERTAIN: { // These can go to each other or either of PROVIDER_STATE_PERM_FAILED and // PROVIDER_STATE_DESTROYED. break; } case PROVIDER_STATE_PERM_FAILED: case PROVIDER_STATE_DESTROYED: { throw new IllegalArgumentException("Illegal transition out of " + prettyPrintStateEnum(this.stateEnum)); } default: { throw new IllegalArgumentException("Invalid this.stateEnum=" + this.stateEnum); } } // Validate "to" transitions / arguments. switch (newStateEnum) { case PROVIDER_STATE_UNKNOWN: { throw new IllegalArgumentException("Cannot transition to " + prettyPrintStateEnum(PROVIDER_STATE_UNKNOWN)); } case PROVIDER_STATE_STOPPED: { if (event != null || currentUserConfig != null) { throw new IllegalArgumentException( "Stopped state: event and currentUserConfig must be null" + ", event=" + event + ", currentUserConfig=" + currentUserConfig); } break; } case PROVIDER_STATE_STARTED_INITIALIZING: case PROVIDER_STATE_STARTED_CERTAIN: case PROVIDER_STATE_STARTED_UNCERTAIN: { if (currentUserConfig == null) { throw new IllegalArgumentException( "Started state: currentUserConfig must not be null"); } break; } case PROVIDER_STATE_PERM_FAILED: case PROVIDER_STATE_DESTROYED: { if (event != null || currentUserConfig != null) { throw new IllegalArgumentException( "Terminal state: event and currentUserConfig must be null" + ", newStateEnum=" + newStateEnum + ", event=" + event + ", currentUserConfig=" + currentUserConfig); } break; } default: { throw new IllegalArgumentException("Unknown newStateEnum=" + newStateEnum); } } return new ProviderState(provider, newStateEnum, event, currentUserConfig, debugInfo); } /** Returns {@code true} if {@link #stateEnum} is one of the started states. */ boolean isStarted() { return stateEnum == PROVIDER_STATE_STARTED_INITIALIZING || stateEnum == PROVIDER_STATE_STARTED_CERTAIN || stateEnum == PROVIDER_STATE_STARTED_UNCERTAIN; } /** Returns {@code true} if {@link #stateEnum} is one of the terminated states. */ boolean isTerminated() { return stateEnum == PROVIDER_STATE_PERM_FAILED || stateEnum == PROVIDER_STATE_DESTROYED; } @Override public String toString() { // this.provider is omitted deliberately to avoid recursion, since the provider holds // a reference to its state. return "ProviderState{" + "stateEnum=" + prettyPrintStateEnum(stateEnum) + ", event=" + event + ", currentUserConfiguration=" + currentUserConfiguration + ", mStateEntryTimeMillis=" + mStateEntryTimeMillis + ", mDebugInfo=" + mDebugInfo + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ProviderState state = (ProviderState) o; return stateEnum == state.stateEnum && Objects.equals(event, state.event) && Objects.equals(currentUserConfiguration, state.currentUserConfiguration); } @Override public int hashCode() { return Objects.hash(stateEnum, event, currentUserConfiguration); } private static String prettyPrintStateEnum(@ProviderStateEnum int state) { switch (state) { case PROVIDER_STATE_STOPPED: return "Stopped (" + PROVIDER_STATE_STOPPED + ")"; case PROVIDER_STATE_STARTED_INITIALIZING: return "Started initializing (" + PROVIDER_STATE_STARTED_INITIALIZING + ")"; case PROVIDER_STATE_STARTED_CERTAIN: return "Started certain (" + PROVIDER_STATE_STARTED_CERTAIN + ")"; case PROVIDER_STATE_STARTED_UNCERTAIN: return "Started uncertain (" + PROVIDER_STATE_STARTED_UNCERTAIN + ")"; case PROVIDER_STATE_PERM_FAILED: return "Perm failure (" + PROVIDER_STATE_PERM_FAILED + ")"; case PROVIDER_STATE_DESTROYED: return "Destroyed (" + PROVIDER_STATE_DESTROYED + ")"; case PROVIDER_STATE_UNKNOWN: default: return "Unknown (" + state + ")"; } } } @NonNull private final ProviderMetricsLogger mProviderMetricsLogger; @NonNull final ThreadingDomain mThreadingDomain; @NonNull final Object mSharedLock; @NonNull final String mProviderName; /** * Usually {@code false} but can be set to {@code true} for testing. */ private final boolean mRecordStateChanges; @GuardedBy("mSharedLock") @NonNull private final ArrayList mRecordedStates = new ArrayList<>(0); /** * The current state (with history for debugging). */ @GuardedBy("mSharedLock") final ReferenceWithHistory mCurrentState = new ReferenceWithHistory<>(10); /** * Used for scheduling initialization timeouts, i.e. for providers that have just been started. */ @NonNull private final SingleRunnableQueue mInitializationTimeoutQueue; // Non-null and effectively final after initialize() is called. ProviderListener mProviderListener; @NonNull private final TimeZoneProviderEventPreProcessor mTimeZoneProviderEventPreProcessor; /** Creates the instance. */ LocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger, @NonNull ThreadingDomain threadingDomain, @NonNull String providerName, @NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor, boolean recordStateChanges) { mThreadingDomain = Objects.requireNonNull(threadingDomain); mProviderMetricsLogger = Objects.requireNonNull(providerMetricsLogger); mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue(); mSharedLock = threadingDomain.getLockObject(); mProviderName = Objects.requireNonNull(providerName); mTimeZoneProviderEventPreProcessor = Objects.requireNonNull(timeZoneProviderEventPreProcessor); mRecordStateChanges = recordStateChanges; } /** * Initializes the provider. Called before the provider is first used. */ final void initialize(@NonNull ProviderListener providerListener) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { if (mProviderListener != null) { throw new IllegalStateException("initialize already called"); } mProviderListener = Objects.requireNonNull(providerListener); ProviderState currentState = ProviderState.createStartingState(this); currentState = currentState.newState(PROVIDER_STATE_STOPPED, null, null, "initialize"); setCurrentState(currentState, false); // Guard against uncaught exceptions due to initialization problems. try { onInitialize(); } catch (RuntimeException e) { warnLog("Unable to initialize the provider", e); currentState = currentState.newState(PROVIDER_STATE_PERM_FAILED, null, null, "Failed to initialize: " + e.getMessage()); setCurrentState(currentState, true); } } } /** * Implemented by subclasses to do work during {@link #initialize}. */ @GuardedBy("mSharedLock") abstract void onInitialize(); /** * Destroys the provider. Called after the provider is stopped. This instance will not be called * again by the {@link LocationTimeZoneProviderController}. */ final void destroy() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { ProviderState currentState = mCurrentState.get(); if (!currentState.isTerminated()) { ProviderState destroyedState = currentState.newState(PROVIDER_STATE_DESTROYED, null, null, "destroy"); setCurrentState(destroyedState, false); onDestroy(); } } } /** * Implemented by subclasses to do work during {@link #destroy()}. */ @GuardedBy("mSharedLock") abstract void onDestroy(); /** * Clears recorded state changes. */ final void clearRecordedStates() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { mRecordedStates.clear(); mRecordedStates.trimToSize(); } } /** * Returns recorded states. */ final List getRecordedStates() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { return new ArrayList<>(mRecordedStates); } } /** * Sets the current state. If {@code #notifyChanges} is {@code true} and {@code newState} is not * equal to the old state, then {@link ProviderListener#onProviderStateChange(ProviderState)} * will be called on {@link #mProviderListener}. */ private void setCurrentState(@NonNull ProviderState newState, boolean notifyChanges) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { ProviderState oldState = mCurrentState.get(); mCurrentState.set(newState); onSetCurrentState(newState); if (!Objects.equals(newState, oldState)) { mProviderMetricsLogger.onProviderStateChanged(newState.stateEnum); if (mRecordStateChanges) { mRecordedStates.add(newState); } if (notifyChanges) { mProviderListener.onProviderStateChange(newState); } } } } /** * Overridden by subclasses to do work during {@link #setCurrentState}. */ @GuardedBy("mSharedLock") void onSetCurrentState(ProviderState newState) { // Default no-op. } /** * Returns the current state of the provider. This method must be called using the handler * thread from the {@link ThreadingDomain}. */ @NonNull final ProviderState getCurrentState() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { return mCurrentState.get(); } } /** * Returns the name of the provider. This method must be called using the handler thread from * the {@link ThreadingDomain}. */ final String getName() { mThreadingDomain.assertCurrentThread(); return mProviderName; } /** * Starts the provider. It is an error to call this method except when the {@link * #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_STOPPED}. This method must be * called using the handler thread from the {@link ThreadingDomain}. */ final void startUpdates(@NonNull ConfigurationInternal currentUserConfiguration, @NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz, @NonNull Duration eventFilteringAgeThreshold) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { assertCurrentState(PROVIDER_STATE_STOPPED); ProviderState currentState = mCurrentState.get(); ProviderState newState = currentState.newState(PROVIDER_STATE_STARTED_INITIALIZING, null /* event */, currentUserConfiguration, "startUpdates"); setCurrentState(newState, false); Duration delay = initializationTimeout.plus(initializationTimeoutFuzz); mInitializationTimeoutQueue.runDelayed( this::handleInitializationTimeout, delay.toMillis()); onStartUpdates(initializationTimeout, eventFilteringAgeThreshold); } } private void handleInitializationTimeout() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { ProviderState currentState = mCurrentState.get(); if (currentState.stateEnum == PROVIDER_STATE_STARTED_INITIALIZING) { // On initialization timeout the provider becomes uncertain. ProviderState newState = currentState.newState(PROVIDER_STATE_STARTED_UNCERTAIN, null /* event */, currentState.currentUserConfiguration, "handleInitializationTimeout"); setCurrentState(newState, true); } else { warnLog("handleInitializationTimeout: Initialization timeout triggered when in" + " an unexpected state=" + currentState); } } } /** * Implemented by subclasses to do work during {@link #startUpdates}. This is where the logic * to start the real provider should be implemented. * * @param initializationTimeout the initialization timeout to pass to the provider * @param eventFilteringAgeThreshold the event filtering age threshold to pass to the provider */ abstract void onStartUpdates(@NonNull Duration initializationTimeout, @NonNull Duration eventFilteringAgeThreshold); /** * Stops the provider. It is an error to call this method except when the {@link * #getCurrentState()} is one of the started states. This method must be * called using the handler thread from the {@link ThreadingDomain}. */ final void stopUpdates() { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { assertIsStarted(); ProviderState currentState = mCurrentState.get(); ProviderState newState = currentState.newState(PROVIDER_STATE_STOPPED, null, null, "stopUpdates"); setCurrentState(newState, false); cancelInitializationTimeoutIfSet(); onStopUpdates(); } } /** * Implemented by subclasses to do work during {@link #stopUpdates}. */ abstract void onStopUpdates(); /** For subclasses to invoke when a {@link TimeZoneProviderEvent} has been received. */ final void handleTimeZoneProviderEvent(@NonNull TimeZoneProviderEvent timeZoneProviderEvent) { mThreadingDomain.assertCurrentThread(); Objects.requireNonNull(timeZoneProviderEvent); timeZoneProviderEvent = mTimeZoneProviderEventPreProcessor.preProcess(timeZoneProviderEvent); synchronized (mSharedLock) { debugLog("handleTimeZoneProviderEvent: mProviderName=" + mProviderName + ", timeZoneProviderEvent=" + timeZoneProviderEvent); ProviderState currentState = mCurrentState.get(); int eventType = timeZoneProviderEvent.getType(); switch (currentState.stateEnum) { case PROVIDER_STATE_DESTROYED: case PROVIDER_STATE_PERM_FAILED: { // After entering a terminated state, there is nothing to do. The remote peer is // supposed to stop sending events after it has reported perm failure. warnLog("handleTimeZoneProviderEvent: Event=" + timeZoneProviderEvent + " received for provider=" + this + " when in terminated state"); return; } case PROVIDER_STATE_STOPPED: { switch (eventType) { case EVENT_TYPE_PERMANENT_FAILURE: { String msg = "handleTimeZoneProviderEvent:" + " Failure event=" + timeZoneProviderEvent + " received for stopped provider=" + mProviderName + ", entering permanently failed state"; warnLog(msg); ProviderState newState = currentState.newState( PROVIDER_STATE_PERM_FAILED, null, null, msg); setCurrentState(newState, true); cancelInitializationTimeoutIfSet(); return; } case EVENT_TYPE_SUGGESTION: case EVENT_TYPE_UNCERTAIN: { // Any geolocation-related events received for a stopped provider are // ignored: they should not happen. warnLog("handleTimeZoneProviderEvent:" + " event=" + timeZoneProviderEvent + " received for stopped provider=" + this + ", ignoring"); return; } default: { throw new IllegalStateException( "Unknown eventType=" + timeZoneProviderEvent); } } } case PROVIDER_STATE_STARTED_INITIALIZING: case PROVIDER_STATE_STARTED_CERTAIN: case PROVIDER_STATE_STARTED_UNCERTAIN: { switch (eventType) { case EVENT_TYPE_PERMANENT_FAILURE: { String msg = "handleTimeZoneProviderEvent:" + " Failure event=" + timeZoneProviderEvent + " received for provider=" + mProviderName + " in state=" + ProviderState.prettyPrintStateEnum( currentState.stateEnum) + ", entering permanently failed state"; warnLog(msg); ProviderState newState = currentState.newState( PROVIDER_STATE_PERM_FAILED, null, null, msg); setCurrentState(newState, true); cancelInitializationTimeoutIfSet(); return; } case EVENT_TYPE_UNCERTAIN: case EVENT_TYPE_SUGGESTION: { @ProviderStateEnum int providerStateEnum; if (eventType == EVENT_TYPE_UNCERTAIN) { providerStateEnum = PROVIDER_STATE_STARTED_UNCERTAIN; } else { providerStateEnum = PROVIDER_STATE_STARTED_CERTAIN; } ProviderState newState = currentState.newState(providerStateEnum, timeZoneProviderEvent, currentState.currentUserConfiguration, "handleTimeZoneProviderEvent"); setCurrentState(newState, true); cancelInitializationTimeoutIfSet(); return; } default: { throw new IllegalStateException( "Unknown eventType=" + timeZoneProviderEvent); } } } default: { throw new IllegalStateException("Unknown providerType=" + currentState); } } } } /** For subclasses to invoke when needing to report a temporary failure. */ final void handleTemporaryFailure(String reason) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { ProviderState currentState = mCurrentState.get(); switch (currentState.stateEnum) { case PROVIDER_STATE_STARTED_INITIALIZING: case PROVIDER_STATE_STARTED_UNCERTAIN: case PROVIDER_STATE_STARTED_CERTAIN: { // A temporary failure is treated as becoming uncertain. // This is an unusual PROVIDER_STATE_STARTED_UNCERTAIN state because // event == null String debugInfo = "handleTemporaryFailure: reason=" + reason + ", currentState=" + ProviderState.prettyPrintStateEnum( currentState.stateEnum); ProviderState newState = currentState.newState(PROVIDER_STATE_STARTED_UNCERTAIN, null, currentState.currentUserConfiguration, debugInfo); setCurrentState(newState, true); cancelInitializationTimeoutIfSet(); break; } case PROVIDER_STATE_STOPPED: { debugLog("handleProviderLost reason=" + reason + ", mProviderName=" + mProviderName + ", currentState=" + currentState + ": No state change required, provider is stopped."); break; } case PROVIDER_STATE_PERM_FAILED: case PROVIDER_STATE_DESTROYED: { debugLog("handleProviderLost reason=" + reason + ", mProviderName=" + mProviderName + ", currentState=" + currentState + ": No state change required, provider is terminated."); break; } default: { throw new IllegalStateException("Unknown currentState=" + currentState); } } } } @GuardedBy("mSharedLock") private void assertIsStarted() { ProviderState currentState = mCurrentState.get(); if (!currentState.isStarted()) { throw new IllegalStateException("Required a started state, but was " + currentState); } } @GuardedBy("mSharedLock") private void assertCurrentState(@ProviderStateEnum int requiredState) { ProviderState currentState = mCurrentState.get(); if (currentState.stateEnum != requiredState) { throw new IllegalStateException( "Required stateEnum=" + requiredState + ", but was " + currentState); } } @VisibleForTesting boolean isInitializationTimeoutSet() { synchronized (mSharedLock) { return mInitializationTimeoutQueue.hasQueued(); } } @GuardedBy("mSharedLock") private void cancelInitializationTimeoutIfSet() { if (mInitializationTimeoutQueue.hasQueued()) { mInitializationTimeoutQueue.cancel(); } } @VisibleForTesting Duration getInitializationTimeoutDelay() { synchronized (mSharedLock) { return Duration.ofMillis(mInitializationTimeoutQueue.getQueuedDelayMillis()); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy