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