• 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 com.android.internal.R;
20 import com.android.internal.app.LocalePicker;
21 import com.android.internal.annotations.VisibleForTesting;
22 
23 import android.annotation.NonNull;
24 import android.app.ActivityManager;
25 import android.app.IActivityManager;
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.res.Configuration;
32 import android.icu.util.ULocale;
33 import android.location.LocationManager;
34 import android.media.AudioManager;
35 import android.media.RingtoneManager;
36 import android.net.Uri;
37 import android.os.IPowerManager;
38 import android.os.LocaleList;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.provider.Settings;
44 import android.telephony.TelephonyManager;
45 import android.text.TextUtils;
46 import android.util.ArraySet;
47 import android.util.Slog;
48 
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Locale;
53 
54 public class SettingsHelper {
55     private static final String TAG = "SettingsHelper";
56     private static final String SILENT_RINGTONE = "_silent";
57     private Context mContext;
58     private AudioManager mAudioManager;
59     private TelephonyManager mTelephonyManager;
60 
61     /**
62      * A few settings elements are special in that a restore of those values needs to
63      * be post-processed by relevant parts of the OS.  A restore of any settings element
64      * mentioned in this table will therefore cause the system to send a broadcast with
65      * the {@link Intent#ACTION_SETTING_RESTORED} action, with extras naming the
66      * affected setting and supplying its pre-restore value for comparison.
67      *
68      * @see Intent#ACTION_SETTING_RESTORED
69      * @see System#SETTINGS_TO_BACKUP
70      * @see Secure#SETTINGS_TO_BACKUP
71      * @see Global#SETTINGS_TO_BACKUP
72      *
73      * {@hide}
74      */
75     private static final ArraySet<String> sBroadcastOnRestore;
76     static {
77         sBroadcastOnRestore = new ArraySet<String>(5);
78         sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
79         sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS);
80         sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
81         sBroadcastOnRestore.add(Settings.Secure.ENABLED_INPUT_METHODS);
82         sBroadcastOnRestore.add(Settings.Global.BLUETOOTH_ON);
83     }
84 
85     private interface SettingsLookup {
lookup(ContentResolver resolver, String name, int userHandle)86         public String lookup(ContentResolver resolver, String name, int userHandle);
87     }
88 
89     private static SettingsLookup sSystemLookup = new SettingsLookup() {
90         public String lookup(ContentResolver resolver, String name, int userHandle) {
91             return Settings.System.getStringForUser(resolver, name, userHandle);
92         }
93     };
94 
95     private static SettingsLookup sSecureLookup = new SettingsLookup() {
96         public String lookup(ContentResolver resolver, String name, int userHandle) {
97             return Settings.Secure.getStringForUser(resolver, name, userHandle);
98         }
99     };
100 
101     private static SettingsLookup sGlobalLookup = new SettingsLookup() {
102         public String lookup(ContentResolver resolver, String name, int userHandle) {
103             return Settings.Global.getStringForUser(resolver, name, userHandle);
104         }
105     };
106 
SettingsHelper(Context context)107     public SettingsHelper(Context context) {
108         mContext = context;
109         mAudioManager = (AudioManager) context
110                 .getSystemService(Context.AUDIO_SERVICE);
111         mTelephonyManager = (TelephonyManager) context
112                 .getSystemService(Context.TELEPHONY_SERVICE);
113     }
114 
115     /**
116      * Sets the property via a call to the appropriate API, if any, and returns
117      * whether or not the setting should be saved to the database as well.
118      * @param name the name of the setting
119      * @param value the string value of the setting
120      * @return whether to continue with writing the value to the database. In
121      * some cases the data will be written by the call to the appropriate API,
122      * and in some cases the property value needs to be modified before setting.
123      */
restoreValue(Context context, ContentResolver cr, ContentValues contentValues, Uri destination, String name, String value, int restoredFromSdkInt)124     public void restoreValue(Context context, ContentResolver cr, ContentValues contentValues,
125             Uri destination, String name, String value, int restoredFromSdkInt) {
126         // Will we need a post-restore broadcast for this element?
127         String oldValue = null;
128         boolean sendBroadcast = false;
129         final SettingsLookup table;
130 
131         if (destination.equals(Settings.Secure.CONTENT_URI)) {
132             table = sSecureLookup;
133         } else if (destination.equals(Settings.System.CONTENT_URI)) {
134             table = sSystemLookup;
135         } else { /* must be GLOBAL; this was preflighted by the caller */
136             table = sGlobalLookup;
137         }
138 
139         if (sBroadcastOnRestore.contains(name)) {
140             // TODO: http://b/22388012
141             oldValue = table.lookup(cr, name, UserHandle.USER_SYSTEM);
142             sendBroadcast = true;
143         }
144 
145         try {
146             if (Settings.System.SCREEN_BRIGHTNESS.equals(name)) {
147                 setBrightness(Integer.parseInt(value));
148                 // fall through to the ordinary write to settings
149             } else if (Settings.System.SOUND_EFFECTS_ENABLED.equals(name)) {
150                 setSoundEffects(Integer.parseInt(value) == 1);
151                 // fall through to the ordinary write to settings
152             } else if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
153                 setGpsLocation(value);
154                 return;
155             } else if (Settings.Secure.BACKUP_AUTO_RESTORE.equals(name)) {
156                 setAutoRestore(Integer.parseInt(value) == 1);
157             } else if (isAlreadyConfiguredCriticalAccessibilitySetting(name)) {
158                 return;
159             } else if (Settings.System.RINGTONE.equals(name)
160                     || Settings.System.NOTIFICATION_SOUND.equals(name)) {
161                 setRingtone(name, value);
162                 return;
163             }
164 
165             // Default case: write the restored value to settings
166             contentValues.clear();
167             contentValues.put(Settings.NameValueTable.NAME, name);
168             contentValues.put(Settings.NameValueTable.VALUE, value);
169             cr.insert(destination, contentValues);
170         } catch (Exception e) {
171             // If we fail to apply the setting, by definition nothing happened
172             sendBroadcast = false;
173         } finally {
174             // If this was an element of interest, send the "we just restored it"
175             // broadcast with the historical value now that the new value has
176             // been committed and observers kicked off.
177             if (sendBroadcast) {
178                 Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
179                         .setPackage("android").addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
180                         .putExtra(Intent.EXTRA_SETTING_NAME, name)
181                         .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, value)
182                         .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, oldValue)
183                         .putExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, restoredFromSdkInt);
184                 context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
185             }
186         }
187     }
188 
onBackupValue(String name, String value)189     public String onBackupValue(String name, String value) {
190         // Special processing for backing up ringtones & notification sounds
191         if (Settings.System.RINGTONE.equals(name)
192                 || Settings.System.NOTIFICATION_SOUND.equals(name)) {
193             if (value == null) {
194                 if (Settings.System.RINGTONE.equals(name)) {
195                     // For ringtones, we need to distinguish between non-telephony vs telephony
196                     if (mTelephonyManager != null && mTelephonyManager.isVoiceCapable()) {
197                         // Backup a null ringtone as silent on voice-capable devices
198                         return SILENT_RINGTONE;
199                     } else {
200                         // Skip backup of ringtone on non-telephony devices.
201                         return null;
202                     }
203                 } else {
204                     // Backup a null notification sound as silent
205                     return SILENT_RINGTONE;
206                 }
207             } else {
208                 return getCanonicalRingtoneValue(value);
209             }
210         }
211         // Return the original value
212         return value;
213     }
214 
215     /**
216      * Sets the ringtone of type specified by the name.
217      *
218      * @param name should be Settings.System.RINGTONE or Settings.System.NOTIFICATION_SOUND.
219      * @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone.
220      */
setRingtone(String name, String value)221     private void setRingtone(String name, String value) {
222         // If it's null, don't change the default
223         if (value == null) return;
224         Uri ringtoneUri = null;
225         if (SILENT_RINGTONE.equals(value)) {
226             ringtoneUri = null;
227         } else {
228             Uri canonicalUri = Uri.parse(value);
229             ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri);
230             if (ringtoneUri == null) {
231                 // Unrecognized or invalid Uri, don't restore
232                 return;
233             }
234         }
235         final int ringtoneType = Settings.System.RINGTONE.equals(name)
236                 ? RingtoneManager.TYPE_RINGTONE : RingtoneManager.TYPE_NOTIFICATION;
237         RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri);
238     }
239 
getCanonicalRingtoneValue(String value)240     private String getCanonicalRingtoneValue(String value) {
241         final Uri ringtoneUri = Uri.parse(value);
242         final Uri canonicalUri = mContext.getContentResolver().canonicalize(ringtoneUri);
243         return canonicalUri == null ? null : canonicalUri.toString();
244     }
245 
isAlreadyConfiguredCriticalAccessibilitySetting(String name)246     private boolean isAlreadyConfiguredCriticalAccessibilitySetting(String name) {
247         // These are the critical accessibility settings that are required for users with
248         // accessibility needs to be able to interact with the device. If these settings are
249         // already configured, we will not overwrite them. If they are already set,
250         // it means that the user has performed a global gesture to enable accessibility or set
251         // these settings in the Accessibility portion of the Setup Wizard, and definitely needs
252         // these features working after the restore.
253         switch (name) {
254             case Settings.Secure.ACCESSIBILITY_ENABLED:
255             case Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD:
256             case Settings.Secure.TOUCH_EXPLORATION_ENABLED:
257             case Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED:
258             case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED:
259             case Settings.Secure.UI_NIGHT_MODE:
260                 return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
261             case Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES:
262             case Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES:
263             case Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER:
264             case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE:
265                 return !TextUtils.isEmpty(Settings.Secure.getString(
266                         mContext.getContentResolver(), name));
267             case Settings.System.FONT_SCALE:
268                 return Settings.System.getFloat(mContext.getContentResolver(), name, 1.0f) != 1.0f;
269             default:
270                 return false;
271         }
272     }
273 
setAutoRestore(boolean enabled)274     private void setAutoRestore(boolean enabled) {
275         try {
276             IBackupManager bm = IBackupManager.Stub.asInterface(
277                     ServiceManager.getService(Context.BACKUP_SERVICE));
278             if (bm != null) {
279                 bm.setAutoRestore(enabled);
280             }
281         } catch (RemoteException e) {}
282     }
283 
setGpsLocation(String value)284     private void setGpsLocation(String value) {
285         UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
286         if (um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) {
287             return;
288         }
289         final String GPS = LocationManager.GPS_PROVIDER;
290         boolean enabled =
291                 GPS.equals(value) ||
292                 value.startsWith(GPS + ",") ||
293                 value.endsWith("," + GPS) ||
294                 value.contains("," + GPS + ",");
295         Settings.Secure.setLocationProviderEnabled(
296                 mContext.getContentResolver(), GPS, enabled);
297     }
298 
setSoundEffects(boolean enable)299     private void setSoundEffects(boolean enable) {
300         if (enable) {
301             mAudioManager.loadSoundEffects();
302         } else {
303             mAudioManager.unloadSoundEffects();
304         }
305     }
306 
setBrightness(int brightness)307     private void setBrightness(int brightness) {
308         try {
309             IPowerManager power = IPowerManager.Stub.asInterface(
310                     ServiceManager.getService("power"));
311             if (power != null) {
312                 power.setTemporaryScreenBrightnessSettingOverride(brightness);
313             }
314         } catch (RemoteException doe) {
315 
316         }
317     }
318 
getLocaleData()319     /* package */ byte[] getLocaleData() {
320         Configuration conf = mContext.getResources().getConfiguration();
321         return conf.getLocales().toLanguageTags().getBytes();
322     }
323 
toFullLocale(@onNull Locale locale)324     private static Locale toFullLocale(@NonNull Locale locale) {
325         if (locale.getScript().isEmpty() || locale.getCountry().isEmpty()) {
326             return ULocale.addLikelySubtags(ULocale.forLocale(locale)).toLocale();
327         }
328         return locale;
329     }
330 
331     /**
332      * Merging the locale came from backup server and current device locale.
333      *
334      * Merge works with following rules.
335      * - The backup locales are appended to the current locale with keeping order.
336      *   e.g. current locale "en-US,zh-CN" and backup locale "ja-JP,ko-KR" are merged to
337      *   "en-US,zh-CH,ja-JP,ko-KR".
338      *
339      * - Duplicated locales are dropped.
340      *   e.g. current locale "en-US,zh-CN" and backup locale "ja-JP,zh-Hans-CN,en-US" are merged to
341      *   "en-US,zh-CN,ja-JP".
342      *
343      * - Unsupported locales are dropped.
344      *   e.g. current locale "en-US" and backup locale "ja-JP,zh-CN" but the supported locales
345      *   are "en-US,zh-CN", the merged locale list is "en-US,zh-CN".
346      *
347      * - The final result locale list only contains the supported locales.
348      *   e.g. current locale "en-US" and backup locale "zh-Hans-CN" and supported locales are
349      *   "en-US,zh-CN", the merged locale list is "en-US,zh-CN".
350      *
351      * @param restore The locale list that came from backup server.
352      * @param current The device's locale setting.
353      * @param supportedLocales The list of language tags supported by this device.
354      */
355     @VisibleForTesting
resolveLocales(LocaleList restore, LocaleList current, String[] supportedLocales)356     public static LocaleList resolveLocales(LocaleList restore, LocaleList current,
357             String[] supportedLocales) {
358         final HashMap<Locale, Locale> allLocales = new HashMap<>(supportedLocales.length);
359         for (String supportedLocaleStr : supportedLocales) {
360             final Locale locale = Locale.forLanguageTag(supportedLocaleStr);
361             allLocales.put(toFullLocale(locale), locale);
362         }
363 
364         final ArrayList<Locale> filtered = new ArrayList<>(current.size());
365         for (int i = 0; i < current.size(); i++) {
366             final Locale locale = current.get(i);
367             allLocales.remove(toFullLocale(locale));
368             filtered.add(locale);
369         }
370 
371         for (int i = 0; i < restore.size(); i++) {
372             final Locale locale = allLocales.remove(toFullLocale(restore.get(i)));
373             if (locale != null) {
374                 filtered.add(locale);
375             }
376         }
377 
378         if (filtered.size() == current.size()) {
379             return current;  // Nothing added to current locale list.
380         }
381 
382         return new LocaleList(filtered.toArray(new Locale[filtered.size()]));
383     }
384 
385     /**
386      * Sets the locale specified. Input data is the byte representation of comma separated
387      * multiple BCP-47 language tags. For backwards compatibility, strings of the form
388      * {@code ll_CC} are also accepted, where {@code ll} is a two letter language
389      * code and {@code CC} is a two letter country code.
390      *
391      * @param data the comma separated BCP-47 language tags in bytes.
392      */
setLocaleData(byte[] data, int size)393     /* package */ void setLocaleData(byte[] data, int size) {
394         final Configuration conf = mContext.getResources().getConfiguration();
395 
396         // Replace "_" with "-" to deal with older backups.
397         final String localeCodes = new String(data, 0, size).replace('_', '-');
398         final LocaleList localeList = LocaleList.forLanguageTags(localeCodes);
399         if (localeList.isEmpty()) {
400             return;
401         }
402 
403         final String[] supportedLocales = LocalePicker.getSupportedLocales(mContext);
404         final LocaleList currentLocales = conf.getLocales();
405 
406         final LocaleList merged = resolveLocales(localeList, currentLocales, supportedLocales);
407         if (merged.equals(currentLocales)) {
408             return;
409         }
410 
411         try {
412             IActivityManager am = ActivityManager.getService();
413             Configuration config = am.getConfiguration();
414             config.setLocales(merged);
415             // indicate this isn't some passing default - the user wants this remembered
416             config.userSetLocale = true;
417 
418             am.updatePersistentConfiguration(config);
419         } catch (RemoteException e) {
420             // Intentionally left blank
421         }
422     }
423 
424     /**
425      * Informs the audio service of changes to the settings so that
426      * they can be re-read and applied.
427      */
applyAudioSettings()428     void applyAudioSettings() {
429         AudioManager am = new AudioManager(mContext);
430         am.reloadAudioSettings();
431     }
432 }
433