• 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 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