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