From 220f4d633be1098e7887dbd06f179138bf19f1ad Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Thu, 2 Jul 2009 02:34:14 -0700 Subject: [PATCH] System and Secure settings backup. This backs up the basic system and secure settings. THe restoration doesn't take effect immediately. You many need to restart the runtime to see all restored values take effect. --- core/java/android/provider/Settings.java | 71 ++++ packages/SettingsProvider/AndroidManifest.xml | 1 + .../settings/SettingsBackupAgent.java | 320 ++++++++++++++++++ .../providers/settings/SettingsHelper.java | 104 ++++++ .../providers/settings/SettingsProvider.java | 17 +- 5 files changed, 511 insertions(+), 2 deletions(-) create mode 100644 packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java create mode 100644 packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 4a40058d59004..70173336af263 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1337,6 +1337,49 @@ public final class Settings { */ public static final String SHOW_WEB_SUGGESTIONS = "show_web_suggestions"; + /** + * Settings to backup. This is here so that it's in the same place as the settings + * keys and easy to update. + * @hide + */ + public static final String[] SETTINGS_TO_BACKUP = { + STAY_ON_WHILE_PLUGGED_IN, + END_BUTTON_BEHAVIOR, + WIFI_SLEEP_POLICY, + WIFI_USE_STATIC_IP, + WIFI_STATIC_IP, + WIFI_STATIC_GATEWAY, + WIFI_STATIC_NETMASK, + WIFI_STATIC_DNS1, + WIFI_STATIC_DNS2, + BLUETOOTH_DISCOVERABILITY, + BLUETOOTH_DISCOVERABILITY_TIMEOUT, + DIM_SCREEN, + SCREEN_OFF_TIMEOUT, + SCREEN_BRIGHTNESS, + VIBRATE_ON, + NOTIFICATIONS_USE_RING_VOLUME, + RINGTONE, + NOTIFICATION_SOUND, + TEXT_AUTO_REPLACE, + TEXT_AUTO_CAPS, + TEXT_AUTO_PUNCTUATE, + TEXT_SHOW_PASSWORD, + AUTO_TIME, + TIME_12_24, + DATE_FORMAT, + ACCELEROMETER_ROTATION, + DTMF_TONE_WHEN_DIALING, + DTMF_TONE_TYPE_WHEN_DIALING, + EMERGENCY_TONE, + CALL_AUTO_RETRY, + HEARING_AID, + TTY_MODE, + SOUND_EFFECTS_ENABLED, + HAPTIC_FEEDBACK_ENABLED, + SHOW_WEB_SUGGESTIONS + }; + // Settings moved to Settings.Secure /** @@ -2245,6 +2288,34 @@ public final class Settings { */ public static final String LAST_SETUP_SHOWN = "last_setup_shown"; + /** + * @hide + */ + public static final String[] SETTINGS_TO_BACKUP = { + INSTALL_NON_MARKET_APPS, + PARENTAL_CONTROL_ENABLED, + PARENTAL_CONTROL_REDIRECT_URL, + USB_MASS_STORAGE_ENABLED, + ACCESSIBILITY_ENABLED, + ENABLED_ACCESSIBILITY_SERVICES, + TTS_USE_DEFAULTS, + TTS_DEFAULT_RATE, + TTS_DEFAULT_PITCH, + TTS_DEFAULT_SYNTH, + TTS_DEFAULT_LANG, + TTS_DEFAULT_COUNTRY, + WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, + WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, + WIFI_NUM_ALLOWED_CHANNELS, + WIFI_NUM_OPEN_NETWORKS_KEPT, + BACKGROUND_DATA, + PREFERRED_NETWORK_MODE, + PREFERRED_TTY_MODE, + CDMA_CELL_BROADCAST_SMS, + PREFERRED_CDMA_SUBSCRIPTION, + ENHANCED_VOICE_PRIVACY_ENABLED + }; + /** * Helper method for determining if a location provider is enabled. * @param cr the content resolver to use diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml index 2407d877a1803..9109606baee9b 100644 --- a/packages/SettingsProvider/AndroidManifest.xml +++ b/packages/SettingsProvider/AndroidManifest.xml @@ -4,6 +4,7 @@ 0? new String(settings, pos, length) : null; + pos += length; + length = readInt(settings, pos); + pos += 4; + String settingValue = length > 0? new String(settings, pos, length) : null; + pos += length; + if (!TextUtils.isEmpty(settingName) && !TextUtils.isEmpty(settingValue)) { + //Log.i(TAG, "Restore " + settingName + " = " + settingValue); + cv.clear(); + cv.put(Settings.NameValueTable.NAME, settingName); + cv.put(Settings.NameValueTable.VALUE, settingValue); + getContentResolver().insert(contentUri, cv); + mSettingsHelper.restoreValue(settingName, settingValue); + } + } + } + + private String[] copyAndSort(String[] keys) { + String[] sortedKeys = new String[keys.length]; + System.arraycopy(keys, 0, sortedKeys, 0, keys.length); + Arrays.sort(sortedKeys); + return sortedKeys; + } + + /** + * Given a cursor sorted by key name and a set of keys sorted by name, + * extract the required keys and values and write them to a byte array. + * @param sortedCursor + * @param sortedKeys + * @return + */ + byte[] extractRelevantValues(Cursor sortedCursor, String[] sortedKeys) { + byte[][] values = new byte[sortedKeys.length * 2][]; // keys and values + if (!sortedCursor.moveToFirst()) { + Log.e(TAG, "Couldn't read from the cursor"); + return new byte[0]; + } + int keyIndex = 0; + int totalSize = 0; + while (!sortedCursor.isAfterLast()) { + String name = sortedCursor.getString(COLUMN_NAME); + while (sortedKeys[keyIndex].compareTo(name.toString()) < 0) { + keyIndex++; + if (keyIndex == sortedKeys.length) break; + } + if (keyIndex < sortedKeys.length && name.equals(sortedKeys[keyIndex])) { + String value = sortedCursor.getString(COLUMN_VALUE); + byte[] nameBytes = name.toString().getBytes(); + totalSize += 4 + nameBytes.length; + values[keyIndex * 2] = nameBytes; + byte[] valueBytes; + if (TextUtils.isEmpty(value)) { + valueBytes = null; + totalSize += 4; + } else { + valueBytes = value.toString().getBytes(); + totalSize += 4 + valueBytes.length; + //Log.i(TAG, "Backing up " + name + " = " + value); + } + values[keyIndex * 2 + 1] = valueBytes; + keyIndex++; + } + if (keyIndex == sortedKeys.length || !sortedCursor.moveToNext()) { + break; + } + } + + byte[] result = new byte[totalSize]; + int pos = 0; + for (int i = 0; i < sortedKeys.length * 2; i++) { + if (values[i] != null) { + pos = writeInt(result, pos, values[i].length); + pos = writeBytes(result, pos, values[i]); + } + } + return result; + } + + private void backupFile(String filename, BackupDataOutput data) { + try { + File file = new File(filename); + if (file.exists()) { + byte[] bytes = new byte[(int) file.length()]; + FileInputStream fis = new FileInputStream(file); + int offset = 0; + int got = 0; + do { + got = fis.read(bytes, offset, bytes.length - offset); + if (got > 0) offset += got; + } while (offset < bytes.length && got > 0); + data.writeEntityHeader(filename, bytes.length); + data.writeEntityData(bytes, bytes.length); + } else { + data.writeEntityHeader(filename, 0); + data.writeEntityData(EMPTY_DATA, 0); + } + } catch (IOException ioe) { + Log.w(TAG, "Couldn't backup " + filename); + } + } + + private void restoreFile(String filename, BackupDataInput data) { + byte[] bytes = new byte[data.getDataSize()]; + if (bytes.length <= 0) return; + try { + data.readEntityData(bytes, 0, bytes.length); + FileOutputStream fos = new FileOutputStream(filename); + fos.write(bytes); + } catch (IOException ioe) { + Log.w(TAG, "Couldn't restore " + filename); + } + } + + /** + * Write an int in BigEndian into the byte array. + * @param out byte array + * @param pos current pos in array + * @param value integer to write + * @return the index after adding the size of an int (4) + */ + private int writeInt(byte[] out, int pos, int value) { + out[pos + 0] = (byte) ((value >> 24) & 0xFF); + out[pos + 1] = (byte) ((value >> 16) & 0xFF); + out[pos + 2] = (byte) ((value >> 8) & 0xFF); + out[pos + 3] = (byte) ((value >> 0) & 0xFF); + return pos + 4; + } + + private int writeBytes(byte[] out, int pos, byte[] value) { + System.arraycopy(value, 0, out, pos, value.length); + return pos + value.length; + } + + private int readInt(byte[] in, int pos) { + int result = + ((in[pos ] & 0xFF) << 24) | + ((in[pos + 1] & 0xFF) << 16) | + ((in[pos + 2] & 0xFF) << 8) | + ((in[pos + 3] & 0xFF) << 0); + return result; + } + + private void enableWifi(boolean enable) { + WifiManager wfm = (WifiManager) getSystemService(Context.WIFI_SERVICE); + if (wfm != null) { + wfm.setWifiEnabled(enable); + } + } + + private void enableBluetooth(boolean enable) { + BluetoothDevice bt = (BluetoothDevice) getSystemService(Context.BLUETOOTH_SERVICE); + if (bt != null) { + if (!enable) { + bt.disable(); + } else { + bt.enable(); + } + } + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java new file mode 100644 index 0000000000000..b0655872df29c --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.settings; + +import android.backup.BackupDataInput; +import android.content.ContentResolver; +import android.content.Context; +import android.media.AudioManager; +import android.os.IHardwareService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.content.IContentService; +import android.util.Log; + +public class SettingsHelper { + private static final String TAG = "SettingsHelper"; + + private Context mContext; + private AudioManager mAudioManager; + private IContentService mContentService; + private static final String SYNC_AUTO = "auto_sync"; + private static final String SYNC_MAIL = "gmail-ls_sync"; + private static final String SYNC_CALENDAR = "calendar_sync"; + private static final String SYNC_CONTACTS = "contacts_sync"; + + public SettingsHelper(Context context) { + mContext = context; + mAudioManager = (AudioManager) context + .getSystemService(Context.AUDIO_SERVICE); + mContentService = ContentResolver.getContentService(); + } + + public void restoreValue(String name, String value) { + if (Settings.System.SCREEN_BRIGHTNESS.equals(name)) { + setBrightness(Integer.parseInt(value)); + } else if (Settings.System.SOUND_EFFECTS_ENABLED.equals(name)) { + if (Integer.parseInt(value) == 1) { + mAudioManager.loadSoundEffects(); + } else { + mAudioManager.unloadSoundEffects(); + } + } + } + + private void setBrightness(int brightness) { + try { + IHardwareService hardware = IHardwareService.Stub + .asInterface(ServiceManager.getService("hardware")); + if (hardware != null) { + hardware.setBacklights(brightness); + } + } catch (RemoteException doe) { + + } + } + + static final String[] PROVIDERS = { "gmail-ls", "calendar", "contacts" }; + + byte[] getSyncProviders() { + byte[] sync = new byte[1 + PROVIDERS.length]; + try { + sync[0] = (byte) (mContentService.getListenForNetworkTickles() ? 1 : 0); + for (int i = 0; i < PROVIDERS.length; i++) { + sync[i + 1] = (byte) + (mContentService.getSyncProviderAutomatically(PROVIDERS[i]) ? 1 : 0); + } + } catch (RemoteException re) { + Log.w(TAG, "Unable to backup sync providers"); + return sync; + } + return sync; + } + + void setSyncProviders(BackupDataInput backup) { + byte[] sync = new byte[backup.getDataSize()]; + + try { + backup.readEntityData(sync, 0, sync.length); + mContentService.setListenForNetworkTickles(sync[0] == 1); + for (int i = 0; i < PROVIDERS.length; i++) { + mContentService.setSyncProviderAutomatically(PROVIDERS[i], sync[i + 1] > 0); + } + } catch (RemoteException re) { + Log.w(TAG, "Unable to restore sync providers"); + } catch (java.io.IOException ioe) { + Log.w(TAG, "Unable to read sync settings"); + } + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 3db52ebd00bf0..a21bf320e7a19 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -16,6 +16,9 @@ package com.android.providers.settings; +import java.io.FileNotFoundException; + +import android.backup.IBackupManager; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; @@ -27,6 +30,7 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.media.RingtoneManager; import android.net.Uri; import android.os.ParcelFileDescriptor; +import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.DrmStore; import android.provider.MediaStore; @@ -34,8 +38,6 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Log; -import java.io.FileNotFoundException; - public class SettingsProvider extends ContentProvider { private static final String TAG = "SettingsProvider"; private static final boolean LOCAL_LOGV = false; @@ -137,6 +139,17 @@ public class SettingsProvider extends ContentProvider { SystemProperties.set(property, Long.toString(version)); } + // Inform the backup manager about a data change + IBackupManager ibm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + if (ibm != null) { + try { + ibm.dataChanged(getContext().getPackageName()); + } catch (Exception e) { + // Try again later + } + } + // Now send the notification through the content framework. String notify = uri.getQueryParameter("notify");