Merge "Add start / stop to location_time_zone_manager"
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package com.android.server.location.timezone;
|
||||
|
||||
import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
|
||||
@@ -71,6 +72,11 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDestroy() {
|
||||
mProxy.destroy();
|
||||
}
|
||||
|
||||
private void handleProviderLost(String reason) {
|
||||
mThreadingDomain.assertCurrentThread();
|
||||
|
||||
@@ -100,11 +106,12 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
|
||||
+ ": No state change required, provider is stopped.");
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
case PROVIDER_STATE_PERM_FAILED:
|
||||
case PROVIDER_STATE_DESTROYED: {
|
||||
debugLog("handleProviderLost reason=" + reason
|
||||
+ ", mProviderName=" + mProviderName
|
||||
+ ", currentState=" + currentState
|
||||
+ ": No state change required, provider is perm failed.");
|
||||
+ ": No state change required, provider is terminated.");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -132,11 +139,12 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
|
||||
+ ", currentState=" + currentState + ": Provider is stopped.");
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
case PROVIDER_STATE_PERM_FAILED:
|
||||
case PROVIDER_STATE_DESTROYED: {
|
||||
debugLog("handleOnProviderBound"
|
||||
+ ", mProviderName=" + mProviderName
|
||||
+ ", currentState=" + currentState
|
||||
+ ": No state change required, provider is perm failed.");
|
||||
+ ": No state change required, provider is terminated.");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.location.timezone;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.timezonedetector.ConfigurationChangeListener;
|
||||
import com.android.server.timezonedetector.ConfigurationInternal;
|
||||
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
|
||||
|
||||
@@ -37,6 +38,7 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir
|
||||
|
||||
@NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
|
||||
@NonNull private final LocationTimeZoneProviderController mController;
|
||||
@NonNull private final ConfigurationChangeListener mConfigurationChangeListener;
|
||||
|
||||
ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
|
||||
@NonNull LocationTimeZoneProviderController controller) {
|
||||
@@ -45,8 +47,14 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir
|
||||
mTimeZoneDetectorInternal = LocalServices.getService(TimeZoneDetectorInternal.class);
|
||||
|
||||
// Listen for configuration changes.
|
||||
mTimeZoneDetectorInternal.addConfigurationListener(
|
||||
() -> mThreadingDomain.post(mController::onConfigChanged));
|
||||
mConfigurationChangeListener = () -> mThreadingDomain.post(mController::onConfigChanged);
|
||||
mTimeZoneDetectorInternal.addConfigurationListener(mConfigurationChangeListener);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void destroy() {
|
||||
mTimeZoneDetectorInternal.removeConfigurationListener(mConfigurationChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.location.timezone;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
|
||||
@@ -151,6 +152,23 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
return mUncertaintyTimeoutQueue.getQueuedDelayMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
void destroy() {
|
||||
mThreadingDomain.assertCurrentThread();
|
||||
|
||||
synchronized (mSharedLock) {
|
||||
stopProviders();
|
||||
mPrimaryProvider.destroy();
|
||||
mSecondaryProvider.destroy();
|
||||
|
||||
// If the controller has made a "certain" suggestion, it should make an uncertain
|
||||
// suggestion to cancel it.
|
||||
if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
|
||||
makeSuggestion(createUncertainSuggestion("Controller is destroyed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mSharedLock")
|
||||
private void stopProviders() {
|
||||
stopProviderIfStarted(mPrimaryProvider);
|
||||
@@ -182,8 +200,9 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
provider.stopUpdates();
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
debugLog("Unable to stop " + provider + ": it is perm failed");
|
||||
case PROVIDER_STATE_PERM_FAILED:
|
||||
case PROVIDER_STATE_DESTROYED: {
|
||||
debugLog("Unable to stop " + provider + ": it is terminated.");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -285,8 +304,9 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
debugLog("No need to start " + provider + ": already started");
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
debugLog("Unable to start " + provider + ": it is perm failed");
|
||||
case PROVIDER_STATE_PERM_FAILED:
|
||||
case PROVIDER_STATE_DESTROYED: {
|
||||
debugLog("Unable to start " + provider + ": it is terminated");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -303,17 +323,20 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
|
||||
synchronized (mSharedLock) {
|
||||
switch (providerState.stateEnum) {
|
||||
case PROVIDER_STATE_STOPPED: {
|
||||
// This should never happen: entering stopped does not trigger a state change.
|
||||
warnLog("onProviderStateChange: Unexpected state change for stopped provider,"
|
||||
case PROVIDER_STATE_STARTED_INITIALIZING:
|
||||
case PROVIDER_STATE_STOPPED:
|
||||
case PROVIDER_STATE_DESTROYED: {
|
||||
// This should never happen: entering initializing, stopped or destroyed are
|
||||
// triggered by the controller so and should not trigger a state change
|
||||
// callback.
|
||||
warnLog("onProviderStateChange: Unexpected state change for provider,"
|
||||
+ " provider=" + provider);
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_STARTED_INITIALIZING:
|
||||
case PROVIDER_STATE_STARTED_CERTAIN:
|
||||
case PROVIDER_STATE_STARTED_UNCERTAIN: {
|
||||
// Entering started does not trigger a state change, so this only happens if an
|
||||
// event is received while the provider is started.
|
||||
// These are valid and only happen if an event is received while the provider is
|
||||
// started.
|
||||
debugLog("onProviderStateChange: Received notification of a state change while"
|
||||
+ " started, provider=" + provider);
|
||||
handleProviderStartedStateChange(providerState);
|
||||
@@ -349,17 +372,18 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
|
||||
// If a provider has failed, the other may need to be started.
|
||||
if (failedProvider == mPrimaryProvider) {
|
||||
if (secondaryCurrentState.stateEnum != PROVIDER_STATE_PERM_FAILED) {
|
||||
// The primary must have failed. Try to start the secondary. This does nothing if
|
||||
// the provider is already started, and will leave the provider in
|
||||
// {started initializing} if the provider is stopped.
|
||||
if (!secondaryCurrentState.isTerminated()) {
|
||||
// Try to start the secondary. This does nothing if the provider is already
|
||||
// started, and will leave the provider in {started initializing} if the provider is
|
||||
// stopped.
|
||||
tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration);
|
||||
}
|
||||
} else if (failedProvider == mSecondaryProvider) {
|
||||
// No-op: The secondary will only be active if the primary is uncertain or is failed.
|
||||
// So, there the primary should not need to be started when the secondary fails.
|
||||
// No-op: The secondary will only be active if the primary is uncertain or is
|
||||
// terminated. So, there the primary should not need to be started when the secondary
|
||||
// fails.
|
||||
if (primaryCurrentState.stateEnum != PROVIDER_STATE_STARTED_UNCERTAIN
|
||||
&& primaryCurrentState.stateEnum != PROVIDER_STATE_PERM_FAILED) {
|
||||
&& !primaryCurrentState.isTerminated()) {
|
||||
warnLog("Secondary provider unexpected reported a failure:"
|
||||
+ " failed provider=" + failedProvider.getName()
|
||||
+ ", primary provider=" + mPrimaryProvider
|
||||
@@ -367,19 +391,18 @@ class ControllerImpl extends LocationTimeZoneProviderController {
|
||||
}
|
||||
}
|
||||
|
||||
// If both providers are now failed, the controller needs to tell the next component in the
|
||||
// time zone detection process.
|
||||
if (primaryCurrentState.stateEnum == PROVIDER_STATE_PERM_FAILED
|
||||
&& secondaryCurrentState.stateEnum == PROVIDER_STATE_PERM_FAILED) {
|
||||
// If both providers are now terminated, the controller needs to tell the next component in
|
||||
// the time zone detection process.
|
||||
if (primaryCurrentState.isTerminated() && secondaryCurrentState.isTerminated()) {
|
||||
|
||||
// If both providers are newly failed then the controller is uncertain by definition
|
||||
// If both providers are newly terminated then the controller is uncertain by definition
|
||||
// and it will never recover so it can send a suggestion immediately.
|
||||
cancelUncertaintyTimeout();
|
||||
|
||||
// If both providers are now failed, then a suggestion must be sent informing the time
|
||||
// zone detector that there are no further updates coming in future.
|
||||
// If both providers are now terminated, then a suggestion must be sent informing the
|
||||
// time zone detector that there are no further updates coming in future.
|
||||
GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
|
||||
"Both providers are permanently failed:"
|
||||
"Both providers are terminated:"
|
||||
+ " primary=" + primaryCurrentState.provider
|
||||
+ ", secondary=" + secondaryCurrentState.provider);
|
||||
makeSuggestion(suggestion);
|
||||
|
||||
@@ -21,6 +21,10 @@ import android.annotation.NonNull;
|
||||
import android.os.Handler;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* The real implementation of {@link ThreadingDomain} that uses a {@link Handler}.
|
||||
@@ -57,6 +61,38 @@ final class HandlerThreadingDomain extends ThreadingDomain {
|
||||
getHandler().post(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
<V> V postAndWait(@NonNull Callable<V> callable, @DurationMillisLong long durationMillis)
|
||||
throws Exception {
|
||||
// Calling this on this domain's thread would lead to deadlock.
|
||||
assertNotCurrentThread();
|
||||
|
||||
AtomicReference<V> resultReference = new AtomicReference<>();
|
||||
AtomicReference<Exception> exceptionReference = new AtomicReference<>();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
post(() -> {
|
||||
try {
|
||||
resultReference.set(callable.call());
|
||||
} catch (Exception e) {
|
||||
exceptionReference.set(e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
if (!latch.await(durationMillis, TimeUnit.MILLISECONDS)) {
|
||||
throw new RuntimeException("Timed out");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (exceptionReference.get() != null) {
|
||||
throw exceptionReference.get();
|
||||
}
|
||||
return resultReference.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
void postDelayed(@NonNull Runnable r, @DurationMillisLong long delayMillis) {
|
||||
getHandler().postDelayed(r, delayMillis);
|
||||
|
||||
@@ -153,10 +153,14 @@ public class LocationTimeZoneManagerService extends Binder {
|
||||
/** The shared lock from {@link #mThreadingDomain}. */
|
||||
@NonNull private final Object mSharedLock;
|
||||
|
||||
// Lazily initialized. Non-null and effectively final after onSystemThirdPartyAppsCanStart().
|
||||
// Lazily initialized. Can be null if the service has been stopped.
|
||||
@GuardedBy("mSharedLock")
|
||||
private ControllerImpl mLocationTimeZoneDetectorController;
|
||||
|
||||
// Lazily initialized. Can be null if the service has been stopped.
|
||||
@GuardedBy("mSharedLock")
|
||||
private ControllerEnvironmentImpl mEnvironment;
|
||||
|
||||
LocationTimeZoneManagerService(Context context) {
|
||||
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
|
||||
mHandler = FgThread.getHandler();
|
||||
@@ -178,31 +182,59 @@ public class LocationTimeZoneManagerService extends Binder {
|
||||
}
|
||||
|
||||
void onSystemThirdPartyAppsCanStart() {
|
||||
// Called on an arbitrary thread during initialization.
|
||||
synchronized (mSharedLock) {
|
||||
LocationTimeZoneProvider primary = createPrimaryProvider();
|
||||
LocationTimeZoneProvider secondary = createSecondaryProvider();
|
||||
mLocationTimeZoneDetectorController =
|
||||
new ControllerImpl(mThreadingDomain, primary, secondary);
|
||||
ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
|
||||
ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl(
|
||||
mThreadingDomain, mLocationTimeZoneDetectorController);
|
||||
// Called on an arbitrary thread during initialization. We do not want to wait for
|
||||
// completion as it would delay boot.
|
||||
final boolean waitForCompletion = false;
|
||||
startInternal(waitForCompletion);
|
||||
}
|
||||
|
||||
// Initialize the controller on the mThreadingDomain thread: this ensures that the
|
||||
// ThreadingDomain requirements for the controller / environment methods are honored.
|
||||
mThreadingDomain.post(() ->
|
||||
mLocationTimeZoneDetectorController.initialize(environment, callback));
|
||||
/**
|
||||
* Starts the service during server initialization or during tests after a call to
|
||||
* {@link #stop()}.
|
||||
*/
|
||||
void start() {
|
||||
enforceManageTimeZoneDetectorPermission();
|
||||
|
||||
final boolean waitForCompletion = true;
|
||||
startInternal(waitForCompletion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the service during server initialization or during tests after a call to
|
||||
* {@link #stop()}.
|
||||
*
|
||||
* <p>To avoid tests needing to sleep, when {@code waitForCompletion} is {@code true}, this
|
||||
* method will not return until all the system server components have started.
|
||||
*/
|
||||
private void startInternal(boolean waitForCompletion) {
|
||||
Runnable runnable = () -> {
|
||||
synchronized (mSharedLock) {
|
||||
if (mLocationTimeZoneDetectorController == null) {
|
||||
LocationTimeZoneProvider primary = createPrimaryProvider();
|
||||
LocationTimeZoneProvider secondary = createSecondaryProvider();
|
||||
mLocationTimeZoneDetectorController =
|
||||
new ControllerImpl(mThreadingDomain, primary, secondary);
|
||||
ControllerCallbackImpl callback = new ControllerCallbackImpl(
|
||||
mThreadingDomain);
|
||||
mEnvironment = new ControllerEnvironmentImpl(
|
||||
mThreadingDomain, mLocationTimeZoneDetectorController);
|
||||
mLocationTimeZoneDetectorController.initialize(mEnvironment, callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (waitForCompletion) {
|
||||
mThreadingDomain.postAndWait(runnable, BLOCKING_OP_WAIT_DURATION_MILLIS);
|
||||
} else {
|
||||
mThreadingDomain.post(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private LocationTimeZoneProvider createPrimaryProvider() {
|
||||
if (isDisabled(PRIMARY_PROVIDER_NAME)) {
|
||||
return new NullLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME);
|
||||
}
|
||||
|
||||
LocationTimeZoneProviderProxy proxy;
|
||||
if (isInSimulationMode(PRIMARY_PROVIDER_NAME)) {
|
||||
proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
|
||||
} else if (isDisabled(PRIMARY_PROVIDER_NAME)) {
|
||||
proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
|
||||
} else {
|
||||
proxy = new RealLocationTimeZoneProviderProxy(
|
||||
mContext,
|
||||
@@ -217,13 +249,11 @@ public class LocationTimeZoneManagerService extends Binder {
|
||||
}
|
||||
|
||||
private LocationTimeZoneProvider createSecondaryProvider() {
|
||||
if (isDisabled(SECONDARY_PROVIDER_NAME)) {
|
||||
return new NullLocationTimeZoneProvider(mThreadingDomain, SECONDARY_PROVIDER_NAME);
|
||||
}
|
||||
|
||||
LocationTimeZoneProviderProxy proxy;
|
||||
if (isInSimulationMode(SECONDARY_PROVIDER_NAME)) {
|
||||
proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
|
||||
} else if (isDisabled(SECONDARY_PROVIDER_NAME)) {
|
||||
proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
|
||||
} else {
|
||||
proxy = new RealLocationTimeZoneProviderProxy(
|
||||
mContext,
|
||||
@@ -274,6 +304,25 @@ public class LocationTimeZoneManagerService extends Binder {
|
||||
return Objects.equals(systemPropertyProviderMode, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the service for tests. To avoid tests needing to sleep, this method will not return
|
||||
* until all the system server components have stopped.
|
||||
*/
|
||||
void stop() {
|
||||
enforceManageTimeZoneDetectorPermission();
|
||||
|
||||
mThreadingDomain.postAndWait(() -> {
|
||||
synchronized (mSharedLock) {
|
||||
if (mLocationTimeZoneDetectorController != null) {
|
||||
mLocationTimeZoneDetectorController.destroy();
|
||||
mLocationTimeZoneDetectorController = null;
|
||||
mEnvironment.destroy();
|
||||
mEnvironment = null;
|
||||
}
|
||||
}
|
||||
}, BLOCKING_OP_WAIT_DURATION_MILLIS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShellCommand(FileDescriptor in, FileDescriptor out,
|
||||
FileDescriptor err, String[] args, ShellCallback callback,
|
||||
@@ -335,7 +384,7 @@ public class LocationTimeZoneManagerService extends Binder {
|
||||
ipw.println("LocationTimeZoneManagerService:");
|
||||
ipw.increaseIndent();
|
||||
if (mLocationTimeZoneDetectorController == null) {
|
||||
ipw.println("{Uninitialized}");
|
||||
ipw.println("{Stopped}");
|
||||
} else {
|
||||
mLocationTimeZoneDetectorController.dump(ipw, args);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
|
||||
private static final List<String> VALID_PROVIDER_NAMES =
|
||||
Arrays.asList(PRIMARY_PROVIDER_NAME, SECONDARY_PROVIDER_NAME);
|
||||
|
||||
private static final String CMD_START = "start";
|
||||
private static final String CMD_STOP = "stop";
|
||||
private static final String CMD_SEND_PROVIDER_TEST_COMMAND = "send_provider_test_command";
|
||||
|
||||
private final LocationTimeZoneManagerService mService;
|
||||
@@ -47,6 +49,12 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case CMD_START: {
|
||||
return runStart();
|
||||
}
|
||||
case CMD_STOP: {
|
||||
return runStop();
|
||||
}
|
||||
case CMD_SEND_PROVIDER_TEST_COMMAND: {
|
||||
return runSendProviderTestCommand();
|
||||
}
|
||||
@@ -62,7 +70,12 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
|
||||
pw.println("Location Time Zone Manager (location_time_zone_manager) commands:");
|
||||
pw.println(" help");
|
||||
pw.println(" Print this help text.");
|
||||
pw.printf(" %s <provider name> <test command>\n", CMD_SEND_PROVIDER_TEST_COMMAND);
|
||||
pw.printf(" %s\n", CMD_START);
|
||||
pw.println(" Starts the location_time_zone_manager, creating time zone providers.");
|
||||
pw.printf(" %s\n", CMD_STOP);
|
||||
pw.println(" Stops the location_time_zone_manager, destroying time zone providers.");
|
||||
pw.printf(" %s <provider name> <test command>\n",
|
||||
CMD_SEND_PROVIDER_TEST_COMMAND);
|
||||
pw.println(" Passes a test command to the named provider.");
|
||||
pw.println();
|
||||
pw.printf("%s details:\n", CMD_SEND_PROVIDER_TEST_COMMAND);
|
||||
@@ -91,6 +104,30 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
|
||||
pw.println();
|
||||
}
|
||||
|
||||
private int runStart() {
|
||||
try {
|
||||
mService.start();
|
||||
} catch (RuntimeException e) {
|
||||
reportError(e);
|
||||
return 1;
|
||||
}
|
||||
PrintWriter outPrintWriter = getOutPrintWriter();
|
||||
outPrintWriter.println("Service started");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int runStop() {
|
||||
try {
|
||||
mService.stop();
|
||||
} catch (RuntimeException e) {
|
||||
reportError(e);
|
||||
return 1;
|
||||
}
|
||||
PrintWriter outPrintWriter = getOutPrintWriter();
|
||||
outPrintWriter.println("Service stopped");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int runSendProviderTestCommand() {
|
||||
PrintWriter outPrintWriter = getOutPrintWriter();
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESU
|
||||
|
||||
import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
|
||||
@@ -52,8 +53,7 @@ import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A facade used by the {@link LocationTimeZoneProviderController} to interact with a location time
|
||||
* zone provider. The provider could have a binder implementation with logic running in another
|
||||
* process, or could be a stubbed instance when no real provider is registered.
|
||||
* zone provider. The provider implementation will typically have logic running in another process.
|
||||
*
|
||||
* <p>The provider is supplied with a {@link ProviderListener} via {@link
|
||||
* #initialize(ProviderListener)}. This starts communication of asynchronous detection / error
|
||||
@@ -61,6 +61,10 @@ import java.util.Objects;
|
||||
* ProviderListener#onProviderStateChange} method. This call must be made on the
|
||||
* {@link Handler} thread from the {@link ThreadingDomain} passed to the constructor.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
@@ -86,7 +90,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
@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_STOPPED, PROVIDER_STATE_PERM_FAILED, PROVIDER_STATE_DESTROYED })
|
||||
@interface ProviderStateEnum {}
|
||||
|
||||
/**
|
||||
@@ -117,12 +121,19 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
static final int PROVIDER_STATE_STOPPED = 4;
|
||||
|
||||
/**
|
||||
* The provider has failed and cannot be re-started.
|
||||
* The provider has failed and cannot be restarted. This is a terminated state triggered by
|
||||
* the provider itself.
|
||||
*
|
||||
* Providers may enter this state after a provider is started.
|
||||
* Providers may enter this state any time after a provider is started.
|
||||
*/
|
||||
static final 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 int PROVIDER_STATE_DESTROYED = 6;
|
||||
|
||||
/** The {@link LocationTimeZoneProvider} the state is for. */
|
||||
public final @NonNull LocationTimeZoneProvider provider;
|
||||
|
||||
@@ -201,12 +212,14 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
case PROVIDER_STATE_STARTED_INITIALIZING:
|
||||
case PROVIDER_STATE_STARTED_CERTAIN:
|
||||
case PROVIDER_STATE_STARTED_UNCERTAIN: {
|
||||
// These can go to each other or PROVIDER_STATE_PERM_FAILED.
|
||||
// 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_PERM_FAILED:
|
||||
case PROVIDER_STATE_DESTROYED: {
|
||||
throw new IllegalArgumentException("Illegal transition out of "
|
||||
+ prettyPrintStateEnum(PROVIDER_STATE_UNKNOWN));
|
||||
+ prettyPrintStateEnum(this.stateEnum));
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException("Invalid this.stateEnum=" + this.stateEnum);
|
||||
@@ -237,10 +250,12 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
case PROVIDER_STATE_PERM_FAILED:
|
||||
case PROVIDER_STATE_DESTROYED: {
|
||||
if (event != null || currentUserConfig != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Perf failed state: event and currentUserConfig must be null"
|
||||
"Terminal state: event and currentUserConfig must be null"
|
||||
+ ", newStateEnum=" + newStateEnum
|
||||
+ ", event=" + event
|
||||
+ ", currentUserConfig=" + currentUserConfig);
|
||||
}
|
||||
@@ -260,6 +275,12 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
|| 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
|
||||
@@ -304,6 +325,8 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
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 + ")";
|
||||
@@ -340,7 +363,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before the provider is first used.
|
||||
* Initializes the provider. Called before the provider is first used.
|
||||
*/
|
||||
final void initialize(@NonNull ProviderListener providerListener) {
|
||||
mThreadingDomain.assertCurrentThread();
|
||||
@@ -372,8 +395,33 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
/**
|
||||
* 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() called");
|
||||
setCurrentState(destroyedState, false);
|
||||
onDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented by subclasses to do work during {@link #destroy()}.
|
||||
*/
|
||||
@GuardedBy("mSharedLock")
|
||||
abstract void onDestroy();
|
||||
|
||||
/**
|
||||
* Set the current state, for use by this class and subclasses only. If {@code #notifyChanges}
|
||||
* is {@code true} and {@code newState} is not equal to the old state, then {@link
|
||||
@@ -460,17 +508,23 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
PROVIDER_STATE_STARTED_UNCERTAIN, null /* event */,
|
||||
currentState.currentUserConfiguration, "initialization timeout");
|
||||
setCurrentState(newState, true);
|
||||
} else {
|
||||
warnLog("handleInitializationTimeout: Initialization timeout triggered when in"
|
||||
+ " an unexpected state=" + currentState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented by subclasses to do work during {@link #startUpdates}.
|
||||
* 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 real provider
|
||||
*/
|
||||
abstract void onStartUpdates(@NonNull Duration initializationTimeout);
|
||||
|
||||
/**
|
||||
* Stops the provider. It is an error* to call this method except when the {@link
|
||||
* 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}.
|
||||
*/
|
||||
@@ -505,6 +559,8 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
* {@code false} and a "Not implemented" error message.
|
||||
*/
|
||||
void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) {
|
||||
Objects.requireNonNull(testCommand);
|
||||
|
||||
if (callback != null) {
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY, false);
|
||||
@@ -525,11 +581,12 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
int eventType = timeZoneProviderEvent.getType();
|
||||
switch (currentState.stateEnum) {
|
||||
case PROVIDER_STATE_DESTROYED:
|
||||
case PROVIDER_STATE_PERM_FAILED: {
|
||||
// After entering perm failed, there is nothing to do. The remote peer is
|
||||
// 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 failed state");
|
||||
+ " received for provider=" + this + " when in terminated state");
|
||||
return;
|
||||
}
|
||||
case PROVIDER_STATE_STOPPED: {
|
||||
|
||||
@@ -94,6 +94,9 @@ abstract class LocationTimeZoneProviderController implements Dumpable {
|
||||
@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.
|
||||
@@ -108,6 +111,9 @@ abstract class LocationTimeZoneProviderController implements Dumpable {
|
||||
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();
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ abstract class LocationTimeZoneProviderProxy implements Dumpable {
|
||||
|
||||
/**
|
||||
* Initializes the proxy. The supplied listener can expect to receive all events after this
|
||||
* point. This method also calls {@link #onInitialize()} for subclasses to handle their own
|
||||
* point. This method calls {@link #onInitialize()} for subclasses to handle their own
|
||||
* initialization.
|
||||
*/
|
||||
void initialize(@NonNull Listener listener) {
|
||||
@@ -85,10 +85,28 @@ abstract class LocationTimeZoneProviderProxy implements Dumpable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the proxy. This is called after {@link #mListener} is set.
|
||||
* Implemented by subclasses to initializes the proxy. This is called after {@link #mListener}
|
||||
* is set.
|
||||
*/
|
||||
@GuardedBy("mSharedLock")
|
||||
abstract void onInitialize();
|
||||
|
||||
/**
|
||||
* Destroys the proxy. This method calls {@link #onDestroy()} for subclasses to handle their own
|
||||
* destruction.
|
||||
*/
|
||||
void destroy() {
|
||||
synchronized (mSharedLock) {
|
||||
onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented by subclasses to destroy the proxy.
|
||||
*/
|
||||
@GuardedBy("mSharedLock")
|
||||
abstract void onDestroy();
|
||||
|
||||
/**
|
||||
* Sets a new request for the provider.
|
||||
*/
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* 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.location.timezone;
|
||||
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.IndentingPrintWriter;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* A {@link LocationTimeZoneProvider} that provides minimal responses needed for the {@link
|
||||
* LocationTimeZoneProviderController} to operate correctly when there is no "real" provider
|
||||
* configured. This can be used during development / testing, or in a production build when the
|
||||
* platform supports more providers than are needed for an Android deployment.
|
||||
*
|
||||
* <p>For example, if the {@link LocationTimeZoneProviderController} supports a primary
|
||||
* and a secondary {@link LocationTimeZoneProvider}, but only a primary is configured, the secondary
|
||||
* config will be left null and the {@link LocationTimeZoneProvider} implementation will be
|
||||
* defaulted to a {@link NullLocationTimeZoneProvider}. The {@link NullLocationTimeZoneProvider}
|
||||
* enters a {@link ProviderState#PROVIDER_STATE_PERM_FAILED} state immediately after being started
|
||||
* for the first time and sends the appropriate event, which ensures the {@link
|
||||
* LocationTimeZoneProviderController} won't expect any further {@link
|
||||
* TimeZoneProviderEvent}s to come from it, and won't attempt to use it
|
||||
* again.
|
||||
*/
|
||||
class NullLocationTimeZoneProvider extends LocationTimeZoneProvider {
|
||||
|
||||
private static final String TAG = "NullLocationTimeZoneProvider";
|
||||
|
||||
/** Creates the instance. */
|
||||
NullLocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
|
||||
@NonNull String providerName) {
|
||||
super(threadingDomain, providerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onInitialize() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
void onStartUpdates(@NonNull Duration initializationTimeout) {
|
||||
// Report a failure (asynchronously using the mThreadingDomain thread to avoid recursion).
|
||||
mThreadingDomain.post(()-> {
|
||||
// Enter the perm-failed state.
|
||||
ProviderState currentState = mCurrentState.get();
|
||||
ProviderState failedState = currentState.newState(
|
||||
PROVIDER_STATE_PERM_FAILED, null, null, "Stubbed provider");
|
||||
setCurrentState(failedState, true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
void onStopUpdates() {
|
||||
// Ignored - this implementation is always permanently failed.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
|
||||
synchronized (mSharedLock) {
|
||||
ipw.println("{Stubbed LocationTimeZoneProvider}");
|
||||
ipw.println("mProviderName=" + mProviderName);
|
||||
ipw.println("mCurrentState=" + mCurrentState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
synchronized (mSharedLock) {
|
||||
return "NullLocationTimeZoneProvider{"
|
||||
+ "mProviderName='" + mProviderName + '\''
|
||||
+ ", mCurrentState='" + mCurrentState + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.location.timezone;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteCallback;
|
||||
import android.service.timezone.TimeZoneProviderService;
|
||||
import android.util.IndentingPrintWriter;
|
||||
|
||||
/**
|
||||
* A {@link LocationTimeZoneProviderProxy} that provides minimal responses needed for the {@link
|
||||
* BinderLocationTimeZoneProvider} to operate correctly when there is no "real" provider
|
||||
* configured / enabled. This can be used during development / testing, or in a production build
|
||||
* when the platform supports more providers than are needed for an Android deployment.
|
||||
*
|
||||
* <p>For example, if the {@link LocationTimeZoneProviderController} supports a primary
|
||||
* and a secondary {@link LocationTimeZoneProvider}, but only a primary is configured, the secondary
|
||||
* config will be left null and the {@link LocationTimeZoneProviderProxy} implementation will be
|
||||
* defaulted to a {@link NullLocationTimeZoneProviderProxy}. The {@link
|
||||
* NullLocationTimeZoneProviderProxy} sends a "permanent failure" event immediately after being
|
||||
* started for the first time, which ensures the {@link LocationTimeZoneProviderController} won't
|
||||
* expect any further {@link TimeZoneProviderEvent}s to come from it, and won't attempt to use it
|
||||
* again.
|
||||
*/
|
||||
class NullLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
|
||||
|
||||
/** Creates the instance. */
|
||||
NullLocationTimeZoneProviderProxy(
|
||||
@NonNull Context context, @NonNull ThreadingDomain threadingDomain) {
|
||||
super(context, threadingDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onInitialize() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDestroy() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
void setRequest(@NonNull TimeZoneProviderRequest request) {
|
||||
if (request.sendUpdates()) {
|
||||
TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent(
|
||||
"Provider is disabled");
|
||||
handleTimeZoneProviderEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) {
|
||||
if (callback != null) {
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY, false);
|
||||
result.putString(TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY,
|
||||
"Provider is disabled");
|
||||
callback.sendResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
|
||||
synchronized (mSharedLock) {
|
||||
ipw.println("{NullLocationTimeZoneProviderProxy}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,11 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDestroy() {
|
||||
mServiceWatcher.unregister();
|
||||
}
|
||||
|
||||
private boolean register() {
|
||||
boolean resolves = mServiceWatcher.checkServiceResolves();
|
||||
if (resolves) {
|
||||
@@ -192,6 +197,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
|
||||
@Override
|
||||
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
|
||||
synchronized (mSharedLock) {
|
||||
ipw.println("{RealLocationTimeZoneProviderProxy}");
|
||||
ipw.println("mRequest=" + mRequest);
|
||||
mServiceWatcher.dump(null, ipw, args);
|
||||
}
|
||||
|
||||
@@ -69,6 +69,11 @@ class SimulatedLocationTimeZoneProviderProxy extends LocationTimeZoneProviderPro
|
||||
// No-op - nothing to do for the simulated provider.
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDestroy() {
|
||||
// No-op - nothing to do for the simulated provider.
|
||||
}
|
||||
|
||||
void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) {
|
||||
mThreadingDomain.assertCurrentThread();
|
||||
|
||||
@@ -152,6 +157,7 @@ class SimulatedLocationTimeZoneProviderProxy extends LocationTimeZoneProviderPro
|
||||
@Override
|
||||
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
|
||||
synchronized (mSharedLock) {
|
||||
ipw.println("{SimulatedLocationTimeZoneProviderProxy}");
|
||||
ipw.println("mRequest=" + mRequest);
|
||||
ipw.println("mLastEvent=" + mLastEvent);
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ShellCommand;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
@@ -50,6 +52,12 @@ final class TestCommand {
|
||||
mArgs = Objects.requireNonNull(args);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@NonNull
|
||||
public static TestCommand createForTests(@NonNull String type, @NonNull Bundle args) {
|
||||
return new TestCommand(type, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TestCommand} from a {@link ShellCommand}'s remaining arguments.
|
||||
*
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.annotation.NonNull;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* A class that can be used to enforce / indicate a set of components that need to share threading
|
||||
* behavior such as a shared lock object and a common thread, with async execution support.
|
||||
@@ -58,7 +60,7 @@ abstract class ThreadingDomain {
|
||||
* being used within a set of components, a lot of races can be avoided.
|
||||
*/
|
||||
void assertCurrentThread() {
|
||||
Preconditions.checkArgument(Thread.currentThread() == getThread());
|
||||
Preconditions.checkState(Thread.currentThread() == getThread());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +68,7 @@ abstract class ThreadingDomain {
|
||||
* Generally useful for documenting expectations in the code and avoiding deadlocks.
|
||||
*/
|
||||
void assertNotCurrentThread() {
|
||||
Preconditions.checkArgument(Thread.currentThread() != getThread());
|
||||
Preconditions.checkState(Thread.currentThread() != getThread());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,6 +76,37 @@ abstract class ThreadingDomain {
|
||||
*/
|
||||
abstract void post(@NonNull Runnable runnable);
|
||||
|
||||
/**
|
||||
* Executes the supplied runnable and waits for up to the duration specified for it to be
|
||||
* executed. This is only intended for use by test and/or shell command code as it consumes
|
||||
* multiple threads and could lead to deadlocks.
|
||||
*
|
||||
* <p>An {@link IllegalStateException} will be thrown if calling this method would cause a
|
||||
* deadlock, e.g. if it is called using the threading domain's own thread.
|
||||
*/
|
||||
final void postAndWait(@NonNull Runnable runnable, @DurationMillisLong long durationMillis) {
|
||||
try {
|
||||
postAndWait(() -> {
|
||||
runnable.run();
|
||||
return null;
|
||||
}, durationMillis);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied callable and waits for up to the duration specified for it to be
|
||||
* executed. This is only intended for use by test and/or shell command code as it consumes
|
||||
* multiple threads and could lead to deadlocks.
|
||||
*
|
||||
* <p>An {@link IllegalStateException} will be thrown if calling this method would cause a
|
||||
* deadlock, e.g. if it is called using the threading domain's own thread.
|
||||
*/
|
||||
abstract <V> V postAndWait(
|
||||
@NonNull Callable<V> callable, @DurationMillisLong long durationMillis)
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* Execute the supplied runnable on the threading domain's thread with a delay.
|
||||
*/
|
||||
|
||||
@@ -30,6 +30,12 @@ public interface TimeZoneDetectorInternal extends Dumpable.Container {
|
||||
/** Adds a listener that will be invoked when time zone detection configuration is changed. */
|
||||
void addConfigurationListener(ConfigurationChangeListener listener);
|
||||
|
||||
/**
|
||||
* Removes a listener previously added via {@link
|
||||
* #addConfigurationListener(ConfigurationChangeListener)}.
|
||||
*/
|
||||
void removeConfigurationListener(ConfigurationChangeListener listener);
|
||||
|
||||
/** Returns the {@link ConfigurationInternal} for the current user. */
|
||||
ConfigurationInternal getCurrentUserConfigurationInternal();
|
||||
|
||||
|
||||
@@ -68,6 +68,13 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConfigurationListener(ConfigurationChangeListener listener) {
|
||||
synchronized (mConfigurationListeners) {
|
||||
mConfigurationListeners.remove(Objects.requireNonNull(listener));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ConfigurationInternal getCurrentUserConfigurationInternal() {
|
||||
|
||||
@@ -960,6 +960,11 @@ public class ControllerImplTest {
|
||||
mConfigurationInternal = Objects.requireNonNull(configurationInternal);
|
||||
}
|
||||
|
||||
@Override
|
||||
void destroy() {
|
||||
// No-op test impl.
|
||||
}
|
||||
|
||||
@Override
|
||||
ConfigurationInternal getCurrentUserConfigurationInternal() {
|
||||
return mConfigurationInternal;
|
||||
@@ -1024,6 +1029,7 @@ public class ControllerImplTest {
|
||||
/** Used to track historic provider states for tests. */
|
||||
private final TestState<ProviderState> mTestProviderState = new TestState<>();
|
||||
private boolean mInitialized;
|
||||
private boolean mDestroyed;
|
||||
|
||||
/**
|
||||
* Creates the instance.
|
||||
@@ -1037,6 +1043,11 @@ public class ControllerImplTest {
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDestroy() {
|
||||
mDestroyed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onSetCurrentState(ProviderState newState) {
|
||||
mTestProviderState.set(newState);
|
||||
|
||||
@@ -30,10 +30,12 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/** Tests for {@link HandlerThreadingDomain}. */
|
||||
@Presubmit
|
||||
@@ -115,6 +117,7 @@ public class HandlerThreadingDomainTest {
|
||||
});
|
||||
domain.post(testLogic);
|
||||
testLogic.assertCompletesWithin(60, TimeUnit.SECONDS);
|
||||
assertTrue(testLogic.isComplete());
|
||||
assertTrue(ranOnExpectedThread.get());
|
||||
}
|
||||
|
||||
@@ -123,17 +126,65 @@ public class HandlerThreadingDomainTest {
|
||||
ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
|
||||
|
||||
long beforeExecutionNanos = System.nanoTime();
|
||||
Duration executionDelay = Duration.ofSeconds(5);
|
||||
|
||||
AtomicReference<Long> executionNanosHolder = new AtomicReference<>();
|
||||
AtomicBoolean ranOnExpectedThread = new AtomicBoolean(false);
|
||||
LatchedRunnable testLogic = new LatchedRunnable(() -> {
|
||||
ranOnExpectedThread.set(Thread.currentThread() == mTestHandler.getLooper().getThread());
|
||||
executionNanosHolder.set(System.nanoTime());
|
||||
});
|
||||
domain.postDelayed(testLogic, 5000);
|
||||
|
||||
testLogic.assertCompletesWithin(60, TimeUnit.SECONDS);
|
||||
domain.postDelayed(testLogic, executionDelay.toMillis());
|
||||
long afterPostNanos = System.nanoTime();
|
||||
|
||||
testLogic.assertCompletesWithin(
|
||||
executionDelay.multipliedBy(10).toMillis(), TimeUnit.MILLISECONDS);
|
||||
long afterWaitNanos = System.nanoTime();
|
||||
|
||||
assertTrue(testLogic.isComplete());
|
||||
assertTrue(ranOnExpectedThread.get());
|
||||
|
||||
// The execution should not take place until at least delayDuration after postDelayed().
|
||||
Duration actualExecutionDelay =
|
||||
Duration.ofNanos(executionNanosHolder.get() - beforeExecutionNanos);
|
||||
assertTrue(actualExecutionDelay.compareTo(executionDelay) >= 0);
|
||||
|
||||
// The time taken in postDelayed() should be negligible. Certainly less than the
|
||||
// executionDelay.
|
||||
Duration postDuration = Duration.ofNanos(afterPostNanos - beforeExecutionNanos);
|
||||
assertTrue(postDuration.compareTo(executionDelay) < 0);
|
||||
|
||||
// The result should not be ready until at least executionDelay has elapsed.
|
||||
Duration delayBeforeExecuted = Duration.ofNanos(afterWaitNanos - beforeExecutionNanos);
|
||||
assertTrue(delayBeforeExecuted.compareTo(executionDelay) >= 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postAndWait() throws Exception {
|
||||
ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
|
||||
|
||||
Duration workDuration = Duration.ofSeconds(5);
|
||||
AtomicBoolean ranOnExpectedThread = new AtomicBoolean(false);
|
||||
LatchedRunnable testLogic = new LatchedRunnable(() -> {
|
||||
ranOnExpectedThread.set(Thread.currentThread() == mTestHandler.getLooper().getThread());
|
||||
|
||||
// The work takes workDuration to complete.
|
||||
try {
|
||||
Thread.sleep(workDuration.toMillis());
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
});
|
||||
|
||||
long beforeExecutionNanos = System.nanoTime();
|
||||
domain.postAndWait(testLogic, workDuration.multipliedBy(10).toMillis());
|
||||
long afterExecutionNanos = System.nanoTime();
|
||||
assertTrue(afterExecutionNanos - beforeExecutionNanos >= TimeUnit.SECONDS.toNanos(5));
|
||||
Duration waitDuration = Duration.ofNanos(afterExecutionNanos - beforeExecutionNanos);
|
||||
|
||||
assertTrue(waitDuration.compareTo(workDuration) >= 0);
|
||||
assertTrue(testLogic.isComplete());
|
||||
assertTrue(ranOnExpectedThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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.location.timezone;
|
||||
|
||||
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
|
||||
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
|
||||
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
|
||||
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteCallback;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.service.timezone.TimeZoneProviderSuggestion;
|
||||
import android.util.IndentingPrintWriter;
|
||||
|
||||
import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderListener;
|
||||
import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
|
||||
import com.android.server.timezonedetector.ConfigurationInternal;
|
||||
import com.android.server.timezonedetector.TestState;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Tests for {@link LocationTimeZoneProvider}.
|
||||
*/
|
||||
@Presubmit
|
||||
public class LocationTimeZoneProviderTest {
|
||||
|
||||
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 123456789L;
|
||||
|
||||
private TestThreadingDomain mTestThreadingDomain;
|
||||
|
||||
private TestProviderListener mProviderListener;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mTestThreadingDomain = new TestThreadingDomain();
|
||||
mProviderListener = new TestProviderListener();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lifecycle() {
|
||||
String providerName = "arbitrary";
|
||||
TestLocationTimeZoneProvider provider =
|
||||
new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName);
|
||||
|
||||
// initialize()
|
||||
provider.initialize(mProviderListener);
|
||||
provider.assertOnInitializeCalled();
|
||||
|
||||
ProviderState currentState = provider.getCurrentState();
|
||||
assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
|
||||
assertNull(currentState.currentUserConfiguration);
|
||||
assertSame(provider, currentState.provider);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
|
||||
// startUpdates()
|
||||
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
|
||||
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
|
||||
Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
|
||||
provider.startUpdates(config, arbitraryInitializationTimeout,
|
||||
arbitraryInitializationTimeoutFuzz);
|
||||
|
||||
provider.assertOnStartCalled(arbitraryInitializationTimeout);
|
||||
|
||||
currentState = provider.getCurrentState();
|
||||
assertSame(provider, currentState.provider);
|
||||
assertEquals(PROVIDER_STATE_STARTED_INITIALIZING, currentState.stateEnum);
|
||||
assertEquals(config, currentState.currentUserConfiguration);
|
||||
assertNull(currentState.event);
|
||||
// The initialization timeout should be queued.
|
||||
Duration expectedInitializationTimeout =
|
||||
arbitraryInitializationTimeout.plus(arbitraryInitializationTimeoutFuzz);
|
||||
mTestThreadingDomain.assertSingleDelayedQueueItem(expectedInitializationTimeout);
|
||||
// We don't intend to trigger the timeout, so clear it.
|
||||
mTestThreadingDomain.removeAllQueuedRunnables();
|
||||
|
||||
// Entering started does not trigger an onProviderStateChanged() as it is requested by the
|
||||
// controller.
|
||||
mProviderListener.assertProviderChangeNotReported();
|
||||
|
||||
// Simulate a suggestion event being received.
|
||||
TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
|
||||
.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
|
||||
.setTimeZoneIds(Arrays.asList("Europe/London"))
|
||||
.build();
|
||||
TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(suggestion);
|
||||
provider.simulateProviderEventReceived(event);
|
||||
|
||||
currentState = provider.getCurrentState();
|
||||
assertSame(provider, currentState.provider);
|
||||
assertEquals(PROVIDER_STATE_STARTED_CERTAIN, currentState.stateEnum);
|
||||
assertEquals(event, currentState.event);
|
||||
assertEquals(config, currentState.currentUserConfiguration);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_CERTAIN);
|
||||
|
||||
// Simulate an uncertain event being received.
|
||||
event = TimeZoneProviderEvent.createUncertainEvent();
|
||||
provider.simulateProviderEventReceived(event);
|
||||
|
||||
currentState = provider.getCurrentState();
|
||||
assertSame(provider, currentState.provider);
|
||||
assertEquals(PROVIDER_STATE_STARTED_UNCERTAIN, currentState.stateEnum);
|
||||
assertEquals(event, currentState.event);
|
||||
assertEquals(config, currentState.currentUserConfiguration);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_UNCERTAIN);
|
||||
|
||||
// stopUpdates()
|
||||
provider.stopUpdates();
|
||||
provider.assertOnStopUpdatesCalled();
|
||||
|
||||
currentState = provider.getCurrentState();
|
||||
assertSame(provider, currentState.provider);
|
||||
assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
|
||||
assertNull(currentState.event);
|
||||
assertNull(currentState.currentUserConfiguration);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
// Entering stopped does not trigger an onProviderStateChanged() as it is requested by the
|
||||
// controller.
|
||||
mProviderListener.assertProviderChangeNotReported();
|
||||
|
||||
// destroy()
|
||||
provider.destroy();
|
||||
provider.assertOnDestroyCalled();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultHandleTestCommandImpl() {
|
||||
String providerName = "primary";
|
||||
TestLocationTimeZoneProvider provider =
|
||||
new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName);
|
||||
|
||||
TestCommand testCommand = TestCommand.createForTests("test", new Bundle());
|
||||
AtomicReference<Bundle> resultReference = new AtomicReference<>();
|
||||
RemoteCallback callback = new RemoteCallback(resultReference::set);
|
||||
provider.handleTestCommand(testCommand, callback);
|
||||
|
||||
Bundle result = resultReference.get();
|
||||
assertNotNull(result);
|
||||
assertFalse(result.getBoolean(TEST_COMMAND_RESULT_SUCCESS_KEY));
|
||||
assertNotNull(result.getString(TEST_COMMAND_RESULT_ERROR_KEY));
|
||||
}
|
||||
|
||||
/** A test stand-in for the real {@link LocationTimeZoneProviderController}'s listener. */
|
||||
private static class TestProviderListener implements ProviderListener {
|
||||
|
||||
private final TestState<ProviderState> mReportedProviderStateChanges = new TestState<>();
|
||||
|
||||
@Override
|
||||
public void onProviderStateChange(ProviderState providerState) {
|
||||
mReportedProviderStateChanges.set(providerState);
|
||||
}
|
||||
|
||||
void assertProviderChangeReported(int expectedStateEnum) {
|
||||
mReportedProviderStateChanges.assertChangeCount(1);
|
||||
|
||||
ProviderState latest = mReportedProviderStateChanges.getLatest();
|
||||
assertEquals(expectedStateEnum, latest.stateEnum);
|
||||
mReportedProviderStateChanges.commitLatest();
|
||||
}
|
||||
|
||||
public void assertProviderChangeNotReported() {
|
||||
mReportedProviderStateChanges.assertHasNotBeenSet();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestLocationTimeZoneProvider extends LocationTimeZoneProvider {
|
||||
|
||||
private boolean mOnInitializeCalled;
|
||||
private boolean mOnDestroyCalled;
|
||||
private boolean mOnStartUpdatesCalled;
|
||||
private Duration mInitializationTimeout;
|
||||
private boolean mOnStopUpdatesCalled;
|
||||
|
||||
/** Creates the instance. */
|
||||
TestLocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
|
||||
@NonNull String providerName) {
|
||||
super(threadingDomain, providerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onInitialize() {
|
||||
mOnInitializeCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDestroy() {
|
||||
mOnDestroyCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onStartUpdates(@NonNull Duration initializationTimeout) {
|
||||
mOnStartUpdatesCalled = true;
|
||||
mInitializationTimeout = initializationTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onStopUpdates() {
|
||||
mOnStopUpdatesCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
|
||||
// No-op for tests
|
||||
}
|
||||
|
||||
void assertOnInitializeCalled() {
|
||||
assertTrue(mOnInitializeCalled);
|
||||
}
|
||||
|
||||
void assertOnStartCalled(Duration expectedInitializationTimeout) {
|
||||
assertTrue(mOnStartUpdatesCalled);
|
||||
assertEquals(expectedInitializationTimeout, mInitializationTimeout);
|
||||
}
|
||||
|
||||
void simulateProviderEventReceived(TimeZoneProviderEvent event) {
|
||||
handleTimeZoneProviderEvent(event);
|
||||
}
|
||||
|
||||
void assertOnStopUpdatesCalled() {
|
||||
assertTrue(mOnStopUpdatesCalled);
|
||||
}
|
||||
|
||||
void assertOnDestroyCalled() {
|
||||
assertTrue(mOnDestroyCalled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* 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.location.timezone;
|
||||
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
|
||||
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
|
||||
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.IndentingPrintWriter;
|
||||
|
||||
import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
|
||||
import com.android.server.timezonedetector.ConfigurationInternal;
|
||||
import com.android.server.timezonedetector.TestState;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Tests for {@link NullLocationTimeZoneProvider} and, indirectly, the class it extends
|
||||
* {@link LocationTimeZoneProvider}.
|
||||
*/
|
||||
@Presubmit
|
||||
public class NullLocationTimeZoneProviderTest {
|
||||
|
||||
private TestThreadingDomain mTestThreadingDomain;
|
||||
|
||||
private TestController mTestController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mTestThreadingDomain = new TestThreadingDomain();
|
||||
mTestController = new TestController(mTestThreadingDomain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialization() {
|
||||
String providerName = "primary";
|
||||
NullLocationTimeZoneProvider provider =
|
||||
new NullLocationTimeZoneProvider(mTestThreadingDomain, providerName);
|
||||
provider.initialize(providerState -> mTestController.onProviderStateChange(providerState));
|
||||
|
||||
ProviderState currentState = provider.getCurrentState();
|
||||
assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
|
||||
assertNull(currentState.currentUserConfiguration);
|
||||
assertSame(provider, currentState.provider);
|
||||
mTestThreadingDomain.assertQueueEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startSchedulesPermFailure() {
|
||||
String providerName = "primary";
|
||||
NullLocationTimeZoneProvider provider =
|
||||
new NullLocationTimeZoneProvider(mTestThreadingDomain, providerName);
|
||||
provider.initialize(providerState -> mTestController.onProviderStateChange(providerState));
|
||||
|
||||
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
|
||||
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
|
||||
Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
|
||||
provider.startUpdates(config, arbitraryInitializationTimeout,
|
||||
arbitraryInitializationTimeoutFuzz);
|
||||
|
||||
// The NullProvider should enter the enabled state, but have schedule an immediate runnable
|
||||
// to switch to perm failure.
|
||||
ProviderState currentState = provider.getCurrentState();
|
||||
assertSame(provider, currentState.provider);
|
||||
assertEquals(PROVIDER_STATE_STARTED_INITIALIZING, currentState.stateEnum);
|
||||
assertEquals(config, currentState.currentUserConfiguration);
|
||||
mTestThreadingDomain.assertNextQueueItemIsImmediate();
|
||||
// Entering enabled() does not trigger an onProviderStateChanged() as it is requested by the
|
||||
// controller.
|
||||
mTestController.assertProviderChangeNotTriggered();
|
||||
|
||||
// Check the queued runnable causes the provider to go into perm failed state.
|
||||
mTestThreadingDomain.executeNext();
|
||||
|
||||
// Entering perm failed triggers an onProviderStateChanged() as it is asynchronously
|
||||
// triggered.
|
||||
mTestController.assertProviderChangeTriggered(PROVIDER_STATE_PERM_FAILED);
|
||||
}
|
||||
|
||||
/** A test stand-in for the {@link LocationTimeZoneProviderController}. */
|
||||
private static class TestController extends LocationTimeZoneProviderController {
|
||||
|
||||
private TestState<ProviderState> mProviderState = new TestState<>();
|
||||
|
||||
TestController(ThreadingDomain threadingDomain) {
|
||||
super(threadingDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initialize(Environment environment, Callback callback) {
|
||||
// Not needed for provider testing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void onConfigChanged() {
|
||||
// Not needed for provider testing.
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isUncertaintyTimeoutSet() {
|
||||
// Not needed for provider testing.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
long getUncertaintyTimeoutDelayMillis() {
|
||||
// Not needed for provider testing.
|
||||
return 0;
|
||||
}
|
||||
|
||||
void onProviderStateChange(ProviderState providerState) {
|
||||
this.mProviderState.set(providerState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(IndentingPrintWriter pw, String[] args) {
|
||||
// Not needed for provider testing.
|
||||
}
|
||||
|
||||
void assertProviderChangeTriggered(int expectedStateEnum) {
|
||||
assertEquals(expectedStateEnum, mProviderState.getLatest().stateEnum);
|
||||
mProviderState.commitLatest();
|
||||
}
|
||||
|
||||
public void assertProviderChangeNotTriggered() {
|
||||
mProviderState.assertHasNotBeenSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* A ThreadingDomain that simulates idealized post() semantics. Execution takes place in zero time,
|
||||
@@ -78,6 +79,11 @@ class TestThreadingDomain extends ThreadingDomain {
|
||||
postDelayed(r, null, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
<V> V postAndWait(Callable<V> callable, long durationMillis) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
void postDelayed(Runnable r, long delayMillis) {
|
||||
postDelayed(r, null, delayMillis);
|
||||
@@ -94,6 +100,10 @@ class TestThreadingDomain extends ThreadingDomain {
|
||||
mQueue.removeIf(runnable -> runnable.token != null && runnable.token == token);
|
||||
}
|
||||
|
||||
void removeAllQueuedRunnables() {
|
||||
mQueue.clear();
|
||||
}
|
||||
|
||||
void assertSingleDelayedQueueItem(Duration expectedDelay) {
|
||||
assertQueueLength(1);
|
||||
assertNextQueueItemIsDelayed(expectedDelay);
|
||||
|
||||
Reference in New Issue
Block a user