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