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

Commit 18ab037e authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Extracted and augmented Environment for TimeZoneDetector tests" into main

parents f732bafb 3b43a4fc
Loading
Loading
Loading
Loading
+80 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.timezonedetector;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;

import com.android.server.SystemTimeZone;

import java.io.PrintWriter;

/**
 * Used by the time zone detector code to interact with device state besides that available from
 * {@link ServiceConfigAccessor}. It can be faked for testing.
 */
public interface Environment {

    /**
     * Returns the device's currently configured time zone. May return an empty string.
     */
    @NonNull
    String getDeviceTimeZone();

    /**
     * Returns the confidence of the device's current time zone.
     */
    @SystemTimeZone.TimeZoneConfidence
    int getDeviceTimeZoneConfidence();

    /**
     * Sets the device's time zone, associated confidence, and records a debug log entry.
     */
    void setDeviceTimeZoneAndConfidence(
            @NonNull String zoneId, @SystemTimeZone.TimeZoneConfidence int confidence,
            @NonNull String logInfo);

    /**
     * Returns the time according to the elapsed realtime clock, the same as {@link
     * android.os.SystemClock#elapsedRealtime()}.
     */
    @ElapsedRealtimeLong
    long elapsedRealtimeMillis();

    /**
     * Returns the current time in milliseconds, the same as
     * {@link java.lang.System#currentTimeMillis()}.
     */
    @CurrentTimeMillisLong
    long currentTimeMillis();

    /**
     * Adds a standalone entry to the time zone debug log.
     */
    void addDebugLogEntry(@NonNull String logMsg);

    /**
     * Dumps the time zone debug log to the supplied {@link PrintWriter}.
     */
    void dumpDebugLog(PrintWriter printWriter);

    /**
     * Requests that the supplied runnable be invoked asynchronously.
     */
    void runAsync(@NonNull Runnable runnable);
}
+8 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.timezonedetector;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.os.Handler;
@@ -31,9 +32,9 @@ import java.io.PrintWriter;
import java.util.Objects;

/**
 * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
 * The real implementation of {@link Environment}.
 */
final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment {
final class EnvironmentImpl implements Environment {

    private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";

@@ -68,6 +69,11 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment
        return SystemClock.elapsedRealtime();
    }

    @Override
    public @CurrentTimeMillisLong long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    @Override
    public void addDebugLogEntry(@NonNull String logMsg) {
        SystemTimeZone.addDebugLogEntry(logMsg);
+13 −7
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.ORIGI

import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
@@ -44,7 +45,6 @@ import android.icu.text.DateFormat;
import android.icu.text.SimpleDateFormat;
import android.icu.util.TimeZone;
import android.os.Handler;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -153,6 +153,9 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
        }
    };

    @NonNull
    private final Environment mEnvironment;

    private final Object mConfigurationLock = new Object();
    @GuardedBy("mConfigurationLock")
    private ConfigurationInternal mConfigurationInternal;
@@ -170,12 +173,14 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
    /** Create and initialise a new {@code TimeZoneChangeTrackerImpl} */
    @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL")
    public static NotifyingTimeZoneChangeListener create(Handler handler, Context context,
            ServiceConfigAccessor serviceConfigAccessor) {
            ServiceConfigAccessor serviceConfigAccessor,
            @NonNull Environment environment) {
        NotifyingTimeZoneChangeListener changeTracker =
                new NotifyingTimeZoneChangeListener(handler,
                        context,
                        serviceConfigAccessor,
                        context.getSystemService(NotificationManager.class));
                        context.getSystemService(NotificationManager.class),
                        environment);

        // Pretend there was an update to initialize configuration.
        changeTracker.handleConfigurationUpdate();
@@ -184,9 +189,9 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
    }

    @VisibleForTesting
    NotifyingTimeZoneChangeListener(
            Handler handler, Context context, ServiceConfigAccessor serviceConfigAccessor,
            NotificationManager notificationManager) {
    NotifyingTimeZoneChangeListener(Handler handler, Context context,
            ServiceConfigAccessor serviceConfigAccessor, NotificationManager notificationManager,
            @NonNull Environment environment) {
        mHandler = Objects.requireNonNull(handler);
        mContext = Objects.requireNonNull(context);
        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
@@ -194,6 +199,7 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
                this::handleConfigurationUpdate);
        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
        mNotificationManager = notificationManager;
        mEnvironment = Objects.requireNonNull(environment);
    }

    @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL")
