1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 package com.android.dialer.assisteddialing.ui;
17 
18 import android.icu.util.ULocale;
19 import android.icu.util.ULocale.Builder;
20 import android.os.Bundle;
21 import android.preference.ListPreference;
22 import android.preference.Preference;
23 import android.preference.PreferenceFragment;
24 import android.preference.SwitchPreference;
25 import android.telephony.TelephonyManager;
26 import com.android.dialer.assisteddialing.AssistedDialingMediator;
27 import com.android.dialer.assisteddialing.ConcreteCreator;
28 import com.android.dialer.assisteddialing.CountryCodeProvider;
29 import com.android.dialer.common.LogUtil;
30 import com.android.dialer.configprovider.ConfigProviderComponent;
31 import com.android.dialer.logging.DialerImpression;
32 import com.android.dialer.logging.Logger;
33 import com.google.auto.value.AutoValue;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Optional;
37 
38 /** The setting for Assisted Dialing */
39 public class AssistedDialingSettingFragment extends PreferenceFragment {
40 
41   private CountryCodeProvider countryCodeProvider;
42   private AssistedDialingMediator assistedDialingMediator;
43 
44   @AutoValue
45   abstract static class DisplayNameAndCountryCodeTuple {
46 
create( CharSequence countryDisplayName, CharSequence countryCode)47     static DisplayNameAndCountryCodeTuple create(
48         CharSequence countryDisplayName, CharSequence countryCode) {
49       return new AutoValue_AssistedDialingSettingFragment_DisplayNameAndCountryCodeTuple(
50           countryDisplayName, countryCode);
51     }
52 
53     // The user-readable name of the country.
countryDisplayname()54     abstract CharSequence countryDisplayname();
55 
56     // The ISO 3166-2 country code of the country.
countryCode()57     abstract CharSequence countryCode();
58   }
59 
60   @Override
onCreate(Bundle savedInstanceState)61   public void onCreate(Bundle savedInstanceState) {
62     super.onCreate(savedInstanceState);
63 
64     assistedDialingMediator =
65         ConcreteCreator.createNewAssistedDialingMediator(
66             getContext().getSystemService(TelephonyManager.class), getContext());
67 
68     countryCodeProvider =
69         ConcreteCreator.getCountryCodeProvider(
70             ConfigProviderComponent.get(getContext()).getConfigProvider());
71 
72     // Load the preferences from an XML resource
73     addPreferencesFromResource(R.xml.assisted_dialing_setting);
74     SwitchPreference switchPref =
75         (SwitchPreference)
76             findPreference(getContext().getString(R.string.assisted_dialing_setting_toggle_key));
77 
78     ListPreference countryChooserPref =
79         (ListPreference)
80             findPreference(getContext().getString(R.string.assisted_dialing_setting_cc_key));
81 
82     updateCountryChoices(countryChooserPref);
83     updateCountryChooserSummary(countryChooserPref);
84 
85     countryChooserPref.setOnPreferenceChangeListener(this::updateListSummary);
86     switchPref.setOnPreferenceChangeListener(this::logIfUserDisabledFeature);
87   }
88 
updateCountryChooserSummary(ListPreference countryChooserPref)89   private void updateCountryChooserSummary(ListPreference countryChooserPref) {
90     String defaultSummaryText = countryChooserPref.getEntries()[0].toString();
91 
92     if (countryChooserPref.getEntry().equals(defaultSummaryText)) {
93       Optional<String> userHomeCountryCode = assistedDialingMediator.userHomeCountryCode();
94       if (userHomeCountryCode.isPresent()) {
95         CharSequence[] entries = countryChooserPref.getEntries();
96         try {
97           CharSequence regionalDisplayName =
98               entries[countryChooserPref.findIndexOfValue(userHomeCountryCode.get())];
99           countryChooserPref.setSummary(
100               getContext()
101                   .getString(
102                       R.string.assisted_dialing_setting_cc_default_summary, regionalDisplayName));
103         } catch (ArrayIndexOutOfBoundsException e) {
104           // This might happen if there is a mismatch between the automatically
105           // detected home country, and the countries currently eligible to select in the settings.
106           LogUtil.i(
107               "AssistedDialingSettingFragment.onCreate",
108               "Failed to find human readable mapping for country code, using default.");
109         }
110       }
111     } else {
112       countryChooserPref.setSummary(countryChooserPref.getEntry());
113     }
114   }
115 
116   /**
117    * Filters the default entries in the country chooser by only showing those countries in which the
118    * feature in enabled.
119    */
updateCountryChoices(ListPreference countryChooserPref)120   private void updateCountryChoices(ListPreference countryChooserPref) {
121 
122     List<DisplayNameAndCountryCodeTuple> defaultCountryChoices =
123         buildDefaultCountryChooserKeysAndValues(countryChooserPref);
124 
125     // Always include the default preference.
126     List<CharSequence> newKeys = new ArrayList<>();
127     List<CharSequence> newValues = new ArrayList<>();
128     newKeys.add(countryChooserPref.getEntries()[0]);
129     newValues.add(countryChooserPref.getEntryValues()[0]);
130 
131     for (DisplayNameAndCountryCodeTuple tuple : defaultCountryChoices) {
132       if (countryCodeProvider.isSupportedCountryCode(tuple.countryCode().toString())) {
133         newKeys.add(tuple.countryDisplayname());
134         newValues.add(tuple.countryCode());
135       }
136     }
137 
138     countryChooserPref.setEntries(newKeys.toArray(new CharSequence[newKeys.size()]));
139     countryChooserPref.setEntryValues(newValues.toArray(new CharSequence[newValues.size()]));
140 
141     if (!newValues.contains(countryChooserPref.getValue())) {
142       ameliorateInvalidSelectedValue(countryChooserPref);
143     }
144   }
145 
146   /**
147    * Restore an invalid user selected value to the default value.
148    *
149    * <p>In the Assisted Dialing settings in Dialer, this state is possible when a user selected a
150    * country code, and then that country code was removed from our filtered list, typically via a
151    * change in the available countries provided by a server side flag.
152    *
153    * @param countryChooserPref The list preference to restore to default when an invalid value is
154    *     detected.
155    */
ameliorateInvalidSelectedValue(ListPreference countryChooserPref)156   private void ameliorateInvalidSelectedValue(ListPreference countryChooserPref) {
157     // Reset the preference value to the default value.
158     countryChooserPref.setValue(countryChooserPref.getEntryValues()[0].toString());
159     LogUtil.i(
160         "AssistedDialingSettingFragment.ameliorateInvalidSelectedValue",
161         "Reset the country chooser preference to the default value.");
162   }
163 
buildDefaultCountryChooserKeysAndValues( ListPreference countryChooserPref)164   private List<DisplayNameAndCountryCodeTuple> buildDefaultCountryChooserKeysAndValues(
165       ListPreference countryChooserPref) {
166     CharSequence[] keys = countryChooserPref.getEntries();
167     CharSequence[] values = countryChooserPref.getEntryValues();
168 
169     if (keys.length != values.length) {
170       throw new IllegalStateException("Unexpected mismatch in country chooser key/value size");
171     }
172 
173     List<DisplayNameAndCountryCodeTuple> displayNamesandCountryCodes = new ArrayList<>();
174     // getCountry() is actually getRegion() and conforms to the iso standards of input for the
175     // builder.
176     ULocale userLocale =
177         new ULocale.Builder()
178             .setRegion(getResources().getConfiguration().getLocales().get(0).getCountry())
179             .setLanguage(getResources().getConfiguration().getLocales().get(0).getLanguage())
180             .build();
181     for (int i = 0; i < keys.length; i++) {
182       ULocale settingRowDisplayCountry = new Builder().setRegion(values[i].toString()).build();
183       String localizedDisplayCountry = settingRowDisplayCountry.getDisplayCountry(userLocale);
184       String settingDisplayName = localizedDisplayCountry + " " + keys[i];
185       displayNamesandCountryCodes.add(
186           DisplayNameAndCountryCodeTuple.create(settingDisplayName, values[i]));
187     }
188 
189     return displayNamesandCountryCodes;
190   }
191 
updateListSummary(Preference pref, Object newValue)192   boolean updateListSummary(Preference pref, Object newValue) {
193     ListPreference listPref = (ListPreference) pref;
194     CharSequence[] entries = listPref.getEntries();
195     listPref.setSummary(entries[listPref.findIndexOfValue(newValue.toString())]);
196     return true;
197   }
198 
logIfUserDisabledFeature(Preference pref, Object newValue)199   boolean logIfUserDisabledFeature(Preference pref, Object newValue) {
200     if (!((boolean) newValue)) {
201       Logger.get(getActivity().getApplicationContext())
202           .logImpression(DialerImpression.Type.ASSISTED_DIALING_FEATURE_DISABLED_BY_USER);
203     }
204 
205     return true;
206   }
207 }
208