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