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