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