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