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

Commit 762c0265 authored by Neil Fuller's avatar Neil Fuller Committed by Android (Google) Code Review
Browse files

Merge "Add LocationTimeZoneProvider test infrastructure"

parents efff97ac 616df78f
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import android.service.timezone.ITimeZoneProviderManager;
 * @hide
 */
oneway interface ITimeZoneProvider {
    void setTimeZoneProviderManager(in @nullable ITimeZoneProviderManager manager);
    void startUpdates(in long initializationTimeoutMillis);
    void startUpdates(in ITimeZoneProviderManager manager, in long initializationTimeoutMillis);
    void stopUpdates();
}
+29 −9
Original line number Diff line number Diff line
@@ -112,7 +112,18 @@ public abstract class TimeZoneProviderService extends Service {

    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
@@ -132,6 +143,8 @@ public abstract class TimeZoneProviderService extends Service {

    private final TimeZoneProviderServiceWrapper mWrapper = new TimeZoneProviderServiceWrapper();

    private final Handler mHandler = BackgroundThread.getHandler();

    /** Set by {@link #mHandler} thread. */
    @Nullable
    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.
     */
    public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis);

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

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

    private class TimeZoneProviderServiceWrapper extends ITimeZoneProvider.Stub {

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

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

        public void stopUpdates() {
            mHandler.post(TimeZoneProviderService.this::onStopUpdates);
            mHandler.post(TimeZoneProviderService.this::onStopUpdatesInternal);
        }
    }
}
+11 −16
Original line number 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.Nullable;
import android.os.RemoteCallback;
import android.util.IndentingPrintWriter;
import android.util.Slog;

import java.time.Duration;
import java.util.Objects;
@@ -161,6 +161,16 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
        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
    public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
        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 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.NonNull;
import android.annotation.Nullable;
import android.os.RemoteCallback;
import android.util.IndentingPrintWriter;

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

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

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

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

    /**
     * Passes a {@link SimulatedBinderProviderEvent] to the appropriate provider.
     * If the provider name does not match a known provider, then the event is logged and discarded.
     * Passes a test command to the specified provider. If the provider name does not match a
     * 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();

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

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.Duration;
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},
@@ -116,8 +122,11 @@ public class LocationTimeZoneManagerService extends Binder {
    static final String PRIMARY_PROVIDER_NAME = "primary";
    static final String SECONDARY_PROVIDER_NAME = "secondary";

    private static final String SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX =
            "persist.sys.location_tz_simulation_mode.";
    static final String PROVIDER_MODE_OVERRIDE_SYSTEM_PROPERTY_PREFIX = "persist.sys.geotz.";
    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";

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

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

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

    private LocationTimeZoneProvider createSecondaryProvider() {
        Resources resources = mContext.getResources();
        if (!resources.getBoolean(R.bool.config_enableSecondaryLocationTimeZoneProvider)) {
        if (isDisabled(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);
    }

    private boolean isInSimulationMode(String providerName) {
        return SystemProperties.getBoolean(
                SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX + providerName, false);
    /** Used for bug triage and in tests to simulate provider events. */
    private static boolean isInSimulationMode(String providerName) {
        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
@@ -244,19 +283,45 @@ public class LocationTimeZoneManagerService extends Binder {
    }

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

        // 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.simulateBinderProviderEvent(event));
                mLocationTimeZoneDetectorController.handleProviderTestCommand(
                        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
@@ -293,4 +358,10 @@ public class LocationTimeZoneManagerService extends Binder {
            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