@@ -420,7 +426,7 @@ public class NotifyingTimeZoneChangeListener implements TimeZoneChangeListener {
            if (!changeEvent.getOldZoneId().equals(lastChangeEvent.getNewZoneId())) {
                int changeEventId = mNextChangeEventId.getAndIncrement();
                TimeZoneChangeEvent syntheticChangeEvent = new TimeZoneChangeEvent(
                        SystemClock.elapsedRealtime(), System.currentTimeMillis(),
                        mEnvironment.elapsedRealtimeMillis(), mEnvironment.currentTimeMillis(),
                        ORIGIN_UNKNOWN, UserHandle.USER_NULL, lastChangeEvent.getNewZoneId(),
                        changeEvent.getOldZoneId(), 0, "Synthetic");
                TimeZoneChangeRecord syntheticTrackedChangeEvent =
+2 −53
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_S
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;

import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -54,7 +53,6 @@ import com.android.server.SystemTimeZone.TimeZoneConfidence;
import com.android.server.flags.Flags;
import com.android.server.timezonedetector.ConfigurationInternal.DetectionMode;

import java.io.PrintWriter;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -67,55 +65,6 @@ import java.util.Objects;
 */
public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy {

    /**
     * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device state besides that
     * available from {@link #mServiceConfigAccessor}. It can be faked for testing.
     */
    @VisibleForTesting
    public interface Environment {

        /**
         * Returns the device's currently configured time zone. May return an empty string.
         */
        @NonNull
        String getDeviceTimeZone();

        /**
         * Returns the confidence of the device's current time zone.
         */
        @TimeZoneConfidence
        int getDeviceTimeZoneConfidence();

        /**
         * Sets the device's time zone, associated confidence, and records a debug log entry.
         */
        void setDeviceTimeZoneAndConfidence(
                @NonNull String zoneId, @TimeZoneConfidence int confidence,
                @NonNull String logInfo);

        /**
         * Returns the time according to the elapsed realtime clock, the same as {@link
         * android.os.SystemClock#elapsedRealtime()}.
         */
        @ElapsedRealtimeLong
        long elapsedRealtimeMillis();

        /**
         * Adds a standalone entry to the time zone debug log.
         */
        void addDebugLogEntry(@NonNull String logMsg);

        /**
         * Dumps the time zone debug log to the supplied {@link PrintWriter}.
         */
        void dumpDebugLog(PrintWriter printWriter);

        /**
         * Requests that the supplied runnable be invoked asynchronously.
         */
        void runAsync(@NonNull Runnable runnable);
    }

    private static final String LOG_TAG = TimeZoneDetectorService.TAG;
    private static final boolean DBG = TimeZoneDetectorService.DBG;

@@ -263,10 +212,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
    public static TimeZoneDetectorStrategyImpl create(
            @NonNull Context context, @NonNull Handler handler,
            @NonNull ServiceConfigAccessor serviceConfigAccessor) {

        Environment environment = new EnvironmentImpl(handler);
        TimeZoneChangeListener changeEventTracker =
                NotifyingTimeZoneChangeListener.create(handler, context, serviceConfigAccessor);
                NotifyingTimeZoneChangeListener.create(handler, context, serviceConfigAccessor,
                        environment);
        return new TimeZoneDetectorStrategyImpl(
                serviceConfigAccessor, environment, changeEventTracker);
    }
+141 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.timezonedetector;

import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;

import com.android.server.SystemTimeZone;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * A partially implemented, fake implementation of Environment for tests.
 */
public class FakeEnvironment implements Environment {

    private final TestState<String> mTimeZoneId = new TestState<>();
    private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
    private final List<Runnable> mAsyncRunnables = new ArrayList<>();
    private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
    private @CurrentTimeMillisLong long mInitializationTimeMillis;

    FakeEnvironment() {
        // Ensure the fake environment starts with the defaults a fresh device would.
        initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
    }

    void initializeClock(@CurrentTimeMillisLong long currentTimeMillis,
            @ElapsedRealtimeLong long elapsedRealtimeMillis) {
        mInitializationTimeMillis = currentTimeMillis - elapsedRealtimeMillis;
        mElapsedRealtimeMillis = elapsedRealtimeMillis;
    }

    void initializeTimeZoneSetting(String zoneId,
            @SystemTimeZone.TimeZoneConfidence int timeZoneConfidence) {
        mTimeZoneId.init(zoneId);
        mTimeZoneConfidence.init(timeZoneConfidence);
    }

    void incrementClock() {
        mElapsedRealtimeMillis++;
    }

    @Override
    public String getDeviceTimeZone() {
        return mTimeZoneId.getLatest();
    }

    @Override
    public int getDeviceTimeZoneConfidence() {
        return mTimeZoneConfidence.getLatest();
    }

    @Override
    public void setDeviceTimeZoneAndConfidence(
            String zoneId, @SystemTimeZone.TimeZoneConfidence int confidence, String logInfo) {
        mTimeZoneId.set(zoneId);
        mTimeZoneConfidence.set(confidence);
    }

    void assertTimeZoneNotChanged() {
        mTimeZoneId.assertHasNotBeenSet();
        mTimeZoneConfidence.assertHasNotBeenSet();
    }

    void assertTimeZoneChangedTo(String timeZoneId,
            @SystemTimeZone.TimeZoneConfidence int confidence) {
        mTimeZoneId.assertHasBeenSet();
        mTimeZoneId.assertChangeCount(1);
        mTimeZoneId.assertLatestEquals(timeZoneId);

        mTimeZoneConfidence.assertHasBeenSet();
        mTimeZoneConfidence.assertChangeCount(1);
        mTimeZoneConfidence.assertLatestEquals(confidence);
    }

    void commitAllChanges() {
        mTimeZoneId.commitLatest();
        mTimeZoneConfidence.commitLatest();
    }

    @Override
    @ElapsedRealtimeLong
    public long elapsedRealtimeMillis() {
        return mElapsedRealtimeMillis;
    }

    @Override
    @CurrentTimeMillisLong
    public long currentTimeMillis() {
        return mInitializationTimeMillis + mElapsedRealtimeMillis;
    }

    @Override
    public void addDebugLogEntry(String logMsg) {
        // No-op for tests
    }

    @Override
    public void dumpDebugLog(PrintWriter printWriter) {
        // No-op for tests
    }

    /**
     * Adds the supplied runnable to a list but does not run them. To run all the runnables that
     * have been supplied, call {@code #runAsyncRunnables}.
     */
    @Override
    public void runAsync(Runnable runnable) {
        mAsyncRunnables.add(runnable);
    }

    /**
     * Requests that the runnable that have been supplied to {@code #runAsync} are invoked
     * asynchronously and cleared.
     */
    public void runAsyncRunnables() {
        for (Runnable runnable : mAsyncRunnables) {
            runnable.run();
        }
        mAsyncRunnables.clear();
    }
}
Loading