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

Commit 229a6a2b authored by Victor Chang's avatar Victor Chang Committed by Android (Google) Code Review
Browse files

Merge "Time Zone data loader"

parents 52b430ba 6c33caad
Loading
Loading
Loading
Loading
+52 −0
Original line number Diff line number Diff line
@@ -15,9 +15,16 @@
 */
package com.android.settings.datetime.timezone;

import android.icu.text.TimeZoneFormat;
import android.icu.text.TimeZoneNames;
import android.icu.util.TimeZone;
import android.text.TextUtils;

import com.android.settingslib.datetime.ZoneGetter;

import java.util.Date;
import java.util.Locale;

/**
 * Data object containing information for displaying a time zone for the user to select.
 */
@@ -131,6 +138,51 @@ public class TimeZoneInfo {
            }
            return new TimeZoneInfo(this);
        }
    }

    public static class Formatter {
        private final Locale mLocale;
        private final Date mNow;
        private final TimeZoneFormat mTimeZoneFormat;

        public Formatter(Locale locale, Date now) {
            mLocale = locale;
            mNow = now;
            mTimeZoneFormat = TimeZoneFormat.getInstance(locale);
        }

        /**
         * @param timeZoneId Olson time zone id
         * @return TimeZoneInfo containing time zone names, exemplar locations and GMT offset
         */
        public TimeZoneInfo format(String timeZoneId) {
            TimeZone timeZone = TimeZone.getFrozenTimeZone(timeZoneId);
            return format(timeZone);
        }

        /**
         * @param timeZone Olson time zone object
         * @return TimeZoneInfo containing time zone names, exemplar locations and GMT offset
         */
        public TimeZoneInfo format(TimeZone timeZone) {
            final String id = timeZone.getID();
            final TimeZoneNames timeZoneNames = mTimeZoneFormat.getTimeZoneNames();
            final java.util.TimeZone javaTimeZone = android.icu.impl.TimeZoneAdapter.wrap(timeZone);
            final CharSequence gmtOffset = ZoneGetter.getGmtOffsetText(mTimeZoneFormat, mLocale,
                javaTimeZone, mNow);
            return new TimeZoneInfo.Builder(timeZone)
                    .setGenericName(timeZoneNames.getDisplayName(id,
                            TimeZoneNames.NameType.LONG_GENERIC, mNow.getTime()))
                    .setStandardName(timeZoneNames.getDisplayName(id,
                            TimeZoneNames.NameType.LONG_STANDARD, mNow.getTime()))
                    .setDaylightName(timeZoneNames.getDisplayName(id,
                            TimeZoneNames.NameType.LONG_DAYLIGHT, mNow.getTime()))
                    .setExemplarLocation(timeZoneNames.getExemplarLocationName(id))
                    .setGmtOffset(gmtOffset)
                    // TODO: move Item id to TimeZoneInfoAdapter
                    .setItemId(0)
                    .build();
        }
    }

}
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.settings.datetime.timezone.model;

import libcore.util.CountryTimeZones;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Wrap {@class CountryTimeZones} to filter time zone that are shown in the picker.
 */
public class FilteredCountryTimeZones {

    private final CountryTimeZones mCountryTimeZones;
    private final List<String> mTimeZoneIds;

    public FilteredCountryTimeZones(CountryTimeZones countryTimeZones) {
        mCountryTimeZones = countryTimeZones;
        List<String> timeZoneIds = countryTimeZones.getTimeZoneMappings().stream()
                .filter(timeZoneMapping -> timeZoneMapping.showInPicker)
                .map(timeZoneMapping -> timeZoneMapping.timeZoneId)
                .collect(Collectors.toList());
        mTimeZoneIds = Collections.unmodifiableList(timeZoneIds);
    }

    public List<String> getTimeZoneIds() {
        return mTimeZoneIds;
    }

    public CountryTimeZones getCountryTimeZones() {
        return mCountryTimeZones;
    }

    public String getRegionId() {
        return TimeZoneData.normalizeRegionId(mCountryTimeZones.getCountryIso());
    }
}
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.settings.datetime.timezone.model;

import android.support.annotation.VisibleForTesting;

import libcore.util.CountryTimeZones;
import libcore.util.CountryZonesFinder;
import libcore.util.TimeZoneFinder;

import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Wrapper of CountryZonesFinder to normalize the country code and only show the regions that are
 * has time zone shown in the time zone picker.
 * The constructor reads the data from underlying file, and this means it should not be called
 * from the UI thread.
 */
public class TimeZoneData {

    private static WeakReference<TimeZoneData> sCache = null;

    private final CountryZonesFinder mCountryZonesFinder;
    private final Set<String> mRegionIds;

