diff --git a/res/layout/time_zone_list.xml b/res/layout/time_zone_list.xml new file mode 100644 index 00000000000..a3c47cd904a --- /dev/null +++ b/res/layout/time_zone_list.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + diff --git a/res/layout/time_zone_list_item.xml b/res/layout/time_zone_list_item.xml new file mode 100644 index 00000000000..471c9d85a50 --- /dev/null +++ b/res/layout/time_zone_list_item.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 8c7b6f4789f..3708a3e5f2c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -752,6 +752,17 @@ Sort alphabetically Sort by time zone + + %1$s starts on %2$s. + + Daylight savings time + + Standard time + + Time zone by region + + Fixed offset time zones + Date diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index e88fb11179a..7d9b331f5bf 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -26,4 +26,5 @@ public class FeatureFlags { public static final String BATTERY_SETTINGS_V2 = "settings_battery_v2"; public static final String BATTERY_DISPLAY_APP_LIST = "settings_battery_display_app_list"; public static final String SECURITY_SETTINGS_V2 = "settings_security_settings_v2"; + public static final String ZONE_PICKER_V2 = "settings_zone_picker_v2"; } diff --git a/src/com/android/settings/datetime/TimeZonePreferenceController.java b/src/com/android/settings/datetime/TimeZonePreferenceController.java index 435b1fe77a3..e29e24550b3 100644 --- a/src/com/android/settings/datetime/TimeZonePreferenceController.java +++ b/src/com/android/settings/datetime/TimeZonePreferenceController.java @@ -20,7 +20,10 @@ import android.content.Context; import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; +import android.util.FeatureFlagUtils; +import com.android.settings.core.FeatureFlags; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.datetime.timezone.ZonePicker; import com.android.settingslib.RestrictedPreference; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.datetime.ZoneGetter; @@ -33,11 +36,13 @@ public class TimeZonePreferenceController extends AbstractPreferenceController private static final String KEY_TIMEZONE = "timezone"; private final AutoTimeZonePreferenceController mAutoTimeZonePreferenceController; + private final boolean mZonePickerV2; public TimeZonePreferenceController(Context context, AutoTimeZonePreferenceController autoTimeZonePreferenceController) { super(context); mAutoTimeZonePreferenceController = autoTimeZonePreferenceController; + mZonePickerV2 = FeatureFlagUtils.isEnabled(mContext, FeatureFlags.ZONE_PICKER_V2); } @Override @@ -45,6 +50,9 @@ public class TimeZonePreferenceController extends AbstractPreferenceController if (!(preference instanceof RestrictedPreference)) { return; } + if (mZonePickerV2) { + preference.setFragment(ZonePicker.class.getName()); + } preference.setSummary(getTimeZoneOffsetAndName()); if( !((RestrictedPreference) preference).isDisabledByAdmin()) { preference.setEnabled(!mAutoTimeZonePreferenceController.isEnabled()); diff --git a/src/com/android/settings/datetime/timezone/TimeZoneAdapter.java b/src/com/android/settings/datetime/timezone/TimeZoneAdapter.java new file mode 100644 index 00000000000..79075ca78f5 --- /dev/null +++ b/src/com/android/settings/datetime/timezone/TimeZoneAdapter.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2017 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 android.content.Context; +import android.graphics.Typeface; +import android.icu.impl.OlsonTimeZone; +import android.icu.text.DateFormat; +import android.icu.text.DisplayContext; +import android.icu.text.SimpleDateFormat; +import android.icu.util.Calendar; +import android.icu.util.TimeZone; +import android.icu.util.TimeZoneTransition; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import com.android.settings.R; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** + * Adapter for showing {@link TimeZoneInfo} objects in a recycler view. + */ +class TimeZoneAdapter extends RecyclerView.Adapter { + + static final int VIEW_TYPE_NORMAL = 1; + static final int VIEW_TYPE_SELECTED = 2; + + private final DateFormat mTimeFormat; + private final DateFormat mDateFormat; + private final View.OnClickListener mOnClickListener; + private final Context mContext; + private final String mCurrentTimeZone; + + private List mTimeZoneInfos; + + TimeZoneAdapter(View.OnClickListener onClickListener, Context context) { + mOnClickListener = onClickListener; + mContext = context; + mTimeFormat = DateFormat.getTimeInstance(SimpleDateFormat.SHORT); + mDateFormat = DateFormat.getDateInstance(SimpleDateFormat.MEDIUM); + mDateFormat.setContext(DisplayContext.CAPITALIZATION_NONE); + mCurrentTimeZone = TimeZone.getDefault().getID(); + setHasStableIds(true); + } + + @Override + public long getItemId(int position) { + return getItem(position).getItemId(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + final View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.time_zone_list_item, parent, false); + view.setOnClickListener(mOnClickListener); + final ViewHolder viewHolder = new ViewHolder(view); + if (viewType == VIEW_TYPE_SELECTED) { + viewHolder.mNameView.setTypeface( + viewHolder.mNameView.getTypeface(), Typeface.BOLD); + } + return viewHolder; + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + final TimeZoneInfo item = getItem(position); + final ViewHolder tzHolder = (ViewHolder) holder; + tzHolder.mNameView.setText(formatName(item)); + tzHolder.mDetailsView.setText(formatDetails(item)); + tzHolder.mTimeView.setText(formatTime(item)); + String dstText = formatDstText(item); + tzHolder.mDstView.setText(dstText); + // Hide DST TextView when it has no content. + tzHolder.mDstView.setVisibility(dstText != null ? View.VISIBLE : View.GONE); + + } + + @Override + public int getItemCount() { + return getTimeZones().size(); + } + + @Override + public int getItemViewType(int position) { + final TimeZoneInfo tz = getItem(position); + if (tz.getId().equals(mCurrentTimeZone)) { + return VIEW_TYPE_SELECTED; + } else { + return VIEW_TYPE_NORMAL; + } + } + + public TimeZoneInfo getItem(int position) { + return getTimeZones().get(position); + } + + private CharSequence formatName(TimeZoneInfo item) { + CharSequence name = item.getExemplarLocation(); + if (name == null) { + name = item.getGenericName(); + } + if (name == null && item.getTimeZone().inDaylightTime(new Date())) { + name = item.getDaylightName(); + } + if (name == null) { + name = item.getStandardName(); + } + if (name == null) { + name = item.getGmtOffset(); + } + return name; + } + + private CharSequence formatDetails(TimeZoneInfo item) { + String name = item.getGenericName(); + if (name == null) { + if (item.getTimeZone().inDaylightTime(new Date())) { + name = item.getDaylightName(); + } else { + name = item.getStandardName(); + } + } + if (name == null) { + return item.getGmtOffset(); + } else { + return TextUtils.concat(item.getGmtOffset(), " ", name); + } + } + + private String formatDstText(TimeZoneInfo item) { + final TimeZone timeZone = item.getTimeZone(); + if (!timeZone.observesDaylightTime()) { + return null; + } + + final TimeZoneTransition nextDstTransition = findNextDstTransition(timeZone); + if (nextDstTransition == null) { + return null; + } + final boolean toDst = nextDstTransition.getTo().getDSTSavings() != 0; + String timeType = toDst ? item.getDaylightName() : item.getStandardName(); + if (timeType == null) { + // Fall back to generic "summer time" and "standard time" if the time zone has no + // specific names. + timeType = toDst ? + mContext.getString(R.string.zone_time_type_dst) : + mContext.getString(R.string.zone_time_type_standard); + + } + final Calendar transitionTime = Calendar.getInstance(timeZone); + transitionTime.setTimeInMillis(nextDstTransition.getTime()); + final String date = mDateFormat.format(transitionTime); + return mContext.getString(R.string.zone_change_to_from_dst, timeType, date); + } + + private TimeZoneTransition findNextDstTransition(TimeZone timeZone) { + if (!(timeZone instanceof OlsonTimeZone)) { + return null; + } + final OlsonTimeZone olsonTimeZone = (OlsonTimeZone) timeZone; + TimeZoneTransition transition = olsonTimeZone.getNextTransition( + System.currentTimeMillis(), /* inclusive */ false); + do { + if (transition.getTo().getDSTSavings() != transition.getFrom().getDSTSavings()) { + break; + } + transition = olsonTimeZone.getNextTransition( + transition.getTime(), /*inclusive */ false); + } while (transition != null); + return transition; + } + + private String formatTime(TimeZoneInfo item) { + return mTimeFormat.format(Calendar.getInstance(item.getTimeZone())); + } + + private List getTimeZones() { + if (mTimeZoneInfos == null) { + return Collections.emptyList(); + } + return mTimeZoneInfos; + } + + void setTimeZoneInfos(List timeZoneInfos) { + mTimeZoneInfos = timeZoneInfos; + notifyDataSetChanged(); + } +} diff --git a/src/com/android/settings/datetime/timezone/ViewHolder.java b/src/com/android/settings/datetime/timezone/ViewHolder.java new file mode 100644 index 00000000000..3cb2c4e29d8 --- /dev/null +++ b/src/com/android/settings/datetime/timezone/ViewHolder.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 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 android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; +import com.android.settings.R; + +/** + * View holder for a time zone list item. + */ +class ViewHolder extends RecyclerView.ViewHolder { + + final TextView mNameView; + final TextView mDstView; + final TextView mDetailsView; + final TextView mTimeView; + + public ViewHolder(View itemView) { + super(itemView); + mNameView = itemView.findViewById(R.id.tz_item_name); + mDstView = itemView.findViewById(R.id.tz_item_dst); + mDetailsView = itemView.findViewById(R.id.tz_item_details); + mTimeView = itemView.findViewById(R.id.tz_item_time); + } +} diff --git a/src/com/android/settings/datetime/timezone/ZonePicker.java b/src/com/android/settings/datetime/timezone/ZonePicker.java new file mode 100644 index 00000000000..eafbaa29bf2 --- /dev/null +++ b/src/com/android/settings/datetime/timezone/ZonePicker.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2017 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 android.app.Activity; +import android.app.AlarmManager; +import android.content.Context; +import android.icu.util.TimeZone; +import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.core.InstrumentedFragment; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * The class displaying a region list and a list of time zones for the selected region. + * Choosing an item from the list will set the time zone. Pressing Back without choosing from the + * list will not result in a change in the time zone setting. + */ +public class ZonePicker extends InstrumentedFragment + implements AdapterView.OnItemSelectedListener, View.OnClickListener { + + private static final int MENU_BY_REGION = Menu.FIRST; + private static final int MENU_BY_OFFSET = Menu.FIRST + 1; + + private Locale mLocale; + private List mRegions; + private Map> mZoneInfos; + private List mFixedOffsetTimeZones; + private TimeZoneAdapter mTimeZoneAdapter; + private String mSelectedTimeZone; + private boolean mSelectByRegion; + private DataLoader mDataLoader; + private RecyclerView mRecyclerView; + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.ZONE_PICKER; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.time_zone_list, container, false); + + mLocale = getContext().getResources().getConfiguration().locale; + mDataLoader = new DataLoader(mLocale); + // TOOD: move this off the UI thread. + mRegions = mDataLoader.loadRegionInfos(); + mZoneInfos = new HashMap<>(); + mSelectByRegion = true; + mSelectedTimeZone = TimeZone.getDefault().getID(); + + mTimeZoneAdapter = new TimeZoneAdapter(this, getContext()); + mRecyclerView = view.findViewById(R.id.tz_list); + mRecyclerView.setAdapter(mTimeZoneAdapter); + mRecyclerView.setLayoutManager( + new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, /* reverseLayout */ false)); + + final ArrayAdapter regionAdapter = new ArrayAdapter<>(getContext(), + R.layout.filter_spinner_item, mRegions); + regionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + final Spinner spinner = view.findViewById(R.id.tz_region_spinner); + spinner.setAdapter(regionAdapter); + spinner.setOnItemSelectedListener(this); + setupForCurrentTimeZone(spinner); + setHasOptionsMenu(true); + return view; + } + + private void setupForCurrentTimeZone(Spinner spinner) { + final String localeRegionId = mLocale.getCountry().toUpperCase(Locale.ROOT); + final String currentTimeZone = TimeZone.getDefault().getID(); + boolean fixedOffset = currentTimeZone.startsWith("Etc/GMT") || + currentTimeZone.equals("Etc/UTC"); + + for (int regionIndex = 0; regionIndex < mRegions.size(); regionIndex++) { + final RegionInfo region = mRegions.get(regionIndex); + if (localeRegionId.equals(region.getId())) { + spinner.setSelection(regionIndex); + } + if (!fixedOffset) { + for (String timeZoneId: region.getTimeZoneIds()) { + if (TextUtils.equals(timeZoneId, mSelectedTimeZone)) { + spinner.setSelection(regionIndex); + return; + } + } + } + } + + if (fixedOffset) { + setSelectByRegion(false); + } + } + + @Override + public void onClick(View view) { + // Ignore extra clicks + if (!isResumed()) { + return; + } + final int position = mRecyclerView.getChildAdapterPosition(view); + if (position == RecyclerView.NO_POSITION) { + return; + } + final TimeZoneInfo timeZoneInfo = mTimeZoneAdapter.getItem(position); + + // Update the system timezone value + final Activity activity = getActivity(); + final AlarmManager alarm = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE); + alarm.setTimeZone(timeZoneInfo.getId()); + + activity.onBackPressed(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_BY_REGION, 0, R.string.zone_menu_by_region); + menu.add(0, MENU_BY_OFFSET, 0, R.string.zone_menu_by_offset); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + if (mSelectByRegion) { + menu.findItem(MENU_BY_REGION).setVisible(false); + menu.findItem(MENU_BY_OFFSET).setVisible(true); + } else { + menu.findItem(MENU_BY_REGION).setVisible(true); + menu.findItem(MENU_BY_OFFSET).setVisible(false); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case MENU_BY_REGION: + setSelectByRegion(true); + return true; + + case MENU_BY_OFFSET: + setSelectByRegion(false); + return true; + + default: + return false; + } + } + + private void setSelectByRegion(boolean selectByRegion) { + mSelectByRegion = selectByRegion; + getView().findViewById(R.id.tz_region_spinner_layout).setVisibility( + mSelectByRegion ? View.VISIBLE : View.GONE); + List tzInfos; + if (selectByRegion) { + Spinner regionSpinner = getView().findViewById(R.id.tz_region_spinner); + int selectedRegion = regionSpinner.getSelectedItemPosition(); + if (selectedRegion == -1) { + // Arbitrarily pick the first item if no region was selected above. + selectedRegion = 0; + regionSpinner.setSelection(selectedRegion); + } + tzInfos = getTimeZoneInfos(mRegions.get(selectedRegion)); + } else { + if (mFixedOffsetTimeZones == null) { + mFixedOffsetTimeZones = mDataLoader.loadFixedOffsets(); + } + tzInfos = mFixedOffsetTimeZones; + } + mTimeZoneAdapter.setTimeZoneInfos(tzInfos); + } + + private List getTimeZoneInfos(RegionInfo regionInfo) { + List tzInfos = mZoneInfos.get(regionInfo.getId()); + if (tzInfos == null) { + // TODO: move this off the UI thread. + Collection tzIds = regionInfo.getTimeZoneIds(); + tzInfos = mDataLoader.loadTimeZoneInfos(tzIds); + mZoneInfos.put(regionInfo.getId(), tzInfos); + } + return tzInfos; + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mTimeZoneAdapter.setTimeZoneInfos(getTimeZoneInfos(mRegions.get(position))); + } + + @Override + public void onNothingSelected(AdapterView parent) { + mTimeZoneAdapter.setTimeZoneInfos(null); + } + +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneAdapterTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneAdapterTest.java new file mode 100644 index 00000000000..5f29a0b65f1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/TimeZoneAdapterTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 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 android.icu.util.TimeZone; +import android.text.TextUtils; +import android.view.View; +import android.widget.FrameLayout; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowResources; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.Collections; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, + shadows = { + SettingsShadowResources.class, + SettingsShadowResources.SettingsShadowTheme.class}) +public class TimeZoneAdapterTest { + @Mock + private View.OnClickListener mOnClickListener; + + private TimeZoneAdapter mTimeZoneAdapter; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTimeZoneAdapter = new TimeZoneAdapter(mOnClickListener, RuntimeEnvironment.application); + } + + @Test + public void getItemViewType_onDefaultTimeZone_returnsTypeSelected() { + final TimeZoneInfo tzi = dummyTimeZoneInfo(TimeZone.getDefault()); + mTimeZoneAdapter.setTimeZoneInfos(Collections.singletonList(tzi)); + assertThat(mTimeZoneAdapter.getItemViewType(0)).isEqualTo(TimeZoneAdapter.VIEW_TYPE_SELECTED); + } + + @Test + public void getItemViewType_onNonDefaultTimeZone_returnsTypeNormal() { + final TimeZoneInfo tzi = dummyTimeZoneInfo(getNonDefaultTimeZone()); + mTimeZoneAdapter.setTimeZoneInfos(Collections.singletonList(tzi)); + assertThat(mTimeZoneAdapter.getItemViewType(0)).isEqualTo(TimeZoneAdapter.VIEW_TYPE_NORMAL); + } + + @Test + public void bindViewHolder_onDstTimeZone_showsDstLabel() { + final TimeZoneInfo tzi = dummyTimeZoneInfo(TimeZone.getTimeZone("America/Los_Angeles")); + mTimeZoneAdapter.setTimeZoneInfos(Collections.singletonList(tzi)); + + final FrameLayout parent = new FrameLayout(RuntimeEnvironment.application); + + final ViewHolder viewHolder = (ViewHolder) mTimeZoneAdapter.createViewHolder(parent, TimeZoneAdapter.VIEW_TYPE_NORMAL); + mTimeZoneAdapter.bindViewHolder(viewHolder, 0); + assertThat(viewHolder.mDstView.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void bindViewHolder_onNonDstTimeZone_hidesDstLabel() { + final TimeZoneInfo tzi = dummyTimeZoneInfo(TimeZone.getTimeZone("Etc/UTC")); + mTimeZoneAdapter.setTimeZoneInfos(Collections.singletonList(tzi)); + + final FrameLayout parent = new FrameLayout(RuntimeEnvironment.application); + + final ViewHolder viewHolder = (ViewHolder) mTimeZoneAdapter.createViewHolder(parent, TimeZoneAdapter.VIEW_TYPE_NORMAL); + mTimeZoneAdapter.bindViewHolder(viewHolder, 0); + assertThat(viewHolder.mDstView.getVisibility()).isEqualTo(View.GONE); + } + + // Pick an arbitrary time zone that's not the current default. + private static TimeZone getNonDefaultTimeZone() { + final String[] availableIDs = TimeZone.getAvailableIDs(); + int index = 0; + if (TextUtils.equals(availableIDs[index], TimeZone.getDefault().getID())) { + index++; + } + return TimeZone.getTimeZone(availableIDs[index]); + } + + private TimeZoneInfo dummyTimeZoneInfo(TimeZone timeZone) { + return new TimeZoneInfo.Builder(timeZone).setGmtOffset("GMT+0").setItemId(1).build(); + } +}