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