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