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

Commit 616df78f authored by Neil Fuller's avatar Neil Fuller
Browse files

Add LocationTimeZoneProvider test infrastructure

Add test infrastructure for the location time zone manager.

This test command injection facility is a generalization of the
pre-existing SimulatedBinderProviderEvent and so replaces it with a
distinct send_provider_test_command.  This can be used to test the
ControllerImpl without using the real LocationTimeZoneProvider(s) (which
are disabled when the provider is set to simulation mode).

A command to a simulated secondary provider:

$ adb shell setprop persist.sys.geotz.secondary simulated
$ adb shell reboot
$ adb shell cmd location_time_zone_manager send_provider_test_command \
    secondary on-bind
$ adb shell cmd location_time_zone_manager send_provider_test_command \
    secondary success \
    "tz=string_array:Europe/London\&Europe/Paris"

See also the following for help:

$ adb shell cmd time_zone_detector
$ adb shell cmd location_time_zone_manager

Bug: 152746105
Test: See steps above
Change-Id: I7259778ad7af8f2937cb3b4b8751a02c149d2b21
parent 5736696b
Loading
Loading
Loading
Loading
+1 −2
Original line number Original line Diff line number Diff line
@@ -22,7 +22,6 @@ import android.service.timezone.ITimeZoneProviderManager;
 * @hide
 * @hide
 */
 */
oneway interface ITimeZoneProvider {
oneway interface ITimeZoneProvider {
    void setTimeZoneProviderManager(in @nullable ITimeZoneProviderManager manager);
    void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis);
    void startUpdates(in long initializationTimeoutMillis);
    void stopUpdates();
    void stopUpdates();
}
}
+29 −9
Original line number Original line Diff line number Diff line
@@ -112,7 +112,18 @@ public abstract class TimeZoneProviderService extends Service {


    private static final String TAG = "TimeZoneProviderService";
    private static final String TAG = "TimeZoneProviderService";


    private final Handler mHandler = BackgroundThread.getHandler();
    /**
     * The test command result key indicating whether a command succeeded. Value type: boolean
     * @hide
     */
    public static final String TEST_COMMAND_RESULT_SUCCESS_KEY = "SUCCESS";

    /**
     * The test command result key for the error message present when {@link
     * #TEST_COMMAND_RESULT_SUCCESS_KEY} is false. Value type: string
     * @hide
     */
    public static final String TEST_COMMAND_RESULT_ERROR_KEY = "ERROR";


    /**
    /**
     * The Intent action that the primary location-derived time zone provider service must respond
     * The Intent action that the primary location-derived time zone provider service must respond
@@ -132,6 +143,8 @@ public abstract class TimeZoneProviderService extends Service {


    private final TimeZoneProviderServiceWrapper mWrapper = new TimeZoneProviderServiceWrapper();
    private final TimeZoneProviderServiceWrapper mWrapper = new TimeZoneProviderServiceWrapper();


    private final Handler mHandler = BackgroundThread.getHandler();

    /** Set by {@link #mHandler} thread. */
    /** Set by {@link #mHandler} thread. */
    @Nullable
    @Nullable
    private ITimeZoneProviderManager mManager;
    private ITimeZoneProviderManager mManager;
@@ -198,11 +211,22 @@ public abstract class TimeZoneProviderService extends Service {
        });
        });
    }
    }


    private void onStartUpdatesInternal(@NonNull ITimeZoneProviderManager manager,
            @DurationMillisLong long initializationTimeoutMillis) {
        mManager = manager;
        onStartUpdates(initializationTimeoutMillis);
    }

    /**
    /**
     * Starts the provider sending updates.
     * Starts the provider sending updates.
     */
     */
    public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis);
    public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis);


    private void onStopUpdatesInternal() {
        onStopUpdates();
        mManager = null;
    }

    /**
    /**
     * Stops the provider sending updates.
     * Stops the provider sending updates.
     */
     */
