- Extract most common view related codes into BaseTimeZoneAdapter
and BaseTimeZonePicker. Subclass handles the text formatting and
order.
- Search view is added compared to previous version of time
zone picker
- SpannableUtil is added to preserve spannable when formatting
String resource.
- Fix the bug using GMT+<arabic> as time zone id. b/73132985
- Fix Talkback treating flags on screens as a separate element
Bug: 72146259
Bug: 73132985
Bug: 73952488
Test: mm RunSettingsRoboTests
Change-Id: I42c6ac369199c09d11e7f5cc4707358fa4780fed
(cherry picked from commit fbd30acef0
)
212 lines
7.5 KiB
Java
212 lines
7.5 KiB
Java
/*
|
|
* 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 android.app.Activity;
|
|
import android.content.Intent;
|
|
import android.graphics.Paint;
|
|
import android.icu.text.Collator;
|
|
import android.icu.text.LocaleDisplayNames;
|
|
import android.os.Bundle;
|
|
import android.util.Log;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.core.SubSettingLauncher;
|
|
import com.android.settings.datetime.timezone.model.FilteredCountryTimeZones;
|
|
import com.android.settings.datetime.timezone.model.TimeZoneData;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.TreeSet;
|
|
|
|
/**
|
|
* Render a list of regions into a list view.
|
|
*/
|
|
public class RegionSearchPicker extends BaseTimeZonePicker {
|
|
private static final int REQUEST_CODE_ZONE_PICKER = 1;
|
|
private static final String TAG = "RegionSearchPicker";
|
|
|
|
private BaseTimeZoneAdapter<RegionItem> mAdapter;
|
|
private TimeZoneData mTimeZoneData;
|
|
|
|
public RegionSearchPicker() {
|
|
super(R.string.date_time_select_region, R.string.search_settings, true, true);
|
|
}
|
|
|
|
@Override
|
|
protected BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) {
|
|
mTimeZoneData = timeZoneData;
|
|
mAdapter = new BaseTimeZoneAdapter<>(createAdapterItem(timeZoneData.getRegionIds()),
|
|
this::onListItemClick, getLocale(), false);
|
|
return mAdapter;
|
|
}
|
|
|
|
private void onListItemClick(int position) {
|
|
final String regionId = mAdapter.getItem(position).getId();
|
|
final FilteredCountryTimeZones countryTimeZones = mTimeZoneData.lookupCountryTimeZones(
|
|
regionId);
|
|
final Activity activity = getActivity();
|
|
if (countryTimeZones == null || countryTimeZones.getTimeZoneIds().isEmpty()) {
|
|
Log.e(TAG, "Region has no time zones: " + regionId);
|
|
activity.setResult(Activity.RESULT_CANCELED);
|
|
activity.finish();
|
|
return;
|
|
}
|
|
|
|
List<String> timeZoneIds = countryTimeZones.getTimeZoneIds();
|
|
// Choose the time zone associated the region if there is only one time zone in that region
|
|
if (timeZoneIds.size() == 1) {
|
|
final Intent resultData = new Intent()
|
|
.putExtra(EXTRA_RESULT_REGION_ID, regionId)
|
|
.putExtra(EXTRA_RESULT_TIME_ZONE_ID, timeZoneIds.get(0));
|
|
getActivity().setResult(Activity.RESULT_OK, resultData);
|
|
getActivity().finish();
|
|
} else {
|
|
// Launch the zone picker and let the user choose a time zone from the list of
|
|
// time zones associated with the region.
|
|
final Bundle args = new Bundle();
|
|
args.putString(RegionZonePicker.EXTRA_REGION_ID, regionId);
|
|
new SubSettingLauncher(getContext())
|
|
.setDestination(RegionZonePicker.class.getCanonicalName())
|
|
.setArguments(args)
|
|
.setSourceMetricsCategory(getMetricsCategory())
|
|
.setResultListener(this, REQUEST_CODE_ZONE_PICKER)
|
|
.launch();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
if (requestCode == REQUEST_CODE_ZONE_PICKER) {
|
|
if (resultCode == Activity.RESULT_OK) {
|
|
getActivity().setResult(Activity.RESULT_OK, data);
|
|
}
|
|
getActivity().finish();
|
|
}
|
|
}
|
|
|
|
private List<RegionItem> createAdapterItem(Set<String> regionIds) {
|
|
final Collator collator = Collator.getInstance(getLocale());
|
|
final TreeSet<RegionItem> items = new TreeSet<>(new RegionInfoComparator(collator));
|
|
final Paint paint = new Paint();
|
|
final LocaleDisplayNames localeDisplayNames = LocaleDisplayNames.getInstance(getLocale());
|
|
long i = 0;
|
|
for (String regionId : regionIds) {
|
|
String name = localeDisplayNames.regionDisplayName(regionId);
|
|
String regionalIndicator = createRegionalIndicator(regionId, paint);
|
|
items.add(new RegionItem(i++, regionId, name, regionalIndicator));
|
|
}
|
|
return new ArrayList<>(items);
|
|
}
|
|
|
|
/**
|
|
* Create a Unicode Region Indicator Symbol for a given region id (a.k.a flag emoji). If the
|
|
* system can't render a flag for this region or the input is not a region id, this returns
|
|
* {@code null}.
|
|
*
|
|
* @param id the two-character region id.
|
|
* @param paint Paint contains the glyph
|
|
* @return a String representing the flag of the region or {@code null}.
|
|
*/
|
|
private static String createRegionalIndicator(String id, Paint paint) {
|
|
if (id.length() != 2) {
|
|
return null;
|
|
}
|
|
final char c1 = id.charAt(0);
|
|
final char c2 = id.charAt(1);
|
|
if ('A' > c1 || c1 > 'Z' || 'A' > c2 || c2 > 'Z') {
|
|
return null;
|
|
}
|
|
// Regional Indicator A is U+1F1E6 which is 0xD83C 0xDDE6 in UTF-16.
|
|
final String regionalIndicator = new String(
|
|
new char[]{0xd83c, (char) (0xdde6 - 'A' + c1), 0xd83c, (char) (0xdde6 - 'A' + c2)});
|
|
if (!paint.hasGlyph(regionalIndicator)) {
|
|
return null;
|
|
}
|
|
return regionalIndicator;
|
|
}
|
|
|
|
private static class RegionItem implements BaseTimeZoneAdapter.AdapterItem {
|
|
|
|
private final String mId;
|
|
private final String mName;
|
|
private final String mRegionalIndicator;
|
|
private final long mItemId;
|
|
private final String[] mSearchKeys;
|
|
|
|
RegionItem(long itemId, String id, String name, String regionalIndicator) {
|
|
mId = id;
|
|
mName = name;
|
|
mRegionalIndicator = regionalIndicator;
|
|
mItemId = itemId;
|
|
// Allow to search with ISO_3166-1 alpha-2 code. It's handy for english users in some
|
|
// countries, e.g. US for United States. It's not best search keys for users, but
|
|
// ICU doesn't have the data for the alias names of a region.
|
|
mSearchKeys = new String[] {mId, mName};
|
|
}
|
|
|
|
public String getId() {
|
|
return mId;
|
|
}
|
|
|
|
@Override
|
|
public CharSequence getTitle() {
|
|
return mName;
|
|
}
|
|
|
|
@Override
|
|
public CharSequence getSummary() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public String getIconText() {
|
|
return mRegionalIndicator;
|
|
}
|
|
|
|
@Override
|
|
public String getCurrentTime() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public long getItemId() {
|
|
return mItemId;
|
|
}
|
|
|
|
@Override
|
|
public String[] getSearchKeys() {
|
|
return mSearchKeys;
|
|
}
|
|
}
|
|
|
|
private static class RegionInfoComparator implements Comparator<RegionItem> {
|
|
private final Collator mCollator;
|
|
|
|
RegionInfoComparator(Collator collator) {
|
|
mCollator = collator;
|
|
}
|
|
|
|
@Override
|
|
public int compare(RegionItem r1, RegionItem r2) {
|
|
return mCollator.compare(r1.getTitle(), r2.getTitle());
|
|
}
|
|
}
|
|
}
|