blob: b4aff3e005ad007662305ba93cb0f1e409a12850 [file] [log] [blame]
/*
* 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 android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.os.Handler;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
import java.time.Duration;
import java.util.Objects;
/**
* An base class for the component responsible handling events from {@link
* LocationTimeZoneProvider}s and synthesizing time zone ID suggestions for sending to the time zone
* detector. This interface primarily exists to extract testable detection logic, i.e. with
* a minimal number of threading considerations or dependencies on Android infrastructure.
*
* <p>The controller interacts with the following components:
* <ul>
* <li>The surrounding service, which calls {@link #initialize(Environment, Callback)} and
* {@link #onConfigChanged()}.</li>
* <li>The {@link Environment} through which obtains information it needs.</li>
* <li>The {@link Callback} through which it makes time zone suggestions.</li>
* <li>Any {@link LocationTimeZoneProvider} instances it owns, which communicate via the
* {@link LocationTimeZoneProvider.ProviderListener#onProviderStateChange(ProviderState)}
* method.</li>
* </ul>
*
* <p>All incoming calls except for {@link
* LocationTimeZoneProviderController#dump(android.util.IndentingPrintWriter, String[])} must be
* made on the {@link Handler} thread of the {@link ThreadingDomain} passed to {@link
* #LocationTimeZoneProviderController(ThreadingDomain)}.
*
* <p>Provider / controller integration notes:
*
* <p>Providers distinguish between "unknown unknowns" ("uncertain") and "known unknowns"
* ("certain"), i.e. a provider can be uncertain and not know what the time zone is, which is
* different from the certainty that there are no time zone IDs for the current location. A provider
* can be certain about there being no time zone IDs for a location for good reason, e.g. for
* disputed areas and oceans. Distinguishing uncertainty allows the controller to try other
* providers (or give up), where as certainty means it should not.
*
* <p>A provider can fail permanently. A permanent failure will stop the provider until next
* boot.
*/
abstract class LocationTimeZoneProviderController implements Dumpable {
@NonNull protected final ThreadingDomain mThreadingDomain;
@NonNull protected final Object mSharedLock;
LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
mSharedLock = threadingDomain.getLockObject();
}
/**
* Called to initialize the controller during boot. Called once only.
* {@link LocationTimeZoneProvider#initialize} must be called by this method.
*/
abstract void initialize(@NonNull Environment environment, @NonNull Callback callback);
/**
* Called when any settings or other device state that affect location-based time zone detection
* have changed. The receiver should call {@link
* Environment#getCurrentUserConfigurationInternal()} to get the current user's config. This
* call must be made on the {@link ThreadingDomain} handler thread.
*/
abstract void onConfigChanged();
@VisibleForTesting
abstract boolean isUncertaintyTimeoutSet();
@VisibleForTesting
@DurationMillisLong
abstract long getUncertaintyTimeoutDelayMillis();
/** Called if the geolocation time zone detection is being reconfigured. */
abstract void destroy();
/**
* Used by {@link LocationTimeZoneProviderController} to obtain information from the surrounding
* service. It can easily be faked for tests.
*/
abstract static class Environment {
@NonNull protected final ThreadingDomain mThreadingDomain;
@NonNull protected final Object mSharedLock;
Environment(@NonNull ThreadingDomain threadingDomain) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
mSharedLock = threadingDomain.getLockObject();
}
/** Destroys the environment, i.e. deregisters listeners, etc. */
abstract void destroy();
/** Returns the {@link ConfigurationInternal} for the current user of the device. */
abstract ConfigurationInternal getCurrentUserConfigurationInternal();
/**
* Returns the value passed to LocationTimeZoneProviders informing them of how long they
* have to return their first time zone suggestion.
*/
abstract Duration getProviderInitializationTimeout();
/**
* Returns the extra time granted on top of {@link #getProviderInitializationTimeout()} to
* allow for slop like communication delays.
*/
abstract Duration getProviderInitializationTimeoutFuzz();
/**
* Returns the delay allowed after receiving uncertainty from a provider before it should be
* passed on.
*/
abstract Duration getUncertaintyDelay();
}
/**
* Used by {@link LocationTimeZoneProviderController} to interact with the surrounding service.
* It can easily be faked for tests.
*/
abstract static class Callback {
@NonNull protected final ThreadingDomain mThreadingDomain;
@NonNull protected final Object mSharedLock;
Callback(@NonNull ThreadingDomain threadingDomain) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
mSharedLock = threadingDomain.getLockObject();
}
/**
* Suggests the latest time zone state for the device.
*/
abstract void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion);
}
}