blob: fa63edbd9842b36275338cdf47e3e0efe8b1a52b [file] [log] [blame]
Amith Yamasani220f4d62009-07-02 02:34:14 -07001/*
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
Brad Fitzpatrick70787892010-11-17 11:31:12 -080019import java.io.BufferedOutputStream;
Amith Yamasani2cfab842009-09-09 18:27:31 -070020import java.io.BufferedReader;
21import java.io.BufferedWriter;
Amith Yamasanid1582142009-07-08 20:04:55 -070022import java.io.DataInputStream;
23import java.io.DataOutputStream;
Amith Yamasani2cfab842009-09-09 18:27:31 -070024import java.io.EOFException;
Amith Yamasani220f4d62009-07-02 02:34:14 -070025import java.io.File;
26import java.io.FileInputStream;
27import java.io.FileOutputStream;
Amith Yamasani2cfab842009-09-09 18:27:31 -070028import java.io.FileReader;
29import java.io.FileWriter;
Amith Yamasani220f4d62009-07-02 02:34:14 -070030import java.io.IOException;
Brad Fitzpatrick70787892010-11-17 11:31:12 -080031import java.io.OutputStream;
Amith Yamasani220f4d62009-07-02 02:34:14 -070032import java.util.Arrays;
Amith Yamasanid1582142009-07-08 20:04:55 -070033import java.util.zip.CRC32;
Amith Yamasani220f4d62009-07-02 02:34:14 -070034
Christopher Tate45281862010-03-05 15:46:30 -080035import android.app.backup.BackupDataInput;
36import android.app.backup.BackupDataOutput;
Christopher Tatecc84c692010-03-29 14:54:02 -070037import android.app.backup.BackupAgentHelper;
Amith Yamasani220f4d62009-07-02 02:34:14 -070038import android.content.ContentValues;
39import android.content.Context;
40import android.database.Cursor;
Amith Yamasani220f4d62009-07-02 02:34:14 -070041import android.net.Uri;
42import android.net.wifi.WifiManager;
Amith Yamasanid1582142009-07-08 20:04:55 -070043import android.os.FileUtils;
Amith Yamasani220f4d62009-07-02 02:34:14 -070044import android.os.ParcelFileDescriptor;
Amith Yamasanid1582142009-07-08 20:04:55 -070045import android.os.Process;
Amith Yamasani220f4d62009-07-02 02:34:14 -070046import android.provider.Settings;
47import android.text.TextUtils;
48import android.util.Log;
49
50/**
51 * Performs backup and restore of the System and Secure settings.
52 * List of settings that are backed up are stored in the Settings.java file
53 */
Christopher Tatecc84c692010-03-29 14:54:02 -070054public class SettingsBackupAgent extends BackupAgentHelper {
Christopher Tate436344a2009-09-30 16:17:37 -070055 private static final boolean DEBUG = false;
Amith Yamasani220f4d62009-07-02 02:34:14 -070056
57 private static final String KEY_SYSTEM = "system";
58 private static final String KEY_SECURE = "secure";
Amith Yamasani8823c0a82009-07-07 14:30:17 -070059 private static final String KEY_LOCALE = "locale";
Amith Yamasani220f4d62009-07-02 02:34:14 -070060
Christopher Tatea286f412009-09-18 15:51:15 -070061 // Versioning of the state file. Increment this version
62 // number any time the set of state items is altered.
63 private static final int STATE_VERSION = 1;
64
Amith Yamasanid1582142009-07-08 20:04:55 -070065 private static final int STATE_SYSTEM = 0;
66 private static final int STATE_SECURE = 1;
Christopher Tatea286f412009-09-18 15:51:15 -070067 private static final int STATE_LOCALE = 2;
68 private static final int STATE_WIFI = 3;
69 private static final int STATE_SIZE = 4; // The number of state items
Amith Yamasanid1582142009-07-08 20:04:55 -070070
Amith Yamasani220f4d62009-07-02 02:34:14 -070071 private static String[] sortedSystemKeys = null;
72 private static String[] sortedSecureKeys = null;
73
74 private static final byte[] EMPTY_DATA = new byte[0];
75
76 private static final String TAG = "SettingsBackupAgent";
77
Amith Yamasani220f4d62009-07-02 02:34:14 -070078 private static final int COLUMN_NAME = 1;
79 private static final int COLUMN_VALUE = 2;
80
81 private static final String[] PROJECTION = {
82 Settings.NameValueTable._ID,
83 Settings.NameValueTable.NAME,
84 Settings.NameValueTable.VALUE
85 };
86
87 private static final String FILE_WIFI_SUPPLICANT = "/data/misc/wifi/wpa_supplicant.conf";
Amith Yamasani2cfab842009-09-09 18:27:31 -070088 private static final String FILE_WIFI_SUPPLICANT_TEMPLATE =
89 "/system/etc/wifi/wpa_supplicant.conf";
Christian Sonntag92c17522009-08-07 15:16:17 -070090
91 // the key to store the WIFI data under, should be sorted as last, so restore happens last.
92 // use very late unicode character to quasi-guarantee last sort position.
Amith Yamasani2cfab842009-09-09 18:27:31 -070093 private static final String KEY_WIFI_SUPPLICANT = "\uffedWIFI";
Amith Yamasani220f4d62009-07-02 02:34:14 -070094
95 private SettingsHelper mSettingsHelper;
96
97 public void onCreate() {
98 mSettingsHelper = new SettingsHelper(this);
99 super.onCreate();
100 }
101
102 @Override
103 public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
104 ParcelFileDescriptor newState) throws IOException {
105
106 byte[] systemSettingsData = getSystemSettings();
107 byte[] secureSettingsData = getSecureSettings();
Amith Yamasani8823c0a82009-07-07 14:30:17 -0700108 byte[] locale = mSettingsHelper.getLocaleData();
Amith Yamasani2cfab842009-09-09 18:27:31 -0700109 byte[] wifiData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
Amith Yamasani220f4d62009-07-02 02:34:14 -0700110
Amith Yamasanid1582142009-07-08 20:04:55 -0700111 long[] stateChecksums = readOldChecksums(oldState);
Amith Yamasani220f4d62009-07-02 02:34:14 -0700112
Amith Yamasanid1582142009-07-08 20:04:55 -0700113 stateChecksums[STATE_SYSTEM] =
114 writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data);
115 stateChecksums[STATE_SECURE] =
116 writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data);
Amith Yamasanid1582142009-07-08 20:04:55 -0700117 stateChecksums[STATE_LOCALE] =
118 writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data);
119 stateChecksums[STATE_WIFI] =
Christian Sonntag92c17522009-08-07 15:16:17 -0700120 writeIfChanged(stateChecksums[STATE_WIFI], KEY_WIFI_SUPPLICANT, wifiData, data);
Amith Yamasani8823c0a82009-07-07 14:30:17 -0700121
Amith Yamasanid1582142009-07-08 20:04:55 -0700122 writeNewChecksums(stateChecksums, newState);
Amith Yamasani220f4d62009-07-02 02:34:14 -0700123 }
124
125 @Override
126 public void onRestore(BackupDataInput data, int appVersionCode,
127 ParcelFileDescriptor newState) throws IOException {
128
Amith Yamasani220f4d62009-07-02 02:34:14 -0700129 while (data.readNextHeader()) {
130 final String key = data.getKey();
Amith Yamasani8823c0a82009-07-07 14:30:17 -0700131 final int size = data.getDataSize();
Amith Yamasani220f4d62009-07-02 02:34:14 -0700132 if (KEY_SYSTEM.equals(key)) {
133 restoreSettings(data, Settings.System.CONTENT_URI);
Amith Yamasanid1582142009-07-08 20:04:55 -0700134 mSettingsHelper.applyAudioSettings();
Amith Yamasani220f4d62009-07-02 02:34:14 -0700135 } else if (KEY_SECURE.equals(key)) {
136 restoreSettings(data, Settings.Secure.CONTENT_URI);
Christian Sonntag92c17522009-08-07 15:16:17 -0700137 } else if (KEY_WIFI_SUPPLICANT.equals(key)) {
Christian Sonntagc5b5b0f2009-08-07 10:00:21 -0700138 int retainedWifiState = enableWifi(false);
Amith Yamasani2cfab842009-09-09 18:27:31 -0700139 restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, data);
Amith Yamasanid1582142009-07-08 20:04:55 -0700140 FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
141 FileUtils.S_IRUSR | FileUtils.S_IWUSR |
142 FileUtils.S_IRGRP | FileUtils.S_IWGRP,
143 Process.myUid(), Process.WIFI_UID);
Christian Sonntagc5b5b0f2009-08-07 10:00:21 -0700144 // retain the previous WIFI state.
145 enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED ||
146 retainedWifiState == WifiManager.WIFI_STATE_ENABLING);
Amith Yamasani8823c0a82009-07-07 14:30:17 -0700147 } else if (KEY_LOCALE.equals(key)) {
148 byte[] localeData = new byte[size];
149 data.readEntityData(localeData, 0, size);
150 mSettingsHelper.setLocaleData(localeData);
Amith Yamasani220f4d62009-07-02 02:34:14 -0700151 } else {
152 data.skipEntityData();
153 }
154 }
155 }
156
Amith Yamasanid1582142009-07-08 20:04:55 -0700157 private long[] readOldChecksums(ParcelFileDescriptor oldState) throws IOException {
158 long[] stateChecksums = new long[STATE_SIZE];
159
160 DataInputStream dataInput = new DataInputStream(
161 new FileInputStream(oldState.getFileDescriptor()));
Christopher Tatea286f412009-09-18 15:51:15 -0700162
163 try {
164 int stateVersion = dataInput.readInt();
165 if (stateVersion == STATE_VERSION) {
166 for (int i = 0; i < STATE_SIZE; i++) {
167 stateChecksums[i] = dataInput.readLong();
168 }
Amith Yamasanid1582142009-07-08 20:04:55 -0700169 }
Christopher Tatea286f412009-09-18 15:51:15 -0700170 } catch (EOFException eof) {
171 // With the default 0 checksum we'll wind up forcing a backup of
172 // any unhandled data sets, which is appropriate.
Amith Yamasanid1582142009-07-08 20:04:55 -0700173 }
174 dataInput.close();
175 return stateChecksums;
176 }
177
178 private void writeNewChecksums(long[] checksums, ParcelFileDescriptor newState)
179 throws IOException {
180 DataOutputStream dataOutput = new DataOutputStream(
181 new FileOutputStream(newState.getFileDescriptor()));
Christopher Tatea286f412009-09-18 15:51:15 -0700182
183 dataOutput.writeInt(STATE_VERSION);
Amith Yamasanid1582142009-07-08 20:04:55 -0700184 for (int i = 0; i < STATE_SIZE; i++) {
185 dataOutput.writeLong(checksums[i]);
186 }
187 dataOutput.close();
188 }
189
190 private long writeIfChanged(long oldChecksum, String key, byte[] data,
191 BackupDataOutput output) {
192 CRC32 checkSummer = new CRC32();
193 checkSummer.update(data);
194 long newChecksum = checkSummer.getValue();
195 if (oldChecksum == newChecksum) {
196 return oldChecksum;
197 }
198 try {
199 output.writeEntityHeader(key, data.length);
200 output.writeEntityData(data, data.length);
201 } catch (IOException ioe) {
202 // Bail
203 }
204 return newChecksum;
205 }
206
Amith Yamasani220f4d62009-07-02 02:34:14 -0700207 private byte[] getSystemSettings() {
208 Cursor sortedCursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION,
209 null, null, Settings.NameValueTable.NAME);
210 // Copy and sort the array
211 if (sortedSystemKeys == null) {
212 sortedSystemKeys = copyAndSort(Settings.System.SETTINGS_TO_BACKUP);
213 }
214 byte[] result = extractRelevantValues(sortedCursor, sortedSystemKeys);
215 sortedCursor.close();
216 return result;
217 }
218
219 private byte[] getSecureSettings() {
220 Cursor sortedCursor = getContentResolver().query(Settings.Secure.CONTENT_URI, PROJECTION,
221 null, null, Settings.NameValueTable.NAME);
222 // Copy and sort the array
223 if (sortedSecureKeys == null) {
224 sortedSecureKeys = copyAndSort(Settings.Secure.SETTINGS_TO_BACKUP);
225 }
226 byte[] result = extractRelevantValues(sortedCursor, sortedSecureKeys);
227 sortedCursor.close();
228 return result;
229 }
230
231 private void restoreSettings(BackupDataInput data, Uri contentUri) {
Christopher Tate796e0f02009-09-22 11:57:58 -0700232 if (DEBUG) Log.i(TAG, "restoreSettings: " + contentUri);
233 String[] whitelist = null;
234 if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
235 whitelist = Settings.Secure.SETTINGS_TO_BACKUP;
236 } else if (contentUri.equals(Settings.System.CONTENT_URI)) {
237 whitelist = Settings.System.SETTINGS_TO_BACKUP;
238 }
239
Amith Yamasani220f4d62009-07-02 02:34:14 -0700240 ContentValues cv = new ContentValues(2);
241 byte[] settings = new byte[data.getDataSize()];
242 try {
243 data.readEntityData(settings, 0, settings.length);
244 } catch (IOException ioe) {
245 Log.e(TAG, "Couldn't read entity data");
246 return;
247 }
248 int pos = 0;
249 while (pos < settings.length) {
250 int length = readInt(settings, pos);
251 pos += 4;
252 String settingName = length > 0? new String(settings, pos, length) : null;
253 pos += length;
254 length = readInt(settings, pos);
255 pos += 4;
256 String settingValue = length > 0? new String(settings, pos, length) : null;
257 pos += length;
258 if (!TextUtils.isEmpty(settingName) && !TextUtils.isEmpty(settingValue)) {
259 //Log.i(TAG, "Restore " + settingName + " = " + settingValue);
Christopher Tate0738e882009-09-11 16:35:39 -0700260
Christopher Tate796e0f02009-09-22 11:57:58 -0700261 // Only restore settings in our list of known-acceptable data
262 if (invalidSavedSetting(whitelist, settingName)) {
Christopher Tate0738e882009-09-11 16:35:39 -0700263 continue;
264 }
265
Amith Yamasani70c874b2009-07-06 14:53:25 -0700266 if (mSettingsHelper.restoreValue(settingName, settingValue)) {
267 cv.clear();
268 cv.put(Settings.NameValueTable.NAME, settingName);
269 cv.put(Settings.NameValueTable.VALUE, settingValue);
270 getContentResolver().insert(contentUri, cv);
271 }
Amith Yamasani220f4d62009-07-02 02:34:14 -0700272 }
273 }
274 }
275
Christopher Tate796e0f02009-09-22 11:57:58 -0700276 // Returns 'true' if the given setting is one that we refuse to restore
277 private boolean invalidSavedSetting(String[] knownNames, String candidate) {
278 // no filter? allow everything
279 if (knownNames == null) {
280 return false;
281 }
282
283 // whitelisted setting? allow it
284 for (String name : knownNames) {
285 if (name.equals(candidate)) {
286 return false;
Christopher Tate0738e882009-09-11 16:35:39 -0700287 }
288 }
289
Christopher Tate796e0f02009-09-22 11:57:58 -0700290 // refuse everything else
291 if (DEBUG) Log.v(TAG, "Ignoring restore datum: " + candidate);
292 return true;
Christopher Tate0738e882009-09-11 16:35:39 -0700293 }
294
Amith Yamasani220f4d62009-07-02 02:34:14 -0700295 private String[] copyAndSort(String[] keys) {
296 String[] sortedKeys = new String[keys.length];
297 System.arraycopy(keys, 0, sortedKeys, 0, keys.length);
298 Arrays.sort(sortedKeys);
299 return sortedKeys;
300 }
301
302 /**
Christian Sonntagc5b5b0f2009-08-07 10:00:21 -0700303 * Given a cursor sorted by key name and a set of keys sorted by name,
Amith Yamasani220f4d62009-07-02 02:34:14 -0700304 * extract the required keys and values and write them to a byte array.
305 * @param sortedCursor
306 * @param sortedKeys
307 * @return
308 */
309 byte[] extractRelevantValues(Cursor sortedCursor, String[] sortedKeys) {
310 byte[][] values = new byte[sortedKeys.length * 2][]; // keys and values
311 if (!sortedCursor.moveToFirst()) {
312 Log.e(TAG, "Couldn't read from the cursor");
313 return new byte[0];
314 }
315 int keyIndex = 0;
316 int totalSize = 0;
317 while (!sortedCursor.isAfterLast()) {
318 String name = sortedCursor.getString(COLUMN_NAME);
319 while (sortedKeys[keyIndex].compareTo(name.toString()) < 0) {
320 keyIndex++;
321 if (keyIndex == sortedKeys.length) break;
322 }
323 if (keyIndex < sortedKeys.length && name.equals(sortedKeys[keyIndex])) {
324 String value = sortedCursor.getString(COLUMN_VALUE);
325 byte[] nameBytes = name.toString().getBytes();
326 totalSize += 4 + nameBytes.length;
327 values[keyIndex * 2] = nameBytes;
328 byte[] valueBytes;
329 if (TextUtils.isEmpty(value)) {
330 valueBytes = null;
331 totalSize += 4;
332 } else {
333 valueBytes = value.toString().getBytes();
334 totalSize += 4 + valueBytes.length;
335 //Log.i(TAG, "Backing up " + name + " = " + value);
336 }
337 values[keyIndex * 2 + 1] = valueBytes;
338 keyIndex++;
339 }
340 if (keyIndex == sortedKeys.length || !sortedCursor.moveToNext()) {
341 break;
342 }
343 }
344
345 byte[] result = new byte[totalSize];
346 int pos = 0;
347 for (int i = 0; i < sortedKeys.length * 2; i++) {
348 if (values[i] != null) {
349 pos = writeInt(result, pos, values[i].length);
350 pos = writeBytes(result, pos, values[i]);
351 }
352 }
353 return result;
354 }
355
Amith Yamasani2cfab842009-09-09 18:27:31 -0700356 private byte[] getWifiSupplicant(String filename) {
Brad Fitzpatrick70787892010-11-17 11:31:12 -0800357 BufferedReader br = null;
Amith Yamasani220f4d62009-07-02 02:34:14 -0700358 try {
359 File file = new File(filename);
360 if (file.exists()) {
Brad Fitzpatrick70787892010-11-17 11:31:12 -0800361 br = new BufferedReader(new FileReader(file));
Amith Yamasani2cfab842009-09-09 18:27:31 -0700362 StringBuffer relevantLines = new StringBuffer();
363 boolean started = false;
364 String line;
365 while ((line = br.readLine()) != null) {
366 if (!started && line.startsWith("network")) {
367 started = true;
368 }
369 if (started) {
370 relevantLines.append(line).append("\n");
371 }
372 }
373 if (relevantLines.length() > 0) {
374 return relevantLines.toString().getBytes();
375 } else {
376 return EMPTY_DATA;
377 }
Amith Yamasani220f4d62009-07-02 02:34:14 -0700378 } else {
Amith Yamasanid1582142009-07-08 20:04:55 -0700379 return EMPTY_DATA;
Amith Yamasani220f4d62009-07-02 02:34:14 -0700380 }
381 } catch (IOException ioe) {
382 Log.w(TAG, "Couldn't backup " + filename);
Amith Yamasanid1582142009-07-08 20:04:55 -0700383 return EMPTY_DATA;
Brad Fitzpatrick70787892010-11-17 11:31:12 -0800384 } finally {
385 if (br != null) {
386 try {
387 br.close();
388 } catch (IOException e) {
389 }
390 }
Amith Yamasani220f4d62009-07-02 02:34:14 -0700391 }
392 }
393
Amith Yamasani2cfab842009-09-09 18:27:31 -0700394 private void restoreWifiSupplicant(String filename, BackupDataInput data) {
Amith Yamasani220f4d62009-07-02 02:34:14 -0700395 byte[] bytes = new byte[data.getDataSize()];
396 if (bytes.length <= 0) return;
397 try {
398 data.readEntityData(bytes, 0, bytes.length);
Amith Yamasani2cfab842009-09-09 18:27:31 -0700399 File supplicantFile = new File(FILE_WIFI_SUPPLICANT);
400 if (supplicantFile.exists()) supplicantFile.delete();
401 copyWifiSupplicantTemplate();
402
Brad Fitzpatrick70787892010-11-17 11:31:12 -0800403 OutputStream os = new BufferedOutputStream(new FileOutputStream(filename, true));
404 os.write("\n".getBytes());
405 os.write(bytes);
406 os.close();
Amith Yamasani220f4d62009-07-02 02:34:14 -0700407 } catch (IOException ioe) {
408 Log.w(TAG, "Couldn't restore " + filename);
409 }
410 }
411
Amith Yamasani2cfab842009-09-09 18:27:31 -0700412 private void copyWifiSupplicantTemplate() {
413 try {
414 BufferedReader br = new BufferedReader(new FileReader(FILE_WIFI_SUPPLICANT_TEMPLATE));
415 BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_WIFI_SUPPLICANT));
416 char[] temp = new char[1024];
417 int size;
418 while ((size = br.read(temp)) > 0) {
419 bw.write(temp, 0, size);
420 }
421 bw.close();
422 br.close();
423 } catch (IOException ioe) {
424 Log.w(TAG, "Couldn't copy wpa_supplicant file");
425 }
426 }
427
Amith Yamasani220f4d62009-07-02 02:34:14 -0700428 /**
429 * Write an int in BigEndian into the byte array.
430 * @param out byte array
431 * @param pos current pos in array
432 * @param value integer to write
433 * @return the index after adding the size of an int (4)
434 */
435 private int writeInt(byte[] out, int pos, int value) {
436 out[pos + 0] = (byte) ((value >> 24) & 0xFF);
437 out[pos + 1] = (byte) ((value >> 16) & 0xFF);
438 out[pos + 2] = (byte) ((value >> 8) & 0xFF);
439 out[pos + 3] = (byte) ((value >> 0) & 0xFF);
440 return pos + 4;
441 }
442
443 private int writeBytes(byte[] out, int pos, byte[] value) {
444 System.arraycopy(value, 0, out, pos, value.length);
445 return pos + value.length;
446 }
447
448 private int readInt(byte[] in, int pos) {
449 int result =
450 ((in[pos ] & 0xFF) << 24) |
451 ((in[pos + 1] & 0xFF) << 16) |
452 ((in[pos + 2] & 0xFF) << 8) |
453 ((in[pos + 3] & 0xFF) << 0);
454 return result;
455 }
456
Christian Sonntagc5b5b0f2009-08-07 10:00:21 -0700457 private int enableWifi(boolean enable) {
Amith Yamasani220f4d62009-07-02 02:34:14 -0700458 WifiManager wfm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
459 if (wfm != null) {
Christian Sonntagc5b5b0f2009-08-07 10:00:21 -0700460 int state = wfm.getWifiState();
Amith Yamasani220f4d62009-07-02 02:34:14 -0700461 wfm.setWifiEnabled(enable);
Christian Sonntagc5b5b0f2009-08-07 10:00:21 -0700462 return state;
Amith Yamasani220f4d62009-07-02 02:34:14 -0700463 }
Christian Sonntagc5b5b0f2009-08-07 10:00:21 -0700464 return WifiManager.WIFI_STATE_UNKNOWN;
Amith Yamasani220f4d62009-07-02 02:34:14 -0700465 }
Amith Yamasani220f4d62009-07-02 02:34:14 -0700466}