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