@@ -210,18 +234,14 @@ public abstract class TimeZoneProviderService extends Service {


    private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub {
    private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub {


        @Override
        public void startUpdates(@NonNull ITimeZoneProviderManager manager,
        public void setTimeZoneProviderManager(ITimeZoneProviderManager manager) {
                @DurationMillisLong long initializationTimeoutMillis) {
            Objects.requireNonNull(manager);
            Objects.requireNonNull(manager);
            mHandler.post(() -> TimeZoneProviderService.this.mManager = manager);
            mHandler.post(() -> onStartUpdatesInternal(manager, initializationTimeoutMillis));
        }

        public void startUpdates(@DurationMillisLong long initializationTimeoutMillis) {
            mHandler.post(() -> onStartUpdates(initializationTimeoutMillis));
        }
        }


        public void stopUpdates() {
        public void stopUpdates() {
            mHandler.post(TimeZoneProviderService.this::onStopUpdates);
            mHandler.post(TimeZoneProviderService.this::onStopUpdatesInternal);
        }
        }
    }
    }
}
}
+11 −16
Original line number Original line Diff line number Diff line
@@ -25,8 +25,8 @@ import static com.android.server.location.timezone.LocationTimeZoneProvider.Prov


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.os.RemoteCallback;
import android.util.IndentingPrintWriter;
import android.util.IndentingPrintWriter;
import android.util.Slog;


import java.time.Duration;
import java.time.Duration;
import java.util.Objects;
import java.util.Objects;
@@ -161,6 +161,16 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
        mProxy.setRequest(request);
        mProxy.setRequest(request);
    }
    }


    /**
     * Passes the supplied test command to the current proxy.
     */
    @Override
    void handleTestCommand(@NonNull TestCommand testCommand, @Nullable RemoteCallback callback) {
        mThreadingDomain.assertCurrentThread();

        mProxy.handleTestCommand(testCommand, callback);
    }

    @Override
    @Override
    public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
    public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
        synchronized (mSharedLock) {
        synchronized (mSharedLock) {
@@ -191,19 +201,4 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
                    + '}';
                    + '}';
        }
        }
    }
    }

    /**
     * Passes the supplied simulation / testing event to the current proxy iff the proxy is a
     * {@link SimulatedLocationTimeZoneProviderProxy}. If not, the event is logged but discarded.
     */
    void simulateBinderProviderEvent(SimulatedBinderProviderEvent event) {
        mThreadingDomain.assertCurrentThread();

        if (!(mProxy instanceof SimulatedLocationTimeZoneProviderProxy)) {
            Slog.w(TAG, mProxy + " is not a " + SimulatedLocationTimeZoneProviderProxy.class
                    + ", event=" + event);
            return;
        }
        ((SimulatedLocationTimeZoneProviderProxy) mProxy).simulate(event);
    }
}
}
+32 −18
Original line number Original line Diff line number Diff line
@@ -31,6 +31,7 @@ import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_T
import android.annotation.DurationMillisLong;
import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.os.RemoteCallback;
import android.util.IndentingPrintWriter;
import android.util.IndentingPrintWriter;


import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
@@ -117,7 +118,7 @@ class ControllerImpl extends LocationTimeZoneProviderController {
        mThreadingDomain.assertCurrentThread();
        mThreadingDomain.assertCurrentThread();


        synchronized (mSharedLock) {
        synchronized (mSharedLock) {
            debugLog("onEnvironmentConfigChanged()");
            debugLog("onConfigChanged()");


            ConfigurationInternal oldConfig = mCurrentUserConfiguration;
            ConfigurationInternal oldConfig = mCurrentUserConfiguration;
            ConfigurationInternal newConfig = mEnvironment.getCurrentUserConfigurationInternal();
            ConfigurationInternal newConfig = mEnvironment.getCurrentUserConfigurationInternal();
@@ -553,6 +554,7 @@ class ControllerImpl extends LocationTimeZoneProviderController {
        }
        }
    }
    }


    @NonNull
    private static GeolocationTimeZoneSuggestion createUncertainSuggestion(@NonNull String reason) {
    private static GeolocationTimeZoneSuggestion createUncertainSuggestion(@NonNull String reason) {
        GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null);
        GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null);
        suggestion.addDebugInfo(reason);
        suggestion.addDebugInfo(reason);
