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