Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cd101fa5 authored by Neil Fuller's avatar Neil Fuller
Browse files

Add start / stop to location_time_zone_manager

Implement a path for reconfiguration of provider behavior at runtime.

Now the location_time_zone_manager can be stopped / started
from the command line with:

$ adb shell location_time_zone_manager stop
$ adb shell location_time_zone_manager start

This is useful during manual and automated (i.e. CTS) tests.

This change introduces LocationTimeZoneProviderController.destroy() and
all the associated plumbing / a new state needed to support "stop".

This commit removes NullLocationTimeZoneProvider and replaces it with a
LocationTimeZoneProviderProxy that does a similar thing.  It adds a
LocationTimeZoneProviderTest to replace the lost coverage.

Bug: 172934905
Bug: 152746105
Test: atest services/tests/servicestests/src/com/android/server/location/timezone/
Change-Id: I1ef7813ff2011251b2d8231a4e80f0374ee32c50
parent 616df78f
Loading
Loading
Loading
Loading
+12 −4
Original line number Diff line number Diff line
@@ -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: {
+10 −2
Original line number Diff line number Diff line
@@ -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
+48 −25
Original line number Diff line number Diff line
@@ -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);
+36 −0
Original line number Diff line number Diff line
@@ -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);
+73 −24
Original line number Diff line number Diff line
@@ -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.
        // 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);
    }

    /**
     * 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);
            ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl(
                    ControllerCallbackImpl callback = new ControllerCallbackImpl(
                            mThreadingDomain);
                    mEnvironment = new ControllerEnvironmentImpl(
                            mThreadingDomain, mLocationTimeZoneDetectorController);

            // 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));
                    mLocationTimeZoneDetectorController.initialize(mEnvironment, callback);
                }
            }

    private LocationTimeZoneProvider createPrimaryProvider() {
        if (isDisabled(PRIMARY_PROVIDER_NAME)) {
            return new NullLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME);
        };
        if (waitForCompletion) {
            mThreadingDomain.postAndWait(runnable, BLOCKING_OP_WAIT_DURATION_MILLIS);
        } else {
            mThreadingDomain.post(runnable);
        }
    }

    private LocationTimeZoneProvider createPrimaryProvider() {
        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);
            }
Loading