@@ -560,30 +562,42 @@ class ControllerImpl extends LocationTimeZoneProviderController {
    }
    }


    /**
    /**
     * Passes a {@link SimulatedBinderProviderEvent] to the appropriate provider.
     * Passes a test command to the specified provider. If the provider name does not match a
     * If the provider name does not match a known provider, then the event is logged and discarded.
     * known provider, then the command is logged and discarded.
     */
     */
    void simulateBinderProviderEvent(@NonNull SimulatedBinderProviderEvent event) {
    void handleProviderTestCommand(
            @NonNull String providerName, @NonNull TestCommand testCommand,
            @Nullable RemoteCallback callback) {
        mThreadingDomain.assertCurrentThread();
        mThreadingDomain.assertCurrentThread();


        String targetProviderName = event.getProviderName();
        LocationTimeZoneProvider targetProvider = getLocationTimeZoneProvider(providerName);
        if (targetProvider == null) {
            warnLog("Unable to process test command:"
                    + " providerName=" + providerName + ", testCommand=" + testCommand);
            return;
        }

        synchronized (mSharedLock) {
            try {
                targetProvider.handleTestCommand(testCommand, callback);
            } catch (Exception e) {
                warnLog("Unable to process test command:"
                        + " providerName=" + providerName + ", testCommand=" + testCommand, e);
            }
        }
    }

    @Nullable
    private LocationTimeZoneProvider getLocationTimeZoneProvider(@NonNull String providerName) {
        LocationTimeZoneProvider targetProvider;
        LocationTimeZoneProvider targetProvider;
        if (Objects.equals(mPrimaryProvider.getName(), targetProviderName)) {
        if (Objects.equals(mPrimaryProvider.getName(), providerName)) {
            targetProvider = mPrimaryProvider;
            targetProvider = mPrimaryProvider;
        } else if (Objects.equals(mSecondaryProvider.getName(), targetProviderName)) {
        } else if (Objects.equals(mSecondaryProvider.getName(), providerName)) {
            targetProvider = mSecondaryProvider;
            targetProvider = mSecondaryProvider;
        } else {
        } else {
            warnLog("Unable to process simulated binder provider event,"
            warnLog("Bad providerName=" + providerName);
                    + " unknown providerName in event=" + event);
            targetProvider = null;
            return;
        }
        if (!(targetProvider instanceof BinderLocationTimeZoneProvider)) {
            warnLog("Unable to process simulated binder provider event,"
                    + " provider=" + targetProvider
                    + " is not a " + BinderLocationTimeZoneProvider.class
                    + ", event=" + event);
            return;
        }
        }
        ((BinderLocationTimeZoneProvider) targetProvider).simulateBinderProviderEvent(event);
        return targetProvider;
    }
    }
}
}
+91 −20
Original line number Original line Diff line number Diff line
@@ -21,7 +21,9 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.os.RemoteCallback;
import android.os.ResultReceiver;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.os.SystemProperties;
@@ -40,7 +42,11 @@ import com.android.server.timezonedetector.TimeZoneDetectorService;


import java.io.FileDescriptor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.time.Duration;
import java.util.Objects;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;


/**
/**
 * A service class that acts as a container for the {@link LocationTimeZoneProviderController},
 * A service class that acts as a container for the {@link LocationTimeZoneProviderController},
@@ -116,8 +122,11 @@ public class LocationTimeZoneManagerService extends Binder {
    static final String PRIMARY_PROVIDER_NAME = "primary";
    static final String PRIMARY_PROVIDER_NAME = "primary";
    static final String SECONDARY_PROVIDER_NAME = "secondary";
    static final String SECONDARY_PROVIDER_NAME = "secondary";


    private static final String SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX =
    static final String PROVIDER_MODE_OVERRIDE_SYSTEM_PROPERTY_PREFIX = "persist.sys.geotz.";
            "persist.sys.location_tz_simulation_mode.";
    static final String PROVIDER_MODE_SIMULATED = "simulated";
    static final String PROVIDER_MODE_DISABLED = "disabled";

    private static final long BLOCKING_OP_WAIT_DURATION_MILLIS = Duration.ofSeconds(20).toMillis();


    private static final String ATTRIBUTION_TAG = "LocationTimeZoneService";
    private static final String ATTRIBUTION_TAG = "LocationTimeZoneService";


@@ -187,8 +196,7 @@ public class LocationTimeZoneManagerService extends Binder {
    }
    }


    private LocationTimeZoneProvider createPrimaryProvider() {
    private LocationTimeZoneProvider createPrimaryProvider() {
        Resources resources = mContext.getResources();
        if (isDisabled(PRIMARY_PROVIDER_NAME)) {
        if (!resources.getBoolean(R.bool.config_enablePrimaryLocationTimeZoneProvider)) {
            return new NullLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME);
            return new NullLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME);
        }
        }


@@ -209,8 +217,7 @@ public class LocationTimeZoneManagerService extends Binder {
    }
    }


    private LocationTimeZoneProvider createSecondaryProvider() {
    private LocationTimeZoneProvider createSecondaryProvider() {
        Resources resources = mContext.getResources();
        if (isDisabled(SECONDARY_PROVIDER_NAME)) {
        if (!resources.getBoolean(R.bool.config_enableSecondaryLocationTimeZoneProvider)) {
            return new NullLocationTimeZoneProvider(mThreadingDomain, SECONDARY_PROVIDER_NAME);
            return new NullLocationTimeZoneProvider(mThreadingDomain, SECONDARY_PROVIDER_NAME);
        }
        }


@@ -230,9 +237,41 @@ public class LocationTimeZoneManagerService extends Binder {
        return new BinderLocationTimeZoneProvider(mThreadingDomain, SECONDARY_PROVIDER_NAME, proxy);
        return new BinderLocationTimeZoneProvider(mThreadingDomain, SECONDARY_PROVIDER_NAME, proxy);
    }
    }


    private boolean isInSimulationMode(String providerName) {
    /** Used for bug triage and in tests to simulate provider events. */
        return SystemProperties.getBoolean(
    private static boolean isInSimulationMode(String providerName) {
                SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX + providerName, false);
        return isProviderModeSetInSystemProperties(providerName, PROVIDER_MODE_SIMULATED);
    }

    /** Used for bug triage, tests and experiments to remove a provider. */
    private boolean isDisabled(String providerName) {
        return !isProviderEnabledInConfig(providerName)
                || isProviderModeSetInSystemProperties(providerName, PROVIDER_MODE_DISABLED);
    }

    private boolean isProviderEnabledInConfig(String providerName) {
        int providerEnabledConfigId;
        switch (providerName) {
            case PRIMARY_PROVIDER_NAME: {
                providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider;
                break;
            }
            case SECONDARY_PROVIDER_NAME: {
                providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider;
                break;
            }
            default: {
                throw new IllegalArgumentException(providerName);
            }
        }
        Resources resources = mContext.getResources();
        return resources.getBoolean(providerEnabledConfigId);
    }

    private static boolean isProviderModeSetInSystemProperties(
            @NonNull String providerName, @NonNull String mode) {
        String systemPropertyProviderMode = SystemProperties.get(
                PROVIDER_MODE_OVERRIDE_SYSTEM_PROPERTY_PREFIX + providerName, null);
        return Objects.equals(systemPropertyProviderMode, mode);
    }
    }


    @Override
    @Override
