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.app.ActivityManagerNative; 20 import android.app.IActivityManager; 21 import android.app.backup.IBackupManager; 22 import android.content.ContentResolver; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.res.Configuration; 27 import android.location.LocationManager; 28 import android.media.AudioManager; 29 import android.media.RingtoneManager; 30 import android.net.Uri; 31 import android.os.IPowerManager; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.provider.Settings; 37 import android.telephony.TelephonyManager; 38 import android.text.TextUtils; 39 import android.util.ArraySet; 40 41 import java.util.Locale; 42 43 public 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>(4); 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 } 71 72 private interface SettingsLookup { 73 public String lookup(ContentResolver resolver, String name, int userHandle); 74 } 75 76 private static SettingsLookup sSystemLookup = new SettingsLookup() { 77 public String lookup(ContentResolver resolver, String name, int userHandle) { 78 return Settings.System.getStringForUser(resolver, name, userHandle); 79 } 80 }; 81 82 private static SettingsLookup sSecureLookup = new SettingsLookup() { 83 public String lookup(ContentResolver resolver, String name, int userHandle) { 84 return Settings.Secure.getStringForUser(resolver, name, userHandle); 85 } 86 }; 87 88 private static SettingsLookup sGlobalLookup = new SettingsLookup() { 89 public String lookup(ContentResolver resolver, String name, int userHandle) { 90 return Settings.Global.getStringForUser(resolver, name, userHandle); 91 } 92 }; 93 94 public SettingsHelper(Context context) { 95 mContext = context; 96 mAudioManager = (AudioManager) context 97 .getSystemService(Context.AUDIO_SERVICE); 98 mTelephonyManager = (TelephonyManager) context 99 .getSystemService(Context.TELEPHONY_SERVICE); 100 } 101 102 /** 103 * Sets the property via a call to the appropriate API, if any, and returns 104 * whether or not the setting should be saved to the database as well. 105 * @param name the name of the setting 106 * @param value the string value of the setting 107 * @return whether to continue with writing the value to the database. In 108 * some cases the data will be written by the call to the appropriate API, 109 * and in some cases the property value needs to be modified before setting. 110 */ 111 public void restoreValue(Context context, ContentResolver cr, ContentValues contentValues, 112 Uri destination, String name, String value) { 113 // Will we need a post-restore broadcast for this element? 114 String oldValue = null; 115 boolean sendBroadcast = false; 116 final SettingsLookup table; 117 118 if (destination.equals(Settings.Secure.CONTENT_URI)) { 119 table = sSecureLookup; 120 } else if (destination.equals(Settings.System.CONTENT_URI)) { 121 table = sSystemLookup; 122 } else { /* must be GLOBAL; this was preflighted by the caller */ 123 table = sGlobalLookup; 124 } 125 126 if (sBroadcastOnRestore.contains(name)) { 127 // TODO: http://b/22388012 128 oldValue = table.lookup(cr, name, UserHandle.USER_SYSTEM); 129 sendBroadcast = true; 130 } 131 132 try { 133 if (Settings.System.SCREEN_BRIGHTNESS.equals(name)) { 134 setBrightness(Integer.parseInt(value)); 135 // fall through to the ordinary write to settings 136 } else if (Settings.System.SOUND_EFFECTS_ENABLED.equals(name)) { 137 setSoundEffects(Integer.parseInt(value) == 1); 138 // fall through to the ordinary write to settings 139 } else if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) { 140 setGpsLocation(value); 141 return; 142 } else if (Settings.Secure.BACKUP_AUTO_RESTORE.equals(name)) { 143 setAutoRestore(Integer.parseInt(value) == 1); 144 } else if (isAlreadyConfiguredCriticalAccessibilitySetting(name)) { 145 return; 146 } else if (Settings.System.RINGTONE.equals(name) 147 || Settings.System.NOTIFICATION_SOUND.equals(name)) { 148 setRingtone(name, value); 149 return; 150 } 151 152 // Default case: write the restored value to settings 153 contentValues.clear(); 154 contentValues.put(Settings.NameValueTable.NAME, name); 155 contentValues.put(Settings.NameValueTable.VALUE, value); 156 cr.insert(destination, contentValues); 157 } catch (Exception e) { 158 // If we fail to apply the setting, by definition nothing happened 159 sendBroadcast = false; 160 } finally { 161 // If this was an element of interest, send the "we just restored it" 162 // broadcast with the historical value now that the new value has 163 // been committed and observers kicked off. 164 if (sendBroadcast) { 165 Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED) 166 .setPackage("android").addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) 167 .putExtra(Intent.EXTRA_SETTING_NAME, name) 168 .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, value) 169 .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, oldValue); 170 context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null); 171 } 172 } 173 } 174 175 public String onBackupValue(String name, String value) { 176 // Special processing for backing up ringtones & notification sounds 177 if (Settings.System.RINGTONE.equals(name) 178 || Settings.System.NOTIFICATION_SOUND.equals(name)) { 179 if (value == null) { 180 if (Settings.System.RINGTONE.equals(name)) { 181 // For ringtones, we need to distinguish between non-telephony vs telephony 182 if (mTelephonyManager != null && mTelephonyManager.isVoiceCapable()) { 183 // Backup a null ringtone as silent on voice-capable devices 184 return SILENT_RINGTONE; 185 } else { 186 // Skip backup of ringtone on non-telephony devices. 187 return null; 188 } 189 } else { 190 // Backup a null notification sound as silent 191 return SILENT_RINGTONE; 192 } 193 } else { 194 return getCanonicalRingtoneValue(value); 195 } 196 } 197 // Return the original value 198 return value; 199 } 200 201 /** 202 * Sets the ringtone of type specified by the name. 203 * 204 * @param name should be Settings.System.RINGTONE or Settings.System.NOTIFICATION_SOUND. 205 * @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone. 206 */ 207 private void setRingtone(String name, String value) { 208 // If it's null, don't change the default 209 if (value == null) return; 210 Uri ringtoneUri = null; 211 if (SILENT_RINGTONE.equals(value)) { 212 ringtoneUri = null; 213 } else { 214 Uri canonicalUri = Uri.parse(value); 215 ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri); 216 if (ringtoneUri == null) { 217 // Unrecognized or invalid Uri, don't restore 218 return; 219 } 220 } 221 final int ringtoneType = Settings.System.RINGTONE.equals(name) 222 ? RingtoneManager.TYPE_RINGTONE : RingtoneManager.TYPE_NOTIFICATION; 223 RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri); 224 } 225 226 private String getCanonicalRingtoneValue(String value) { 227 final Uri ringtoneUri = Uri.parse(value); 228 final Uri canonicalUri = mContext.getContentResolver().canonicalize(ringtoneUri); 229 return canonicalUri == null ? null : canonicalUri.toString(); 230 } 231 232 private boolean isAlreadyConfiguredCriticalAccessibilitySetting(String name) { 233 // These are the critical accessibility settings that are required for users with 234 // accessibility needs to be able to interact with the device. If these settings are 235 // already configured, we will not overwrite them. If they are already set, 236 // it means that the user has performed a global gesture to enable accessibility or set 237 // these settings in the Accessibility portion of the Setup Wizard, and definitely needs 238 // these features working after the restore. 239 switch (name) { 240 case Settings.Secure.ACCESSIBILITY_ENABLED: 241 case Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION: 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 = ActivityManagerNative.getDefault(); 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