OSDN Git Service

Always give wifi a chance to quiet down during restore
[android-x86/frameworks-base.git] / packages / SettingsProvider / src / com / android / providers / settings / SettingsBackupAgent.java
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                 }
356                 // !!! Give the wifi stack a moment to quiesce
357                 try { Thread.sleep(1500); } catch (InterruptedException e) {}
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             long wifiBounceDelayMillis = Settings.Global.getLong(
426                     getContentResolver(),
427                     Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
428                     WIFI_BOUNCE_DELAY_MILLIS);
429             new Handler(getMainLooper()).postDelayed(mWifiRestore, wifiBounceDelayMillis);
430         }
431     }
432
433     @Override
434     public void onFullBackup(FullBackupDataOutput data)  throws IOException {
435         byte[] systemSettingsData = getSystemSettings();
436         byte[] secureSettingsData = getSecureSettings();
437         byte[] globalSettingsData = getGlobalSettings();
438         byte[] locale = mSettingsHelper.getLocaleData();
439         byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
440         byte[] wifiConfigData = getFileData(mWifiConfigFile);
441
442         // Write the data to the staging file, then emit that as our tarfile
443         // representation of the backed-up settings.
444         String root = getFilesDir().getAbsolutePath();
445         File stage = new File(root, STAGE_FILE);
446         try {
447             FileOutputStream filestream = new FileOutputStream(stage);
448             BufferedOutputStream bufstream = new BufferedOutputStream(filestream);
449             DataOutputStream out = new DataOutputStream(bufstream);
450
451             if (DEBUG_BACKUP) Log.d(TAG, "Writing flattened data version " + FULL_BACKUP_VERSION);
452             out.writeInt(FULL_BACKUP_VERSION);
453
454             if (DEBUG_BACKUP) Log.d(TAG, systemSettingsData.length + " bytes of settings data");
455             out.writeInt(systemSettingsData.length);
456             out.write(systemSettingsData);
457             if (DEBUG_BACKUP) Log.d(TAG, secureSettingsData.length + " bytes of secure settings data");
458             out.writeInt(secureSettingsData.length);
459             out.write(secureSettingsData);
460             if (DEBUG_BACKUP) Log.d(TAG, globalSettingsData.length + " bytes of global settings data");
461             out.writeInt(globalSettingsData.length);
462             out.write(globalSettingsData);
463             if (DEBUG_BACKUP) Log.d(TAG, locale.length + " bytes of locale data");
464             out.writeInt(locale.length);
465             out.write(locale);
466             if (DEBUG_BACKUP) Log.d(TAG, wifiSupplicantData.length + " bytes of wifi supplicant data");
467             out.writeInt(wifiSupplicantData.length);
468             out.write(wifiSupplicantData);
469             if (DEBUG_BACKUP) Log.d(TAG, wifiConfigData.length + " bytes of wifi config data");
470             out.writeInt(wifiConfigData.length);
471             out.write(wifiConfigData);
472
473             out.flush();    // also flushes downstream
474
475             // now we're set to emit the tar stream
476             fullBackupFile(stage, data);
477         } finally {
478             stage.delete();
479         }
480     }
481
482     @Override
483     public void onRestoreFile(ParcelFileDescriptor data, long size,
484             int type, String domain, String relpath, long mode, long mtime)
485             throws IOException {
486         if (DEBUG_BACKUP) Log.d(TAG, "onRestoreFile() invoked");
487         // Our data is actually a blob of flattened settings data identical to that
488         // produced during incremental backups.  Just unpack and apply it all in
489         // turn.
490         FileInputStream instream = new FileInputStream(data.getFileDescriptor());
491         DataInputStream in = new DataInputStream(instream);
492
493         int version = in.readInt();
494         if (DEBUG_BACKUP) Log.d(TAG, "Flattened data version " + version);
495         if (version <= FULL_BACKUP_VERSION) {
496             // Generate the moved-to-global lookup table
497             HashSet<String> movedToGlobal = new HashSet<String>();
498             Settings.System.getMovedKeys(movedToGlobal);
499             Settings.Secure.getMovedKeys(movedToGlobal);
500
501             // system settings data first
502             int nBytes = in.readInt();
503             if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of settings data");
504             byte[] buffer = new byte[nBytes];
505             in.readFully(buffer, 0, nBytes);
506             restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal);
507
508             // secure settings
509             nBytes = in.readInt();
510             if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data");
511             if (nBytes > buffer.length) buffer = new byte[nBytes];
512             in.readFully(buffer, 0, nBytes);
513             restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal);
514
515             // Global only if sufficiently new
516             if (version >= FULL_BACKUP_ADDED_GLOBAL) {
517                 nBytes = in.readInt();
518                 if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of global settings data");
519                 if (nBytes > buffer.length) buffer = new byte[nBytes];
520                 in.readFully(buffer, 0, nBytes);
521                 movedToGlobal.clear();  // no redirection; this *is* the global namespace
522                 restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal);
523             }
524
525             // locale
526             nBytes = in.readInt();
527             if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of locale data");
528             if (nBytes > buffer.length) buffer = new byte[nBytes];
529             in.readFully(buffer, 0, nBytes);
530             mSettingsHelper.setLocaleData(buffer, nBytes);
531
532             // wifi supplicant
533             nBytes = in.readInt();
534             if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi supplicant data");
535             if (nBytes > buffer.length) buffer = new byte[nBytes];
536             in.readFully(buffer, 0, nBytes);
537             int retainedWifiState = enableWifi(false);
538             restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, buffer, nBytes);
539             FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
540                     FileUtils.S_IRUSR | FileUtils.S_IWUSR |
541                     FileUtils.S_IRGRP | FileUtils.S_IWGRP,
542                     Process.myUid(), Process.WIFI_UID);
543             // retain the previous WIFI state.
544             enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED ||
545                     retainedWifiState == WifiManager.WIFI_STATE_ENABLING);
546
547             // wifi config
548             nBytes = in.readInt();
549             if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi config data");
550             if (nBytes > buffer.length) buffer = new byte[nBytes];
551             in.readFully(buffer, 0, nBytes);
552             restoreFileData(mWifiConfigFile, buffer, nBytes);
553
554             if (DEBUG_BACKUP) Log.d(TAG, "Full restore complete.");
555         } else {
556             data.close();
557             throw new IOException("Invalid file schema");
558         }
559     }
560
561     private long[] readOldChecksums(ParcelFileDescriptor oldState) throws IOException {
562         long[] stateChecksums = new long[STATE_SIZE];
563
564         DataInputStream dataInput = new DataInputStream(
565                 new FileInputStream(oldState.getFileDescriptor()));
566
567         try {
568             int stateVersion = dataInput.readInt();
569             for (int i = 0; i < STATE_SIZES[stateVersion]; i++) {
570                 stateChecksums[i] = dataInput.readLong();
571             }
572         } catch (EOFException eof) {
573             // With the default 0 checksum we'll wind up forcing a backup of
574             // any unhandled data sets, which is appropriate.
575         }
576         dataInput.close();
577         return stateChecksums;
578     }
579
580     private void writeNewChecksums(long[] checksums, ParcelFileDescriptor newState)
581             throws IOException {
582         DataOutputStream dataOutput = new DataOutputStream(
583                 new FileOutputStream(newState.getFileDescriptor()));
584
585         dataOutput.writeInt(STATE_VERSION);
586         for (int i = 0; i < STATE_SIZE; i++) {
587             dataOutput.writeLong(checksums[i]);
588         }
589         dataOutput.close();
590     }
591
592     private long writeIfChanged(long oldChecksum, String key, byte[] data,
593             BackupDataOutput output) {
594         CRC32 checkSummer = new CRC32();
595         checkSummer.update(data);
596         long newChecksum = checkSummer.getValue();
597         if (oldChecksum == newChecksum) {
598             return oldChecksum;
599         }
600         try {
601             output.writeEntityHeader(key, data.length);
602             output.writeEntityData(data, data.length);
603         } catch (IOException ioe) {
604             // Bail
605         }
606         return newChecksum;
607     }
608
609     private byte[] getSystemSettings() {
610         Cursor cursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION, null,
611                 null, null);
612         try {
613             return extractRelevantValues(cursor, Settings.System.SETTINGS_TO_BACKUP);
614         } finally {
615             cursor.close();
616         }
617     }
618
619     private byte[] getSecureSettings() {
620         Cursor cursor = getContentResolver().query(Settings.Secure.CONTENT_URI, PROJECTION, null,
621                 null, null);
622         try {
623             return extractRelevantValues(cursor, Settings.Secure.SETTINGS_TO_BACKUP);
624         } finally {
625             cursor.close();
626         }
627     }
628
629     private byte[] getGlobalSettings() {
630         Cursor cursor = getContentResolver().query(Settings.Global.CONTENT_URI, PROJECTION, null,
631                 null, null);
632         try {
633             return extractRelevantValues(cursor, Settings.Global.SETTINGS_TO_BACKUP);
634         } finally {
635             cursor.close();
636         }
637     }
638
639     private void restoreSettings(BackupDataInput data, Uri contentUri,
640             HashSet<String> movedToGlobal) {
641         byte[] settings = new byte[data.getDataSize()];
642         try {
643             data.readEntityData(settings, 0, settings.length);
644         } catch (IOException ioe) {
645             Log.e(TAG, "Couldn't read entity data");
646             return;
647         }
648         restoreSettings(settings, settings.length, contentUri, movedToGlobal);
649     }
650
651     private void restoreSettings(byte[] settings, int bytes, Uri contentUri,
652             HashSet<String> movedToGlobal) {
653         if (DEBUG) {
654             Log.i(TAG, "restoreSettings: " + contentUri);
655         }
656
657         // Figure out the white list and redirects to the global table.
658         String[] whitelist = null;
659         if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
660             whitelist = Settings.Secure.SETTINGS_TO_BACKUP;
661         } else if (contentUri.equals(Settings.System.CONTENT_URI)) {
662             whitelist = Settings.System.SETTINGS_TO_BACKUP;
663         } else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
664             whitelist = Settings.Global.SETTINGS_TO_BACKUP;
665         } else {
666             throw new IllegalArgumentException("Unknown URI: " + contentUri);
667         }
668
669         // Restore only the white list data.
670         int pos = 0;
671         Map<String, String> cachedEntries = new HashMap<String, String>();
672         ContentValues contentValues = new ContentValues(2);
673         SettingsHelper settingsHelper = mSettingsHelper;
674
675         final int whiteListSize = whitelist.length;
676         for (int i = 0; i < whiteListSize; i++) {
677             String key = whitelist[i];
678             String value = cachedEntries.remove(key);
679
680             // If the value not cached, let us look it up.
681             if (value == null) {
682                 while (pos < bytes) {
683                     int length = readInt(settings, pos);
684                     pos += INTEGER_BYTE_COUNT;
685                     String dataKey = length > 0 ? new String(settings, pos, length) : null;
686                     pos += length;
687                     length = readInt(settings, pos);
688                     pos += INTEGER_BYTE_COUNT;
689                     String dataValue = length > 0 ? new String(settings, pos, length) : null;
690                     pos += length;
691                     if (key.equals(dataKey)) {
692                         value = dataValue;
693                         break;
694                     }
695                     cachedEntries.put(dataKey, dataValue);
696                 }
697             }
698
699             if (value == null) {
700                 continue;
701             }
702
703             final Uri destination = (movedToGlobal != null && movedToGlobal.contains(key))
704                     ? Settings.Global.CONTENT_URI
705                     : contentUri;
706
707             // The helper doesn't care what namespace the keys are in
708             if (settingsHelper.restoreValue(key, value)) {
709                 contentValues.clear();
710                 contentValues.put(Settings.NameValueTable.NAME, key);
711                 contentValues.put(Settings.NameValueTable.VALUE, value);
712                 getContentResolver().insert(destination, contentValues);
713             }
714
715             if (DEBUG) {
716                 Log.d(TAG, "Restored setting: " + destination + " : "+ key + "=" + value);
717             }
718         }
719     }
720
721     /**
722      * Given a cursor and a set of keys, extract the required keys and
723      * values and write them to a byte array.
724      *
725      * @param cursor A cursor with settings data.
726      * @param settings The settings to extract.
727      * @return The byte array of extracted values.
728      */
729     private byte[] extractRelevantValues(Cursor cursor, String[] settings) {
730         final int settingsCount = settings.length;
731         byte[][] values = new byte[settingsCount * 2][]; // keys and values
732         if (!cursor.moveToFirst()) {
733             Log.e(TAG, "Couldn't read from the cursor");
734             return new byte[0];
735         }
736
737         // Obtain the relevant data in a temporary array.
738         int totalSize = 0;
739         int backedUpSettingIndex = 0;
740         Map<String, String> cachedEntries = new HashMap<String, String>();
741         for (int i = 0; i < settingsCount; i++) {
742             String key = settings[i];
743             String value = cachedEntries.remove(key);
744
745             // If the value not cached, let us look it up.
746             if (value == null) {
747                 while (!cursor.isAfterLast()) {
748                     String cursorKey = cursor.getString(COLUMN_NAME);
749                     String cursorValue = cursor.getString(COLUMN_VALUE);
750                     cursor.moveToNext();
751                     if (key.equals(cursorKey)) {
752                         value = cursorValue;
753                         break;
754                     }
755                     cachedEntries.put(cursorKey, cursorValue);
756                 }
757             }
758
759             // Intercept the keys and see if they need special handling
760             value = mSettingsHelper.onBackupValue(key, value);
761
762             if (value == null) {
763                 continue;
764             }
765             // Write the key and value in the intermediary array.
766             byte[] keyBytes = key.getBytes();
767             totalSize += INTEGER_BYTE_COUNT + keyBytes.length;
768             values[backedUpSettingIndex * 2] = keyBytes;
769
770             byte[] valueBytes = value.getBytes();
771             totalSize += INTEGER_BYTE_COUNT + valueBytes.length;
772             values[backedUpSettingIndex * 2 + 1] = valueBytes;
773
774             backedUpSettingIndex++;
775
776             if (DEBUG) {
777                 Log.d(TAG, "Backed up setting: " + key + "=" + value);
778             }
779         }
780
781         // Aggregate the result.
782         byte[] result = new byte[totalSize];
783         int pos = 0;
784         final int keyValuePairCount = backedUpSettingIndex * 2;
785         for (int i = 0; i < keyValuePairCount; i++) {
786             pos = writeInt(result, pos, values[i].length);
787             pos = writeBytes(result, pos, values[i]);
788         }
789         return result;
790     }
791
792     private byte[] getFileData(String filename) {
793         InputStream is = null;
794         try {
795             File file = new File(filename);
796             is = new FileInputStream(file);
797
798             //Will truncate read on a very long file,
799             //should not happen for a config file
800             byte[] bytes = new byte[(int)file.length()];
801
802             int offset = 0;
803             int numRead = 0;
804             while (offset < bytes.length
805                     && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
806                 offset += numRead;
807             }
808
809             //read failure
810             if (offset < bytes.length) {
811                 Log.w(TAG, "Couldn't backup " + filename);
812                 return EMPTY_DATA;
813             }
814             return bytes;
815         } catch (IOException ioe) {
816             Log.w(TAG, "Couldn't backup " + filename);
817             return EMPTY_DATA;
818         } finally {
819             if (is != null) {
820                 try {
821                     is.close();
822                 } catch (IOException e) {
823                 }
824             }
825         }
826
827     }
828
829     private void restoreFileData(String filename, byte[] bytes, int size) {
830         try {
831             File file = new File(filename);
832             if (file.exists()) file.delete();
833
834             OutputStream os = new BufferedOutputStream(new FileOutputStream(filename, true));
835             os.write(bytes, 0, size);
836             os.close();
837         } catch (IOException ioe) {
838             Log.w(TAG, "Couldn't restore " + filename);
839         }
840     }
841
842
843     private byte[] getWifiSupplicant(String filename) {
844         BufferedReader br = null;
845         try {
846             File file = new File(filename);
847             if (file.exists()) {
848                 br = new BufferedReader(new FileReader(file));
849                 StringBuffer relevantLines = new StringBuffer();
850                 boolean started = false;
851                 String line;
852                 while ((line = br.readLine()) != null) {
853                     if (!started && line.startsWith("network")) {
854                         started = true;
855                     }
856                     if (started) {
857                         relevantLines.append(line).append("\n");
858                     }
859                 }
860                 if (relevantLines.length() > 0) {
861                     return relevantLines.toString().getBytes();
862                 } else {
863                     return EMPTY_DATA;
864                 }
865             } else {
866                 return EMPTY_DATA;
867             }
868         } catch (IOException ioe) {
869             Log.w(TAG, "Couldn't backup " + filename);
870             return EMPTY_DATA;
871         } finally {
872             if (br != null) {
873                 try {
874                     br.close();
875                 } catch (IOException e) {
876                 }
877             }
878         }
879     }
880
881     private void restoreWifiSupplicant(String filename, byte[] bytes, int size) {
882         try {
883             WifiNetworkSettings supplicantImage = new WifiNetworkSettings();
884
885             File supplicantFile = new File(FILE_WIFI_SUPPLICANT);
886             if (supplicantFile.exists()) {
887                 // Retain the existing APs; we'll append the restored ones to them
888                 BufferedReader in = new BufferedReader(new FileReader(FILE_WIFI_SUPPLICANT));
889                 supplicantImage.readNetworks(in);
890                 in.close();
891
892                 supplicantFile.delete();
893             }
894
895             // Incorporate the restore AP information
896             if (size > 0) {
897                 char[] restoredAsBytes = new char[size];
898                 for (int i = 0; i < size; i++) restoredAsBytes[i] = (char) bytes[i];
899                 BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsBytes));
900                 supplicantImage.readNetworks(in);
901
902                 if (DEBUG_BACKUP) {
903                     Log.v(TAG, "Final AP list:");
904                     supplicantImage.dump();
905                 }
906             }
907
908             // Install the correct default template
909             BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_WIFI_SUPPLICANT));
910             copyWifiSupplicantTemplate(bw);
911
912             // Write the restored supplicant config and we're done
913             supplicantImage.write(bw);
914             bw.close();
915         } catch (IOException ioe) {
916             Log.w(TAG, "Couldn't restore " + filename);
917         }
918     }
919
920     private void copyWifiSupplicantTemplate(BufferedWriter bw) {
921         try {
922             BufferedReader br = new BufferedReader(new FileReader(FILE_WIFI_SUPPLICANT_TEMPLATE));
923             char[] temp = new char[1024];
924             int size;
925             while ((size = br.read(temp)) > 0) {
926                 bw.write(temp, 0, size);
927             }
928             br.close();
929         } catch (IOException ioe) {
930             Log.w(TAG, "Couldn't copy wpa_supplicant file");
931         }
932     }
933
934     /**
935      * Write an int in BigEndian into the byte array.
936      * @param out byte array
937      * @param pos current pos in array
938      * @param value integer to write
939      * @return the index after adding the size of an int (4) in bytes.
940      */
941     private int writeInt(byte[] out, int pos, int value) {
942         out[pos + 0] = (byte) ((value >> 24) & 0xFF);
943         out[pos + 1] = (byte) ((value >> 16) & 0xFF);
944         out[pos + 2] = (byte) ((value >>  8) & 0xFF);
945         out[pos + 3] = (byte) ((value >>  0) & 0xFF);
946         return pos + INTEGER_BYTE_COUNT;
947     }
948
949     private int writeBytes(byte[] out, int pos, byte[] value) {
950         System.arraycopy(value, 0, out, pos, value.length);
951         return pos + value.length;
952     }
953
954     private int readInt(byte[] in, int pos) {
955         int result =
956                 ((in[pos    ] & 0xFF) << 24) |
957                 ((in[pos + 1] & 0xFF) << 16) |
958                 ((in[pos + 2] & 0xFF) <<  8) |
959                 ((in[pos + 3] & 0xFF) <<  0);
960         return result;
961     }
962
963     private int enableWifi(boolean enable) {
964         if (mWfm == null) {
965             mWfm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
966         }
967         if (mWfm != null) {
968             int state = mWfm.getWifiState();
969             mWfm.setWifiEnabled(enable);
970             return state;
971         } else {
972             Log.e(TAG, "Failed to fetch WifiManager instance");
973         }
974         return WifiManager.WIFI_STATE_UNKNOWN;
975     }
976 }