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