SettingsBackupAgent.java revision 5067685ccf6c294a77a3e7f0577190600a0e6238
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.backup.BackupAgentHelper;
20import android.app.backup.BackupDataInput;
21import android.app.backup.BackupDataOutput;
22import android.app.backup.FullBackupDataOutput;
23import android.content.ContentValues;
24import android.content.Context;
25import android.database.Cursor;
26import android.net.Uri;
27import android.net.wifi.WifiManager;
28import android.os.FileUtils;
29import android.os.ParcelFileDescriptor;
30import android.os.Process;
31import android.provider.Settings;
32import android.util.Log;
33
34import java.io.BufferedOutputStream;
35import java.io.BufferedReader;
36import java.io.BufferedWriter;
37import java.io.CharArrayReader;
38import java.io.DataInputStream;
39import java.io.DataOutputStream;
40import java.io.EOFException;
41import java.io.File;
42import java.io.FileInputStream;
43import java.io.FileOutputStream;
44import java.io.FileReader;
45import java.io.FileWriter;
46import java.io.IOException;
47import java.io.InputStream;
48import java.io.OutputStream;
49import java.io.Writer;
50import java.util.ArrayList;
51import java.util.HashMap;
52import java.util.HashSet;
53import java.util.Map;
54import java.util.zip.CRC32;
55
56/**
57 * Performs backup and restore of the System and Secure settings.
58 * List of settings that are backed up are stored in the Settings.java file
59 */
60public class SettingsBackupAgent extends BackupAgentHelper {
61    private static final boolean DEBUG = false;
62    private static final boolean DEBUG_BACKUP = DEBUG || false;
63
64    /* Don't restore wifi config until we have new logic for parsing the
65     * saved wifi config and configuring the new APs without having to
66     * disable and re-enable wifi
67     */
68    private static final boolean NAIVE_WIFI_RESTORE = false;
69
70    private static final String KEY_SYSTEM = "system";
71    private static final String KEY_SECURE = "secure";
72    private static final String KEY_GLOBAL = "global";
73    private static final String KEY_LOCALE = "locale";
74
75    // Versioning of the state file.  Increment this version
76    // number any time the set of state items is altered.
77    private static final int STATE_VERSION = 3;
78
79    // Slots in the checksum array.  Never insert new items in the middle
80    // of this array; new slots must be appended.
81    private static final int STATE_SYSTEM          = 0;
82    private static final int STATE_SECURE          = 1;
83    private static final int STATE_LOCALE          = 2;
84    private static final int STATE_WIFI_SUPPLICANT = 3;
85    private static final int STATE_WIFI_CONFIG     = 4;
86    private static final int STATE_GLOBAL          = 5;
87
88    private static final int STATE_SIZE            = 6; // The current number of state items
89
90    // Number of entries in the checksum array at various version numbers
91    private static final int STATE_SIZES[] = {
92        0,
93        4,              // version 1
94        5,              // version 2 added STATE_WIFI_CONFIG
95        STATE_SIZE      // version 3 added STATE_GLOBAL
96    };
97
98    // Versioning of the 'full backup' format
99    private static final int FULL_BACKUP_VERSION = 2;
100    private static final int FULL_BACKUP_ADDED_GLOBAL = 2;  // added the "global" entry
101
102    private static final int INTEGER_BYTE_COUNT = Integer.SIZE / Byte.SIZE;
103
104    private static final byte[] EMPTY_DATA = new byte[0];
105
106    private static final String TAG = "SettingsBackupAgent";
107
108    private static final int COLUMN_NAME = 1;
109    private static final int COLUMN_VALUE = 2;
110
111    private static final String[] PROJECTION = {
112        Settings.NameValueTable._ID,
113        Settings.NameValueTable.NAME,
114        Settings.NameValueTable.VALUE
115    };
116
117    private static final String FILE_WIFI_SUPPLICANT = "/data/misc/wifi/wpa_supplicant.conf";
118    private static final String FILE_WIFI_SUPPLICANT_TEMPLATE =
119            "/system/etc/wifi/wpa_supplicant.conf";
120
121    // the key to store the WIFI data under, should be sorted as last, so restore happens last.
122    // use very late unicode character to quasi-guarantee last sort position.
123    private static final String KEY_WIFI_SUPPLICANT = "\uffedWIFI";
124    private static final String KEY_WIFI_CONFIG = "\uffedCONFIG_WIFI";
125
126    // Name of the temporary file we use during full backup/restore.  This is
127    // stored in the full-backup tarfile as well, so should not be changed.
128    private static final String STAGE_FILE = "flattened-data";
129
130    private SettingsHelper mSettingsHelper;
131    private WifiManager mWfm;
132    private static String mWifiConfigFile;
133
134    // Class for capturing a network definition from the wifi supplicant config file
135    static class Network {
136        String ssid = "";  // equals() and hashCode() need these to be non-null
137        String key_mgmt = "";
138        final ArrayList<String> rawLines = new ArrayList<String>();
139
140        public static Network readFromStream(BufferedReader in) {
141            final Network n = new Network();
142            String line;
143            try {
144                while (in.ready()) {
145                    line = in.readLine();
146                    if (line == null || line.startsWith("}")) {
147                        break;
148                    }
149                    n.rememberLine(line);
150                }
151            } catch (IOException e) {
152                return null;
153            }
154            return n;
155        }
156
157        void rememberLine(String line) {
158            // can't rely on particular whitespace patterns so strip leading/trailing
159            line = line.trim();
160            if (line.isEmpty()) return; // only whitespace; drop the line
161            rawLines.add(line);
162
163            // remember the ssid and key_mgmt lines for duplicate culling
164            if (line.startsWith("ssid")) {
165                ssid = line;
166            } else if (line.startsWith("key_mgmt")) {
167                key_mgmt = line;
168            }
169        }
170
171        public void write(Writer w) throws IOException {
172            w.write("\nnetwork={\n");
173            for (String line : rawLines) {
174                w.write("\t" + line + "\n");
175            }
176            w.write("}\n");
177        }
178
179        public void dump() {
180            Log.v(TAG, "network={");
181            for (String line : rawLines) {
182                Log.v(TAG, "   " + line);
183            }
184            Log.v(TAG, "}");
185        }
186
187        // Same approach as Pair.equals() and Pair.hashCode()
188        @Override
189        public boolean equals(Object o) {
190            if (o == this) return true;
191            if (!(o instanceof Network)) return false;
192            final Network other;
193            try {
194                other = (Network) o;
195            } catch (ClassCastException e) {
196                return false;
197            }
198            return ssid.equals(other.ssid) && key_mgmt.equals(other.key_mgmt);
199        }
200
201        @Override
202        public int hashCode() {
203            int result = 17;
204            result = 31 * result + ssid.hashCode();
205            result = 31 * result + key_mgmt.hashCode();
206            return result;
207        }
208    }
209
210    // Ingest multiple wifi config file fragments, looking for network={} blocks
211    // and eliminating duplicates
212    class WifiNetworkSettings {
213        // One for fast lookup, one for maintaining ordering
214        final HashSet<Network> mKnownNetworks = new HashSet<Network>();
215        final ArrayList<Network> mNetworks = new ArrayList<Network>(8);
216
217        public void readNetworks(BufferedReader in) {
218            try {
219                String line;
220                while (in.ready()) {
221                    line = in.readLine();
222                    if (line != null) {
223                        // Parse out 'network=' decls so we can ignore duplicates
224                        if (line.startsWith("network")) {
225                            Network net = Network.readFromStream(in);
226                            if (! mKnownNetworks.contains(net)) {
227                                if (DEBUG_BACKUP) {
228                                    Log.v(TAG, "Adding " + net.ssid + " / " + net.key_mgmt);
229                                }
230                                mKnownNetworks.add(net);
231                                mNetworks.add(net);
232                            } else {
233                                if (DEBUG_BACKUP) {
234                                    Log.v(TAG, "Dupe; skipped " + net.ssid + " / " + net.key_mgmt);
235                                }
236                            }
237                        }
238                    }
239                }
240            } catch (IOException e) {
241                // whatever happened, we're done now
242            }
243        }
244
245        public void write(Writer w) throws IOException {
246            for (Network net : mNetworks) {
247                net.write(w);
248            }
249        }
250
251        public void dump() {
252            for (Network net : mNetworks) {
253                net.dump();
254            }
255        }
256    }
257
258    @Override
259    public void onCreate() {
260        if (DEBUG_BACKUP) Log.d(TAG, "onCreate() invoked");
261
262        mSettingsHelper = new SettingsHelper(this);
263        super.onCreate();
264
265        WifiManager mWfm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
266        if (mWfm != null) mWifiConfigFile = mWfm.getConfigFile();
267    }
268
269    @Override
270    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
271            ParcelFileDescriptor newState) throws IOException {
272
273        byte[] systemSettingsData = getSystemSettings();
274        byte[] secureSettingsData = getSecureSettings();
275        byte[] globalSettingsData = getGlobalSettings();
276        byte[] locale = mSettingsHelper.getLocaleData();
277        byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
278        byte[] wifiConfigData = getFileData(mWifiConfigFile);
279
280        long[] stateChecksums = readOldChecksums(oldState);
281
282        stateChecksums[STATE_SYSTEM] =
283            writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data);
284        stateChecksums[STATE_SECURE] =
285            writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data);
286        stateChecksums[STATE_GLOBAL] =
287            writeIfChanged(stateChecksums[STATE_GLOBAL], KEY_GLOBAL, secureSettingsData, data);
288        stateChecksums[STATE_LOCALE] =
289            writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data);
290        stateChecksums[STATE_WIFI_SUPPLICANT] =
291            writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT,
292                    wifiSupplicantData, data);
293        stateChecksums[STATE_WIFI_CONFIG] =
294            writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData,
295                    data);
296
297        writeNewChecksums(stateChecksums, newState);
298    }
299
300    @Override
301    public void onRestore(BackupDataInput data, int appVersionCode,
302            ParcelFileDescriptor newState) throws IOException {
303
304        HashSet<String> movedToGlobal = new HashSet<String>();
305        Settings.System.getMovedKeys(movedToGlobal);
306        Settings.Secure.getMovedKeys(movedToGlobal);
307
308        while (data.readNextHeader()) {
309            final String key = data.getKey();
310            final int size = data.getDataSize();
311            if (KEY_SYSTEM.equals(key)) {
312                restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal);
313                mSettingsHelper.applyAudioSettings();
314            } else if (KEY_SECURE.equals(key)) {
315                restoreSettings(data, Settings.Secure.CONTENT_URI, movedToGlobal);
316            } else if (NAIVE_WIFI_RESTORE && KEY_WIFI_SUPPLICANT.equals(key)) {
317                int retainedWifiState = enableWifi(false);
318                restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, data);
319                FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
320                        FileUtils.S_IRUSR | FileUtils.S_IWUSR |
321                        FileUtils.S_IRGRP | FileUtils.S_IWGRP,
322                        Process.myUid(), Process.WIFI_UID);
323                // retain the previous WIFI state.
324                enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED ||
325                        retainedWifiState == WifiManager.WIFI_STATE_ENABLING);
326            } else if (KEY_LOCALE.equals(key)) {
327                byte[] localeData = new byte[size];
328                data.readEntityData(localeData, 0, size);
329                mSettingsHelper.setLocaleData(localeData, size);
330            } else if (NAIVE_WIFI_RESTORE && KEY_WIFI_CONFIG.equals(key)) {
331                restoreFileData(mWifiConfigFile, data);
332             } else {
333                data.skipEntityData();
334            }
335        }
336    }
337
338    @Override
339    public void onFullBackup(FullBackupDataOutput data)  throws IOException {
340        byte[] systemSettingsData = getSystemSettings();
341        byte[] secureSettingsData = getSecureSettings();
342        byte[] globalSettingsData = getGlobalSettings();
343        byte[] locale = mSettingsHelper.getLocaleData();
344        byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
345        byte[] wifiConfigData = getFileData(mWifiConfigFile);
346
347        // Write the data to the staging file, then emit that as our tarfile
348        // representation of the backed-up settings.
349        String root = getFilesDir().getAbsolutePath();
350        File stage = new File(root, STAGE_FILE);
351        try {
352            FileOutputStream filestream = new FileOutputStream(stage);
353            BufferedOutputStream bufstream = new BufferedOutputStream(filestream);
354            DataOutputStream out = new DataOutputStream(bufstream);
355
356            if (DEBUG_BACKUP) Log.d(TAG, "Writing flattened data version " + FULL_BACKUP_VERSION);
357            out.writeInt(FULL_BACKUP_VERSION);
358
359            if (DEBUG_BACKUP) Log.d(TAG, systemSettingsData.length + " bytes of settings data");
360            out.writeInt(systemSettingsData.length);
361            out.write(systemSettingsData);
362            if (DEBUG_BACKUP) Log.d(TAG, secureSettingsData.length + " bytes of secure settings data");
363            out.writeInt(secureSettingsData.length);
364            out.write(secureSettingsData);
365            if (DEBUG_BACKUP) Log.d(TAG, globalSettingsData.length + " bytes of global settings data");
366            out.writeInt(globalSettingsData.length);
367            out.write(globalSettingsData);
368            if (DEBUG_BACKUP) Log.d(TAG, locale.length + " bytes of locale data");
369            out.writeInt(locale.length);
370            out.write(locale);
371            if (DEBUG_BACKUP) Log.d(TAG, wifiSupplicantData.length + " bytes of wifi supplicant data");
372            out.writeInt(wifiSupplicantData.length);
373            out.write(wifiSupplicantData);
374            if (DEBUG_BACKUP) Log.d(TAG, wifiConfigData.length + " bytes of wifi config data");
375            out.writeInt(wifiConfigData.length);
376            out.write(wifiConfigData);
377
378            out.flush();    // also flushes downstream
379
380            // now we're set to emit the tar stream
381            fullBackupFile(stage, data);
382        } finally {
383            stage.delete();
384        }
385    }
386
387    @Override
388    public void onRestoreFile(ParcelFileDescriptor data, long size,
389            int type, String domain, String relpath, long mode, long mtime)
390            throws IOException {
391        if (DEBUG_BACKUP) Log.d(TAG, "onRestoreFile() invoked");
392        // Our data is actually a blob of flattened settings data identical to that
393        // produced during incremental backups.  Just unpack and apply it all in
394        // turn.
395        FileInputStream instream = new FileInputStream(data.getFileDescriptor());
396        DataInputStream in = new DataInputStream(instream);
397
398        int version = in.readInt();
399        if (DEBUG_BACKUP) Log.d(TAG, "Flattened data version " + version);
400        if (version <= FULL_BACKUP_VERSION) {
401            // Generate the moved-to-global lookup table
402            HashSet<String> movedToGlobal = new HashSet<String>();
403            Settings.System.getMovedKeys(movedToGlobal);
404            Settings.Secure.getMovedKeys(movedToGlobal);
405
406            // system settings data first
407            int nBytes = in.readInt();
408            if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of settings data");
409            byte[] buffer = new byte[nBytes];
410            in.readFully(buffer, 0, nBytes);
411            restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal);
412
413            // secure settings
414            nBytes = in.readInt();
415            if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data");
416            if (nBytes > buffer.length) buffer = new byte[nBytes];
417            in.readFully(buffer, 0, nBytes);
418            restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal);
419
420            // Global only if sufficiently new
421            if (version >= FULL_BACKUP_ADDED_GLOBAL) {
422                nBytes = in.readInt();
423                if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of global settings data");
424                if (nBytes > buffer.length) buffer = new byte[nBytes];
425                in.readFully(buffer, 0, nBytes);
426                movedToGlobal.clear();  // no redirection; this *is* the global namespace
427                restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal);
428            }
429
430            // locale
431            nBytes = in.readInt();
432            if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of locale data");
433            if (nBytes > buffer.length) buffer = new byte[nBytes];
434            in.readFully(buffer, 0, nBytes);
435            mSettingsHelper.setLocaleData(buffer, nBytes);
436
437            // wifi supplicant
438            nBytes = in.readInt();
439            if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi supplicant data");
440            if (nBytes > buffer.length) buffer = new byte[nBytes];
441            in.readFully(buffer, 0, nBytes);
442            int retainedWifiState = enableWifi(false);
443            restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, buffer, nBytes);
444            FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
445                    FileUtils.S_IRUSR | FileUtils.S_IWUSR |
446                    FileUtils.S_IRGRP | FileUtils.S_IWGRP,
447                    Process.myUid(), Process.WIFI_UID);
448            // retain the previous WIFI state.
449            enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED ||
450                    retainedWifiState == WifiManager.WIFI_STATE_ENABLING);
451
452            // wifi config
453            nBytes = in.readInt();
454            if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi config data");
455            if (nBytes > buffer.length) buffer = new byte[nBytes];
456            in.readFully(buffer, 0, nBytes);
457            restoreFileData(mWifiConfigFile, buffer, nBytes);
458
459            if (DEBUG_BACKUP) Log.d(TAG, "Full restore complete.");
460        } else {
461            data.close();
462            throw new IOException("Invalid file schema");
463        }
464    }
465
466    private long[] readOldChecksums(ParcelFileDescriptor oldState) throws IOException {
467        long[] stateChecksums = new long[STATE_SIZE];
468
469        DataInputStream dataInput = new DataInputStream(
470                new FileInputStream(oldState.getFileDescriptor()));
471
472        try {
473            int stateVersion = dataInput.readInt();
474            for (int i = 0; i < STATE_SIZES[stateVersion]; i++) {
475                stateChecksums[i] = dataInput.readLong();
476            }
477        } catch (EOFException eof) {
478            // With the default 0 checksum we'll wind up forcing a backup of
479            // any unhandled data sets, which is appropriate.
480        }
481        dataInput.close();
482        return stateChecksums;
483    }
484
485    private void writeNewChecksums(long[] checksums, ParcelFileDescriptor newState)
486            throws IOException {
487        DataOutputStream dataOutput = new DataOutputStream(
488                new FileOutputStream(newState.getFileDescriptor()));
489
490        dataOutput.writeInt(STATE_VERSION);
491        for (int i = 0; i < STATE_SIZE; i++) {
492            dataOutput.writeLong(checksums[i]);
493        }
494        dataOutput.close();
495    }
496
497    private long writeIfChanged(long oldChecksum, String key, byte[] data,
498            BackupDataOutput output) {
499        CRC32 checkSummer = new CRC32();
500        checkSummer.update(data);
501        long newChecksum = checkSummer.getValue();
502        if (oldChecksum == newChecksum) {
503            return oldChecksum;
504        }
505        try {
506            output.writeEntityHeader(key, data.length);
507            output.writeEntityData(data, data.length);
508        } catch (IOException ioe) {
509            // Bail
510        }
511        return newChecksum;
512    }
513
514    private byte[] getSystemSettings() {
515        Cursor cursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION, null,
516                null, null);
517        try {
518            return extractRelevantValues(cursor, Settings.System.SETTINGS_TO_BACKUP);
519        } finally {
520            cursor.close();
521        }
522    }
523
524    private byte[] getSecureSettings() {
525        Cursor cursor = getContentResolver().query(Settings.Secure.CONTENT_URI, PROJECTION, null,
526                null, null);
527        try {
528            return extractRelevantValues(cursor, Settings.Secure.SETTINGS_TO_BACKUP);
529        } finally {
530            cursor.close();
531        }
532    }
533
534    private byte[] getGlobalSettings() {
535        Cursor cursor = getContentResolver().query(Settings.Global.CONTENT_URI, PROJECTION, null,
536                null, null);
537        try {
538            return extractRelevantValues(cursor, Settings.Global.SETTINGS_TO_BACKUP);
539        } finally {
540            cursor.close();
541        }
542    }
543
544    private void restoreSettings(BackupDataInput data, Uri contentUri,
545            HashSet<String> movedToGlobal) {
546        byte[] settings = new byte[data.getDataSize()];
547        try {
548            data.readEntityData(settings, 0, settings.length);
549        } catch (IOException ioe) {
550            Log.e(TAG, "Couldn't read entity data");
551            return;
552        }
553        restoreSettings(settings, settings.length, contentUri, movedToGlobal);
554    }
555
556    private void restoreSettings(byte[] settings, int bytes, Uri contentUri,
557            HashSet<String> movedToGlobal) {
558        if (DEBUG) {
559            Log.i(TAG, "restoreSettings: " + contentUri);
560        }
561
562        // Figure out the white list and redirects to the global table.
563        String[] whitelist = null;
564        if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
565            whitelist = Settings.Secure.SETTINGS_TO_BACKUP;
566        } else if (contentUri.equals(Settings.System.CONTENT_URI)) {
567            whitelist = Settings.System.SETTINGS_TO_BACKUP;
568        } else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
569            whitelist = Settings.Global.SETTINGS_TO_BACKUP;
570        } else {
571            throw new IllegalArgumentException("Unknown URI: " + contentUri);
572        }
573
574        // Restore only the white list data.
575        int pos = 0;
576        Map<String, String> cachedEntries = new HashMap<String, String>();
577        ContentValues contentValues = new ContentValues(2);
578        SettingsHelper settingsHelper = mSettingsHelper;
579
580        final int whiteListSize = whitelist.length;
581        for (int i = 0; i < whiteListSize; i++) {
582            String key = whitelist[i];
583            String value = cachedEntries.remove(key);
584
585            // If the value not cached, let us look it up.
586            if (value == null) {
587                while (pos < bytes) {
588                    int length = readInt(settings, pos);
589                    pos += INTEGER_BYTE_COUNT;
590                    String dataKey = length > 0 ? new String(settings, pos, length) : null;
591                    pos += length;
592                    length = readInt(settings, pos);
593                    pos += INTEGER_BYTE_COUNT;
594                    String dataValue = length > 0 ? new String(settings, pos, length) : null;
595                    pos += length;
596                    if (key.equals(dataKey)) {
597                        value = dataValue;
598                        break;
599                    }
600                    cachedEntries.put(dataKey, dataValue);
601                }
602            }
603
604            if (value == null) {
605                continue;
606            }
607
608            final Uri destination = (movedToGlobal.contains(key))
609                    ? Settings.Global.CONTENT_URI
610                    : contentUri;
611
612            // The helper doesn't care what namespace the keys are in
613            if (settingsHelper.restoreValue(key, value)) {
614                contentValues.clear();
615                contentValues.put(Settings.NameValueTable.NAME, key);
616                contentValues.put(Settings.NameValueTable.VALUE, value);
617                getContentResolver().insert(destination, contentValues);
618            }
619
620            if (DEBUG || true) {
621                Log.d(TAG, "Restored setting: " + destination + " : "+ key + "=" + value);
622            }
623        }
624    }
625
626    /**
627     * Given a cursor and a set of keys, extract the required keys and
628     * values and write them to a byte array.
629     *
630     * @param cursor A cursor with settings data.
631     * @param settings The settings to extract.
632     * @return The byte array of extracted values.
633     */
634    private byte[] extractRelevantValues(Cursor cursor, String[] settings) {
635        final int settingsCount = settings.length;
636        byte[][] values = new byte[settingsCount * 2][]; // keys and values
637        if (!cursor.moveToFirst()) {
638            Log.e(TAG, "Couldn't read from the cursor");
639            return new byte[0];
640        }
641
642        // Obtain the relevant data in a temporary array.
643        int totalSize = 0;
644        int backedUpSettingIndex = 0;
645        Map<String, String> cachedEntries = new HashMap<String, String>();
646        for (int i = 0; i < settingsCount; i++) {
647            String key = settings[i];
648            String value = cachedEntries.remove(key);
649
650            // If the value not cached, let us look it up.
651            if (value == null) {
652                while (!cursor.isAfterLast()) {
653                    String cursorKey = cursor.getString(COLUMN_NAME);
654                    String cursorValue = cursor.getString(COLUMN_VALUE);
655                    cursor.moveToNext();
656                    if (key.equals(cursorKey)) {
657                        value = cursorValue;
658                        break;
659                    }
660                    cachedEntries.put(cursorKey, cursorValue);
661                }
662            }
663
664            if (value == null) {
665                continue;
666            }
667
668            // Write the key and value in the intermediary array.
669            byte[] keyBytes = key.getBytes();
670            totalSize += INTEGER_BYTE_COUNT + keyBytes.length;
671            values[backedUpSettingIndex * 2] = keyBytes;
672
673            byte[] valueBytes = value.getBytes();
674            totalSize += INTEGER_BYTE_COUNT + valueBytes.length;
675            values[backedUpSettingIndex * 2 + 1] = valueBytes;
676
677            backedUpSettingIndex++;
678
679            if (DEBUG) {
680                Log.d(TAG, "Backed up setting: " + key + "=" + value);
681            }
682        }
683
684        // Aggregate the result.
685        byte[] result = new byte[totalSize];
686        int pos = 0;
687        final int keyValuePairCount = backedUpSettingIndex * 2;
688        for (int i = 0; i < keyValuePairCount; i++) {
689            pos = writeInt(result, pos, values[i].length);
690            pos = writeBytes(result, pos, values[i]);
691        }
692        return result;
693    }
694
695    private byte[] getFileData(String filename) {
696        InputStream is = null;
697        try {
698            File file = new File(filename);
699            is = new FileInputStream(file);
700
701            //Will truncate read on a very long file,
702            //should not happen for a config file
703            byte[] bytes = new byte[(int)file.length()];
704
705            int offset = 0;
706            int numRead = 0;
707            while (offset < bytes.length
708                    && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
709                offset += numRead;
710            }
711
712            //read failure
713            if (offset < bytes.length) {
714                Log.w(TAG, "Couldn't backup " + filename);
715                return EMPTY_DATA;
716            }
717            return bytes;
718        } catch (IOException ioe) {
719            Log.w(TAG, "Couldn't backup " + filename);
720            return EMPTY_DATA;
721        } finally {
722            if (is != null) {
723                try {
724                    is.close();
725                } catch (IOException e) {
726                }
727            }
728        }
729
730    }
731
732    private void restoreFileData(String filename, BackupDataInput data) {
733        byte[] bytes = new byte[data.getDataSize()];
734        if (bytes.length <= 0) return;
735        try {
736            data.readEntityData(bytes, 0, data.getDataSize());
737            restoreFileData(filename, bytes, bytes.length);
738        } catch (IOException e) {
739            Log.w(TAG, "Unable to read file data for " + filename);
740        }
741    }
742
743    private void restoreFileData(String filename, byte[] bytes, int size) {
744        try {
745            File file = new File(filename);
746            if (file.exists()) file.delete();
747
748            OutputStream os = new BufferedOutputStream(new FileOutputStream(filename, true));
749            os.write(bytes, 0, size);
750            os.close();
751        } catch (IOException ioe) {
752            Log.w(TAG, "Couldn't restore " + filename);
753        }
754    }
755
756
757    private byte[] getWifiSupplicant(String filename) {
758        BufferedReader br = null;
759        try {
760            File file = new File(filename);
761            if (file.exists()) {
762                br = new BufferedReader(new FileReader(file));
763                StringBuffer relevantLines = new StringBuffer();
764                boolean started = false;
765                String line;
766                while ((line = br.readLine()) != null) {
767                    if (!started && line.startsWith("network")) {
768                        started = true;
769                    }
770                    if (started) {
771                        relevantLines.append(line).append("\n");
772                    }
773                }
774                if (relevantLines.length() > 0) {
775                    return relevantLines.toString().getBytes();
776                } else {
777                    return EMPTY_DATA;
778                }
779            } else {
780                return EMPTY_DATA;
781            }
782        } catch (IOException ioe) {
783            Log.w(TAG, "Couldn't backup " + filename);
784            return EMPTY_DATA;
785        } finally {
786            if (br != null) {
787                try {
788                    br.close();
789                } catch (IOException e) {
790                }
791            }
792        }
793    }
794
795    private void restoreWifiSupplicant(String filename, BackupDataInput data) {
796        byte[] bytes = new byte[data.getDataSize()];
797        if (bytes.length <= 0) return;
798        try {
799            data.readEntityData(bytes, 0, data.getDataSize());
800            restoreWifiSupplicant(filename, bytes, bytes.length);
801        } catch (IOException e) {
802            Log.w(TAG, "Unable to read supplicant data");
803        }
804    }
805
806    private void restoreWifiSupplicant(String filename, byte[] bytes, int size) {
807        try {
808            WifiNetworkSettings supplicantImage = new WifiNetworkSettings();
809
810            File supplicantFile = new File(FILE_WIFI_SUPPLICANT);
811            if (supplicantFile.exists()) {
812                // Retain the existing APs; we'll append the restored ones to them
813                BufferedReader in = new BufferedReader(new FileReader(FILE_WIFI_SUPPLICANT));
814                supplicantImage.readNetworks(in);
815                in.close();
816
817                supplicantFile.delete();
818            }
819
820            // Incorporate the restore AP information
821            if (size > 0) {
822                char[] restoredAsBytes = new char[size];
823                for (int i = 0; i < size; i++) restoredAsBytes[i] = (char) bytes[i];
824                BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsBytes));
825                supplicantImage.readNetworks(in);
826
827                if (DEBUG_BACKUP) {
828                    Log.v(TAG, "Final AP list:");
829                    supplicantImage.dump();
830                }
831            }
832
833            // Install the correct default template
834            BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_WIFI_SUPPLICANT));
835            copyWifiSupplicantTemplate(bw);
836
837            // Write the restored supplicant config and we're done
838            supplicantImage.write(bw);
839            bw.close();
840        } catch (IOException ioe) {
841            Log.w(TAG, "Couldn't restore " + filename);
842        }
843    }
844
845    private void copyWifiSupplicantTemplate(BufferedWriter bw) {
846        try {
847            BufferedReader br = new BufferedReader(new FileReader(FILE_WIFI_SUPPLICANT_TEMPLATE));
848            char[] temp = new char[1024];
849            int size;
850            while ((size = br.read(temp)) > 0) {
851                bw.write(temp, 0, size);
852            }
853            br.close();
854        } catch (IOException ioe) {
855            Log.w(TAG, "Couldn't copy wpa_supplicant file");
856        }
857    }
858
859    /**
860     * Write an int in BigEndian into the byte array.
861     * @param out byte array
862     * @param pos current pos in array
863     * @param value integer to write
864     * @return the index after adding the size of an int (4) in bytes.
865     */
866    private int writeInt(byte[] out, int pos, int value) {
867        out[pos + 0] = (byte) ((value >> 24) & 0xFF);
868        out[pos + 1] = (byte) ((value >> 16) & 0xFF);
869        out[pos + 2] = (byte) ((value >>  8) & 0xFF);
870        out[pos + 3] = (byte) ((value >>  0) & 0xFF);
871        return pos + INTEGER_BYTE_COUNT;
872    }
873
874    private int writeBytes(byte[] out, int pos, byte[] value) {
875        System.arraycopy(value, 0, out, pos, value.length);
876        return pos + value.length;
877    }
878
879    private int readInt(byte[] in, int pos) {
880        int result =
881                ((in[pos    ] & 0xFF) << 24) |
882                ((in[pos + 1] & 0xFF) << 16) |
883                ((in[pos + 2] & 0xFF) <<  8) |
884                ((in[pos + 3] & 0xFF) <<  0);
885        return result;
886    }
887
888    private int enableWifi(boolean enable) {
889        if (mWfm == null) {
890            mWfm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
891        }
892        if (mWfm != null) {
893            int state = mWfm.getWifiState();
894            mWfm.setWifiEnabled(enable);
895            return state;
896        } else {
897            Log.e(TAG, "Failed to fetch WifiManager instance");
898        }
899        return WifiManager.WIFI_STATE_UNKNOWN;
900    }
901}
902