@@ -244,19 +283,45 @@ public class LocationTimeZoneManagerService extends Binder {
    }
    }


    /**
    /**
     * Asynchronously passes a {@link SimulatedBinderProviderEvent] to the appropriate provider.
     * Passes a {@link TestCommand} to the specified provider and waits for the response.
     * The device must be in simulation mode, otherwise an {@link IllegalStateException} will be
     * thrown.
     */
     */
    void simulateBinderProviderEvent(SimulatedBinderProviderEvent event)
    @NonNull
            throws IllegalStateException {
    Bundle handleProviderTestCommand(
        if (!isInSimulationMode(event.getProviderName())) {
            @NonNull String providerName, @NonNull TestCommand testCommand) {
            throw new IllegalStateException("Use \"setprop "
        enforceManageTimeZoneDetectorPermission();
                    + SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX + event.getProviderName()

                    + " 1\" and reboot before injecting simulated binder events.");
        // Because this method blocks and posts work to the threading domain thread, it would cause
        // a deadlock if it were called by the threading domain thread.
        mThreadingDomain.assertNotCurrentThread();

        AtomicReference<Bundle> resultReference = new AtomicReference<>();
        CountDownLatch latch = new CountDownLatch(1);
        RemoteCallback remoteCallback = new RemoteCallback(x -> {
            resultReference.set(x);
            latch.countDown();
        });

        mThreadingDomain.post(() -> {
            synchronized (mSharedLock) {
                if (mLocationTimeZoneDetectorController == null) {
                    remoteCallback.sendResult(null);
                    return;
                }
                }
        mThreadingDomain.post(
                mLocationTimeZoneDetectorController.handleProviderTestCommand(
                () -> mLocationTimeZoneDetectorController.simulateBinderProviderEvent(event));
                        providerName, testCommand, remoteCallback);
            }
        });

        try {
            // Wait, but not indefinitely.
            if (!latch.await(BLOCKING_OP_WAIT_DURATION_MILLIS, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Command did not complete in time");
            }
        } catch (InterruptedException e) {
            throw new AssertionError(e);
        }

        return resultReference.get();
    }
    }


    @Override
    @Override
@@ -293,4 +358,10 @@ public class LocationTimeZoneManagerService extends Binder {
            Slog.w(TAG, msg, t);
            Slog.w(TAG, msg, t);
        }
        }
    }
    }

    private void enforceManageTimeZoneDetectorPermission() {
        mContext.enforceCallingPermission(
                android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION,
                "manage time and time zone detection");
    }
}
}
Loading