    public static synchronized TimeZoneData getInstance() {
        TimeZoneData data = sCache == null ? null : sCache.get();
        if (data != null) {
            return data;
        }
        data = new TimeZoneData();
        sCache = new WeakReference<>(data);
        return data;
    }

    public TimeZoneData() {
        this(TimeZoneFinder.getInstance().getCountryZonesFinder());
    }

    @VisibleForTesting
    TimeZoneData(CountryZonesFinder countryZonesFinder) {
        mCountryZonesFinder = countryZonesFinder;
        mRegionIds = getNormalizedRegionIds(mCountryZonesFinder.lookupAllCountryIsoCodes());
    }

    public Set<String> getRegionIds() {
        return mRegionIds;
    }

    public Set<String> lookupCountryCodesForZoneId(String tzId) {
        if (tzId == null) {
            return Collections.emptySet();
        }
        return mCountryZonesFinder.lookupCountryTimeZonesForZoneId(tzId).stream()
                .filter(countryTimeZones ->
                    countryTimeZones.getTimeZoneMappings().stream()
                            .anyMatch(mapping ->
                                    mapping.timeZoneId.equals(tzId) && mapping.showInPicker))
                .map(countryTimeZones -> normalizeRegionId(countryTimeZones.getCountryIso()))
                .collect(Collectors.toSet());
    }

    public FilteredCountryTimeZones lookupCountryTimeZones(String regionId) {
        CountryTimeZones finder = regionId == null ? null
                : mCountryZonesFinder.lookupCountryTimeZones(regionId);
       return finder == null ? null : new FilteredCountryTimeZones(finder);
    }

    private static Set<String> getNormalizedRegionIds(List<String> regionIds) {
        final Set<String> result = new HashSet<>(regionIds.size());
        for (String regionId : regionIds) {
            result.add(normalizeRegionId(regionId));
        }
        return Collections.unmodifiableSet(result);
    }

    // Uppercase ASCII is normalized for the purpose of using ICU API
    public static String normalizeRegionId(String regionId) {
        return regionId == null ? null : regionId.toUpperCase(Locale.US);
    }
}
+74 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.settings.datetime.timezone.model;

import android.app.LoaderManager;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;

import com.android.settingslib.utils.AsyncLoader;

public class TimeZoneDataLoader extends AsyncLoader<TimeZoneData> {

    public TimeZoneDataLoader(Context context) {
        super(context);
    }

    @Override
    public TimeZoneData loadInBackground() {
        // Heavy operation due to reading the underlying file
        return new TimeZoneData();
    }

    @Override
    protected void onDiscardResult(TimeZoneData result) {
        // This class doesn't hold resource of the result.
    }

    public interface OnDataReadyCallback {
        void onTimeZoneDataReady(TimeZoneData data);
    }

    public static class LoaderCreator implements LoaderManager.LoaderCallbacks<TimeZoneData> {

        private final Context mContext;
        private final OnDataReadyCallback mCallback;

        public LoaderCreator(Context context, OnDataReadyCallback callback) {
            mContext = context;
            mCallback = callback;
        }

        @Override
        public Loader onCreateLoader(int id, Bundle args) {
            return new TimeZoneDataLoader(mContext);
        }

        @Override
        public void onLoadFinished(Loader<TimeZoneData> loader, TimeZoneData data) {
            if (mCallback != null) {
                mCallback.onTimeZoneDataReady(data);
            }
        }

        @Override
        public void onLoaderReset(Loader<TimeZoneData> loader) {
            //It's okay to keep the time zone data when loader is reset
        }
    }
}
+51 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.settings.datetime.timezone;


import com.android.settings.TestConfig;
import com.android.settings.datetime.timezone.TimeZoneInfo.Formatter;
import com.android.settings.testutils.SettingsRobolectricTestRunner;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;

import java.util.Date;
import java.util.Locale;

import static com.google.common.truth.Truth.assertThat;


@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class TimeZoneInfoTest {

    @Test
    public void testFormat() {
        Date now = new Date(0L); // 00:00 1/1/1970
        Formatter formatter = new Formatter(Locale.US, now);

        TimeZoneInfo timeZoneInfo = formatter.format("America/Los_Angeles");
        assertThat(timeZoneInfo.getId()).isEqualTo("America/Los_Angeles");
        assertThat(timeZoneInfo.getExemplarLocation()).isEqualTo("Los Angeles");
        assertThat(timeZoneInfo.getGmtOffset().toString()).isEqualTo("GMT-08:00");
        assertThat(timeZoneInfo.getGenericName()).isEqualTo("Pacific Time");
        assertThat(timeZoneInfo.getStandardName()).isEqualTo("Pacific Standard Time");
        assertThat(timeZoneInfo.getDaylightName()).isEqualTo("Pacific Daylight Time");
    }
}
Loading