• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 
17 package com.android.providers.settings;
18 
19 import android.annotation.NonNull;
20 import android.app.ActivityManager;
21 import android.app.IActivityManager;
22 import android.app.backup.IBackupManager;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.res.Configuration;
28 import android.hardware.display.ColorDisplayManager;
29 import android.icu.util.ULocale;
30 import android.media.AudioManager;
31 import android.media.RingtoneManager;
32 import android.net.Uri;
33 import android.os.LocaleList;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.os.UserHandle;
37 import android.provider.Settings;
38 import android.telephony.TelephonyManager;
39 import android.text.TextUtils;
40 import android.util.ArraySet;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.app.LocalePicker;
44 import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
45 
46 import java.util.ArrayList;
47 import java.util.HashMap;
48 import java.util.Locale;
49 
50 public class SettingsHelper {
51     private static final String TAG = "SettingsHelper";
52     private static final String SILENT_RINGTONE = "_silent";
53     private static final String SETTINGS_REPLACED_KEY = "backup_skip_user_facing_data";
54     private static final String SETTING_ORIGINAL_KEY_SUFFIX = "_original";
55     private static final float FLOAT_TOLERANCE = 0.01f;
56 
57     /** See frameworks/base/core/res/res/values/config.xml#config_longPressOnPowerBehavior **/
58     private static final int LONG_PRESS_POWER_NOTHING = 0;
59     private static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
60     private static final int LONG_PRESS_POWER_FOR_ASSISTANT = 5;
61     /** See frameworks/base/core/res/res/values/config.xml#config_keyChordPowerVolumeUp **/
62     private static final int KEY_CHORD_POWER_VOLUME_UP_GLOBAL_ACTIONS = 2;
63 
64     private Context mContext;
65     private AudioManager mAudioManager;
66     private TelephonyManager mTelephonyManager;
67 
68     /**
69      * A few settings elements are special in that a restore of those values needs to
70      * be post-processed by relevant parts of the OS.  A restore of any settings element
71      * mentioned in this table will therefore cause the system to send a broadcast with
72      * the {@link Intent#ACTION_SETTING_RESTORED} action, with extras naming the
73      * affected setting and supplying its pre-restore value for comparison.
74      *
75      * @see Intent#ACTION_SETTING_RESTORED
76      * @see System#SETTINGS_TO_BACKUP
77      * @see Secure#SETTINGS_TO_BACKUP
78      * @see Global#SETTINGS_TO_BACKUP
79      *
80      * {@hide}
81      */
82     private static final ArraySet<String> sBroadcastOnRestore;
83     private static final ArraySet<String> sBroadcastOnRestoreSystemUI;
84     static {
85         sBroadcastOnRestore = new ArraySet<String>(9);
86         sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
87         sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS);
88         sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
89         sBroadcastOnRestore.add(Settings.Global.BLUETOOTH_ON);
90         sBroadcastOnRestore.add(Settings.Secure.UI_NIGHT_MODE);
91         sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_START_TIME);
92         sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_END_TIME);
93         sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
94         sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
95         sBroadcastOnRestoreSystemUI = new ArraySet<String>(2);
96         sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES);
97         sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES);
98     }
99 
100     private interface SettingsLookup {
lookup(ContentResolver resolver, String name, int userHandle)101         public String lookup(ContentResolver resolver, String name, int userHandle);
102     }
103 
104     private static SettingsLookup sSystemLookup = new SettingsLookup() {
105         public String lookup(ContentResolver resolver, String name, int userHandle) {
106             return Settings.System.getStringForUser(resolver, name, userHandle);
107         }
108     };
109 
110     private static SettingsLookup sSecureLookup = new SettingsLookup() {
111         public String lookup(ContentResolver resolver, String name, int userHandle) {
112             return Settings.Secure.getStringForUser(resolver, name, userHandle);
113         }
114     };
115 
116     private static SettingsLookup sGlobalLookup = new SettingsLookup() {
117         public String lookup(ContentResolver resolver, String name, int userHandle) {
118             return Settings.Global.getStringForUser(resolver, name, userHandle);
119         }
120     };
121 
SettingsHelper(Context context)122     public SettingsHelper(Context context) {
123         mContext = context;
124         mAudioManager = (AudioManager) context
125                 .getSystemService(Context.AUDIO_SERVICE);
126         mTelephonyManager = (TelephonyManager) context
127                 .getSystemService(Context.TELEPHONY_SERVICE);
128     }
129 
130     /**
131      * Sets the property via a call to the appropriate API, if any, and returns
132      * whether or not the setting should be saved to the database as well.
133      * @param name the name of the setting
134      * @param value the string value of the setting
135      * @return whether to continue with writing the value to the database. In
136      * some cases the data will be written by the call to the appropriate API,
137      * and in some cases the property value needs to be modified before setting.
138      */
restoreValue(Context context, ContentResolver cr, ContentValues contentValues, Uri destination, String name, String value, int restoredFromSdkInt)139     public void restoreValue(Context context, ContentResolver cr, ContentValues contentValues,
140             Uri destination, String name, String value, int restoredFromSdkInt) {
141         if (isReplacedSystemSetting(name)) {
142             return;
143         }
144 
145         // Will we need a post-restore broadcast for this element?
146         String oldValue = null;
147         boolean sendBroadcast = false;
148         boolean sendBroadcastSystemUI = false;
149         final SettingsLookup table;
150 
151         if (destination.equals(Settings.Secure.CONTENT_URI)) {
152             table = sSecureLookup;
153         } else if (destination.equals(Settings.System.CONTENT_URI)) {
154             table = sSystemLookup;
155         } else { /* must be GLOBAL; this was preflighted by the caller */
156             table = sGlobalLookup;
157         }
158 
159         sendBroadcast = sBroadcastOnRestore.contains(name);
160         sendBroadcastSystemUI = sBroadcastOnRestoreSystemUI.contains(name);
161 
162         if (sendBroadcast || sendBroadcastSystemUI) {
163             // TODO: http://b/22388012
164             oldValue = table.lookup(cr, name, UserHandle.USER_SYSTEM);
165         }
166 
167         try {
168             if (Settings.System.SOUND_EFFECTS_ENABLED.equals(name)) {
169                 setSoundEffects(Integer.parseInt(value) == 1);
170                 // fall through to the ordinary write to settings
171             } else if (Settings.Secure.BACKUP_AUTO_RESTORE.equals(name)) {
172                 setAutoRestore(Integer.parseInt(value) == 1);
173             } else if (isAlreadyConfiguredCriticalAccessibilitySetting(name)) {
174                 return;
175             } else if (Settings.System.RINGTONE.equals(name)
176                     || Settings.System.NOTIFICATION_SOUND.equals(name)
177                     || Settings.System.ALARM_ALERT.equals(name)) {
178                 setRingtone(name, value);
179                 return;
180             } else if (Settings.System.DISPLAY_COLOR_MODE.equals(name)) {
181                 int mode = Integer.parseInt(value);
182                 String restoredVendorHint = Settings.System.getString(mContext.getContentResolver(),
183                         Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT);
184                 final String deviceVendorHint = mContext.getResources().getString(
185                         com.android.internal.R.string.config_vendorColorModesRestoreHint);
186                 boolean displayColorModeVendorModeHintsMatch =
187                         !TextUtils.isEmpty(deviceVendorHint)
188                                 && deviceVendorHint.equals(restoredVendorHint);
189                 // Replace vendor hint with new device's vendor hint.
190                 contentValues.clear();
191                 contentValues.put(Settings.NameValueTable.NAME,
192                         Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT);
193                 contentValues.put(Settings.NameValueTable.VALUE, deviceVendorHint);
194                 cr.insert(destination, contentValues);
195                 // If vendor hints match, modes in the vendor range can be restored. Otherwise, only
196                 // map standard modes.
197                 if (!ColorDisplayManager.isStandardColorMode(mode)
198                         && !displayColorModeVendorModeHintsMatch) {
199                     return;
200                 }
201             } else if (Settings.Global.POWER_BUTTON_LONG_PRESS.equals(name)) {
202                 setLongPressPowerBehavior(cr, value);
203                 return;
204             } else if (Settings.System.ACCELEROMETER_ROTATION.equals(name)
205                     && shouldSkipAutoRotateRestore()) {
206                 return;
207             }
208 
209             // Default case: write the restored value to settings
210             contentValues.clear();
211             contentValues.put(Settings.NameValueTable.NAME, name);
212             contentValues.put(Settings.NameValueTable.VALUE, value);
213             cr.insert(destination, contentValues);
214         } catch (Exception e) {
215             // If we fail to apply the setting, by definition nothing happened
216             sendBroadcast = false;
217             sendBroadcastSystemUI = false;
218         } finally {
219             // If this was an element of interest, send the "we just restored it"
220             // broadcast with the historical value now that the new value has
221             // been committed and observers kicked off.
222             if (sendBroadcast || sendBroadcastSystemUI) {
223                 Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
224                         .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
225                         .putExtra(Intent.EXTRA_SETTING_NAME, name)
226                         .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, value)
227                         .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, oldValue)
228                         .putExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, restoredFromSdkInt);
229 
230                 if (sendBroadcast) {
231                     intent.setPackage("android");
232                     context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
233                 }
234                 if (sendBroadcastSystemUI) {
235                     intent.setPackage(
236                             context.getString(com.android.internal.R.string.config_systemUi));
237                     context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
238                 }
239             }
240         }
241     }
242 
shouldSkipAutoRotateRestore()243     private boolean shouldSkipAutoRotateRestore() {
244         // When device state based auto rotation settings are available, let's skip the restoring
245         // of the standard auto rotation settings to avoid conflicting setting values.
246         return DeviceStateRotationLockSettingsManager.isDeviceStateRotationLockEnabled(mContext);
247     }
248 
onBackupValue(String name, String value)249     public String onBackupValue(String name, String value) {
250         // Special processing for backing up ringtones & notification sounds
251         if (Settings.System.RINGTONE.equals(name)
252                 || Settings.System.NOTIFICATION_SOUND.equals(name)
253                 || Settings.System.ALARM_ALERT.equals(name)) {
254             if (value == null) {
255                 if (Settings.System.RINGTONE.equals(name)) {
256                     // For ringtones, we need to distinguish between non-telephony vs telephony
257                     if (mTelephonyManager != null && mTelephonyManager.isVoiceCapable()) {
258                         // Backup a null ringtone as silent on voice-capable devices
259                         return SILENT_RINGTONE;
260                     } else {
261                         // Skip backup of ringtone on non-telephony devices.
262                         return null;
263                     }
264                 } else {
265                     // Backup a null notification sound or alarm alert as silent
266                     return SILENT_RINGTONE;
267                 }
268             } else {
269                 return getCanonicalRingtoneValue(value);
270             }
271         }
272         // Return the original value
273         return isReplacedSystemSetting(name) ? getRealValueForSystemSetting(name) : value;
274     }
275 
276     /**
277      * The setting value might have been replaced temporarily. If that's the case, return the real
278      * value instead of the temporary one.
279      */
280     @VisibleForTesting
getRealValueForSystemSetting(String setting)281     public String getRealValueForSystemSetting(String setting) {
282         // The real value irrespectively of the original setting's namespace is stored in
283         // Settings.Secure.
284         return Settings.Secure.getString(mContext.getContentResolver(),
285                 setting + SETTING_ORIGINAL_KEY_SUFFIX);
286     }
287 
288     @VisibleForTesting
isReplacedSystemSetting(String setting)289     public boolean isReplacedSystemSetting(String setting) {
290         // This list should not be modified.
291         if (!Settings.System.SCREEN_OFF_TIMEOUT.equals(setting)) {
292             return false;
293         }
294         // If this flag is set, values for the system settings from the list above have been
295         // temporarily replaced. We don't want to back up the temporary value or run restore for
296         // such settings.
297         // TODO(154822946): Remove this logic in the next release.
298         return Settings.Secure.getInt(mContext.getContentResolver(), SETTINGS_REPLACED_KEY,
299                 /* def */ 0) != 0;
300     }
301 
302     /**
303      * Sets the ringtone of type specified by the name.
304      *
305      * @param name should be Settings.System.RINGTONE, Settings.System.NOTIFICATION_SOUND
306      * or Settings.System.ALARM_ALERT.
307      * @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone.
308      */
setRingtone(String name, String value)309     private void setRingtone(String name, String value) {
310         // If it's null, don't change the default
311         if (value == null) return;
312         final Uri ringtoneUri;
313         if (SILENT_RINGTONE.equals(value)) {
314             ringtoneUri = null;
315         } else {
316             Uri canonicalUri = Uri.parse(value);
317             ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri);
318             if (ringtoneUri == null) {
319                 // Unrecognized or invalid Uri, don't restore
320                 return;
321             }
322         }
323         final int ringtoneType = getRingtoneType(name);
324 
325         RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri);
326     }
327 
getRingtoneType(String name)328     private int getRingtoneType(String name) {
329         switch (name) {
330             case Settings.System.RINGTONE:
331                 return RingtoneManager.TYPE_RINGTONE;
332             case Settings.System.NOTIFICATION_SOUND:
333                 return RingtoneManager.TYPE_NOTIFICATION;
334             case Settings.System.ALARM_ALERT:
335                 return RingtoneManager.TYPE_ALARM;
336             default:
337                 throw new IllegalArgumentException("Incorrect ringtone name: " + name);
338         }
339     }
340 
getCanonicalRingtoneValue(String value)341     private String getCanonicalRingtoneValue(String value) {
342         final Uri ringtoneUri = Uri.parse(value);
343         final Uri canonicalUri = mContext.getContentResolver().canonicalize(ringtoneUri);
344         return canonicalUri == null ? null : canonicalUri.toString();
345     }
346 
isAlreadyConfiguredCriticalAccessibilitySetting(String name)347     private boolean isAlreadyConfiguredCriticalAccessibilitySetting(String name) {
348         // These are the critical accessibility settings that are required for users with
349         // accessibility needs to be able to interact with the device. If these settings are
350         // already configured, we will not overwrite them. If they are already set,
351         // it means that the user has performed a global gesture to enable accessibility or set
352         // these settings in the Accessibility portion of the Setup Wizard, and definitely needs
353         // these features working after the restore.
354         switch (name) {
355             case Settings.Secure.ACCESSIBILITY_ENABLED:
356             case Settings.Secure.TOUCH_EXPLORATION_ENABLED:
357             case Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED:
358             case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED:
359             case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED:
360                 return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
361             case Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES:
362             case Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES:
363             case Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER:
364                 return !TextUtils.isEmpty(Settings.Secure.getString(
365                         mContext.getContentResolver(), name));
366             case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE:
367                 float defaultScale = mContext.getResources().getFraction(
368                         R.fraction.def_accessibility_display_magnification_scale, 1, 1);
369                 float currentScale = Settings.Secure.getFloat(
370                         mContext.getContentResolver(), name, defaultScale);
371                 return Math.abs(currentScale - defaultScale) >= FLOAT_TOLERANCE;
372             case Settings.System.FONT_SCALE:
373                 return Settings.System.getFloat(mContext.getContentResolver(), name, 1.0f) != 1.0f;
374             default:
375                 return false;
376         }
377     }
378 
setAutoRestore(boolean enabled)379     private void setAutoRestore(boolean enabled) {
380         try {
381             IBackupManager bm = IBackupManager.Stub.asInterface(
382                     ServiceManager.getService(Context.BACKUP_SERVICE));
383             if (bm != null) {
384                 bm.setAutoRestore(enabled);
385             }
386         } catch (RemoteException e) {}
387     }
388 
setSoundEffects(boolean enable)389     private void setSoundEffects(boolean enable) {
390         if (enable) {
391             mAudioManager.loadSoundEffects();
392         } else {
393             mAudioManager.unloadSoundEffects();
394         }
395     }
396 
397     /**
398      * Correctly sets long press power button Behavior.
399      *
400      * The issue is that setting for LongPressPower button Behavior is not available on all devices
401      * and actually changes default Behavior of two properties - the long press power button
402      * and volume up + power button combo. OEM can also reconfigure these Behaviors in config.xml,
403      * so we need to be careful that we don't irreversibly change power button Behavior when
404      * restoring. Or switch to a non-default button Behavior.
405      */
setLongPressPowerBehavior(ContentResolver cr, String value)406     private void setLongPressPowerBehavior(ContentResolver cr, String value) {
407         // We will not restore the value if the long press power setting option is unavailable.
408         if (!mContext.getResources().getBoolean(
409                 com.android.internal.R.bool.config_longPressOnPowerForAssistantSettingAvailable)) {
410             return;
411         }
412 
413         int longPressOnPowerBehavior;
414         try {
415             longPressOnPowerBehavior = Integer.parseInt(value);
416         } catch (NumberFormatException e) {
417             return;
418         }
419 
420         if (longPressOnPowerBehavior < LONG_PRESS_POWER_NOTHING
421                 || longPressOnPowerBehavior > LONG_PRESS_POWER_FOR_ASSISTANT) {
422             return;
423         }
424 
425         // When user enables long press power for Assistant, we also switch the meaning
426         // of Volume Up + Power key chord to the "Show power menu" option.
427         // If the user disables long press power for Assistant, we switch back to default OEM
428         // Behavior configured in config.xml. If the default Behavior IS "LPP for Assistant",
429         // then we fall back to "Long press for Power Menu" Behavior.
430         if (longPressOnPowerBehavior == LONG_PRESS_POWER_FOR_ASSISTANT) {
431             Settings.Global.putInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
432                     LONG_PRESS_POWER_FOR_ASSISTANT);
433             Settings.Global.putInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
434                     KEY_CHORD_POWER_VOLUME_UP_GLOBAL_ACTIONS);
435         } else {
436             // We're restoring "LPP for Assistant Disabled" state, prefer OEM config.xml Behavior
437             // if possible.
438             int longPressOnPowerDeviceBehavior = mContext.getResources().getInteger(
439                     com.android.internal.R.integer.config_longPressOnPowerBehavior);
440             if (longPressOnPowerDeviceBehavior == LONG_PRESS_POWER_FOR_ASSISTANT) {
441                 // The default on device IS "LPP for Assistant Enabled" so fall back to power menu.
442                 Settings.Global.putInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
443                         LONG_PRESS_POWER_GLOBAL_ACTIONS);
444             } else {
445                 // The default is non-Assistant Behavior, so restore that default.
446                 Settings.Global.putInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
447                         longPressOnPowerDeviceBehavior);
448             }
449 
450             // Clear and restore default power + volume up Behavior as well.
451             int powerVolumeUpDefaultBehavior = mContext.getResources().getInteger(
452                     com.android.internal.R.integer.config_keyChordPowerVolumeUp);
453             Settings.Global.putInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
454                     powerVolumeUpDefaultBehavior);
455         }
456     }
457 
getLocaleData()458     /* package */ byte[] getLocaleData() {
459         Configuration conf = mContext.getResources().getConfiguration();
460         return conf.getLocales().toLanguageTags().getBytes();
461     }
462 
toFullLocale(@onNull Locale locale)463     private static Locale toFullLocale(@NonNull Locale locale) {
464         if (locale.getScript().isEmpty() || locale.getCountry().isEmpty()) {
465             return ULocale.addLikelySubtags(ULocale.forLocale(locale)).toLocale();
466         }
467         return locale;
468     }
469 
470     /**
471      * Merging the locale came from backup server and current device locale.
472      *
473      * Merge works with following rules.
474      * - The backup locales are appended to the current locale with keeping order.
475      *   e.g. current locale "en-US,zh-CN" and backup locale "ja-JP,ko-KR" are merged to
476      *   "en-US,zh-CH,ja-JP,ko-KR".
477      *
478      * - Duplicated locales are dropped.
479      *   e.g. current locale "en-US,zh-CN" and backup locale "ja-JP,zh-Hans-CN,en-US" are merged to
480      *   "en-US,zh-CN,ja-JP".
481      *
482      * - Unsupported locales are dropped.
483      *   e.g. current locale "en-US" and backup locale "ja-JP,zh-CN" but the supported locales
484      *   are "en-US,zh-CN", the merged locale list is "en-US,zh-CN".
485      *
486      * - The final result locale list only contains the supported locales.
487      *   e.g. current locale "en-US" and backup locale "zh-Hans-CN" and supported locales are
488      *   "en-US,zh-CN", the merged locale list is "en-US,zh-CN".
489      *
490      * @param restore The locale list that came from backup server.
491      * @param current The device's locale setting.
492      * @param supportedLocales The list of language tags supported by this device.
493      */
494     @VisibleForTesting
resolveLocales(LocaleList restore, LocaleList current, String[] supportedLocales)495     public static LocaleList resolveLocales(LocaleList restore, LocaleList current,
496             String[] supportedLocales) {
497         final HashMap<Locale, Locale> allLocales = new HashMap<>(supportedLocales.length);
498         for (String supportedLocaleStr : supportedLocales) {
499             final Locale locale = Locale.forLanguageTag(supportedLocaleStr);
500             allLocales.put(toFullLocale(locale), locale);
501         }
502 
503         final ArrayList<Locale> filtered = new ArrayList<>(current.size());
504         for (int i = 0; i < current.size(); i++) {
505             final Locale locale = current.get(i);
506             allLocales.remove(toFullLocale(locale));
507             filtered.add(locale);
508         }
509 
510         for (int i = 0; i < restore.size(); i++) {
511             final Locale locale = allLocales.remove(toFullLocale(restore.get(i)));
512             if (locale != null) {
513                 filtered.add(locale);
514             }
515         }
516 
517         if (filtered.size() == current.size()) {
518             return current;  // Nothing added to current locale list.
519         }
520 
521         return new LocaleList(filtered.toArray(new Locale[filtered.size()]));
522     }
523 
524     /**
525      * Sets the locale specified. Input data is the byte representation of comma separated
526      * multiple BCP-47 language tags. For backwards compatibility, strings of the form
527      * {@code ll_CC} are also accepted, where {@code ll} is a two letter language
528      * code and {@code CC} is a two letter country code.
529      *
530      * @param data the comma separated BCP-47 language tags in bytes.
531      */
setLocaleData(byte[] data, int size)532     /* package */ void setLocaleData(byte[] data, int size) {
533         final Configuration conf = mContext.getResources().getConfiguration();
534 
535         // Replace "_" with "-" to deal with older backups.
536         final String localeCodes = new String(data, 0, size).replace('_', '-');
537         final LocaleList localeList = LocaleList.forLanguageTags(localeCodes);
538         if (localeList.isEmpty()) {
539             return;
540         }
541 
542         final String[] supportedLocales = LocalePicker.getSupportedLocales(mContext);
543         final LocaleList currentLocales = conf.getLocales();
544 
545         final LocaleList merged = resolveLocales(localeList, currentLocales, supportedLocales);
546         if (merged.equals(currentLocales)) {
547             return;
548         }
549 
550         try {
551             IActivityManager am = ActivityManager.getService();
552             final Configuration config = new Configuration();
553             config.setLocales(merged);
554             // indicate this isn't some passing default - the user wants this remembered
555             config.userSetLocale = true;
556 
557             am.updatePersistentConfigurationWithAttribution(config, mContext.getOpPackageName(),
558                     mContext.getAttributionTag());
559         } catch (RemoteException e) {
560             // Intentionally left blank
561         }
562     }
563 
564     /**
565      * Informs the audio service of changes to the settings so that
566      * they can be re-read and applied.
567      */
applyAudioSettings()568     void applyAudioSettings() {
569         AudioManager am = new AudioManager(mContext);
570         am.reloadAudioSettings();
571     }
572 }
573