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

Commit 2c6d510e authored by Neil Fuller's avatar Neil Fuller
Browse files

Add a manual path for setting time zone

Add a mechanism for suggesting the "manual" time zone setting to the
TimeZoneDetectorStrategy, e.g. for the settings app to use. This
involves various changes / renames to TimeZoneDetectorStrategy to
introduce a distinction between 3 things:
1) General detection code, applicable for anything that changes device
time zone.
2) Phone detection code, applicable for setting the device time zone
from phone signals.
3) Automatic detection code, applicable for setting the device time zone
automatically (of which telephony signals is currently the only example,
but could be joined by new things in future).

Adds tests for the new ManualTimeZoneSuggestion and makes improvements
to the existing tests for PhoneTimeZoneSuggestionTest.

Test: atest android.app.timezonedetector
Test: atest com.android.server.timezonedetector
Bug: 140712361
Change-Id: I9c3a18e47b157cccabda74855e5c91b45fa93b92
parent c7a4eec4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.app.timezonedetector;

import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.PhoneTimeZoneSuggestion;

/**
@@ -32,5 +33,6 @@ import android.app.timezonedetector.PhoneTimeZoneSuggestion;
 * {@hide}
 */
interface ITimeZoneDetectorService {
  void suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion);
  void suggestPhoneTimeZone(in PhoneTimeZoneSuggestion timeZoneSuggestion);
}
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 android.app.timezonedetector;

parcelable ManualTimeZoneSuggestion;
+128 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 android.app.timezonedetector;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * A time signal from a manual (user provided) source. The value consists of the number of
 * milliseconds elapsed since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime
 * clock when that number was established. The elapsed realtime clock is considered accurate but
 * volatile, so time signals must not be persisted across device resets.
 *
 * @hide
 */
public final class ManualTimeZoneSuggestion implements Parcelable {

    public static final @NonNull Creator<ManualTimeZoneSuggestion> CREATOR =
            new Creator<ManualTimeZoneSuggestion>() {
                public ManualTimeZoneSuggestion createFromParcel(Parcel in) {
                    return ManualTimeZoneSuggestion.createFromParcel(in);
                }

                public ManualTimeZoneSuggestion[] newArray(int size) {
                    return new ManualTimeZoneSuggestion[size];
                }
            };

    @NonNull
    private final String mZoneId;
    @Nullable
    private ArrayList<String> mDebugInfo;

    public ManualTimeZoneSuggestion(@NonNull String zoneId) {
        mZoneId = Objects.requireNonNull(zoneId);
    }

    private static ManualTimeZoneSuggestion createFromParcel(Parcel in) {
        String zoneId = in.readString();
        ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(zoneId);
        @SuppressWarnings("unchecked")
        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
        suggestion.mDebugInfo = debugInfo;
        return suggestion;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString(mZoneId);
        dest.writeList(mDebugInfo);
    }

    @NonNull
    public String getZoneId() {
        return mZoneId;
    }

    @NonNull
    public List<String> getDebugInfo() {
        return mDebugInfo == null
                ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
    }

    /**
     * Associates information with the instance that can be useful for debugging / logging. The
     * information is present in {@link #toString()} but is not considered for
     * {@link #equals(Object)} and {@link #hashCode()}.
     */
    public void addDebugInfo(String... debugInfos) {
        if (mDebugInfo == null) {
            mDebugInfo = new ArrayList<>();
        }
        mDebugInfo.addAll(Arrays.asList(debugInfos));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        ManualTimeZoneSuggestion
                that = (ManualTimeZoneSuggestion) o;
        return Objects.equals(mZoneId, that.mZoneId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mZoneId);
    }

    @Override
    public String toString() {
        return "ManualTimeSuggestion{"
                + "mZoneId=" + mZoneId
                + ", mDebugInfo=" + mDebugInfo
                + '}';
    }
}
+61 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 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 android.app.timezonedetector;

import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

import org.junit.Test;

public class ManualTimeZoneSuggestionTest {

    private static final String ARBITRARY_ZONE_ID1 = "Europe/London";
    private static final String ARBITRARY_ZONE_ID2 = "Europe/Paris";

    @Test
    public void testEquals() {
        ManualTimeZoneSuggestion one = new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID1);
        assertEquals(one, one);

        ManualTimeZoneSuggestion two = new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID1);
        assertEquals(one, two);
        assertEquals(two, one);

        ManualTimeZoneSuggestion three = new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID2);
        assertNotEquals(one, three);
        assertNotEquals(three, one);

        // DebugInfo must not be considered in equals().
        one.addDebugInfo("Debug info 1");
        two.addDebugInfo("Debug info 2");
        assertEquals(one, two);
    }

    @Test
    public void testParcelable() {
        ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID1);
        assertRoundTripParcelable(suggestion);

        // DebugInfo should also be stored (but is not checked by equals()
        suggestion.addDebugInfo("This is debug info");
        ManualTimeZoneSuggestion rtSuggestion = roundTripParcelable(suggestion);
        assertEquals(suggestion.getDebugInfo(), rtSuggestion.getDebugInfo());
    }
}
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 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 android.app.timezonedetector;

import static org.junit.Assert.assertEquals;

import android.os.Parcel;
import android.os.Parcelable;

import java.lang.reflect.Field;

/** Utility methods related to {@link Parcelable} objects used in several tests. */
public final class ParcelableTestSupport {

    private ParcelableTestSupport() {}

    /** Returns the result of parceling and unparceling the argument. */
    @SuppressWarnings("unchecked")
    public static <T extends Parcelable> T roundTripParcelable(T parcelable) {
        Parcel parcel = Parcel.obtain();
        parcel.writeTypedObject(parcelable, 0);
        parcel.setDataPosition(0);

        Parcelable.Creator<T> creator;
        try {
            Field creatorField = parcelable.getClass().getField("CREATOR");
            creator = (Parcelable.Creator<T>) creatorField.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new AssertionError(e);
        }
        T toReturn = parcel.readTypedObject(creator);
        parcel.recycle();
        return toReturn;
    }

    public static <T extends Parcelable> void assertRoundTripParcelable(T instance) {
        assertEquals(instance, roundTripParcelable(instance));
    }
}
Loading