Restore audio settings and wifi.

Optimize backups by writing an entity only if the checksum of the data has changed.
Call into the hidden AudioService API to apply changed audio settings.
After restoring wifi data, make sure that the permissions and ownership are set
properly for the supplicant process to access it.
Locale isn't restoring properly - TODO added.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 1214abc..51e6c1e 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -68,6 +68,12 @@
     public static final int PHONE_UID = 1001;
 
     /**
+     * Defines the UID/GID for the WIFI supplicant process.
+     * @hide
+     */
+    public static final int WIFI_UID = 1010;
+
+    /**
      * Defines the start of a range of UIDs (and GIDs), going from this
      * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
      * to applications.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index b6bc8a5..2b36904 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -16,12 +16,16 @@
 
 package com.android.providers.settings;
 
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.EOFException;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.zip.CRC32;
 
 import android.backup.BackupDataInput;
 import android.backup.BackupDataOutput;
@@ -34,7 +38,9 @@
 import android.media.AudioManager;
 import android.net.Uri;
 import android.net.wifi.WifiManager;
+import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
@@ -52,6 +58,13 @@
     private static final String KEY_SYNC = "sync_providers";
     private static final String KEY_LOCALE = "locale";
 
+    private static final int STATE_SYSTEM = 0;
+    private static final int STATE_SECURE = 1;
+    private static final int STATE_SYNC   = 2;
+    private static final int STATE_LOCALE = 3;
+    private static final int STATE_WIFI   = 4;
+    private static final int STATE_SIZE   = 5; // The number of state items
+
     private static String[] sortedSystemKeys = null;
     private static String[] sortedSecureKeys = null;
 
@@ -87,20 +100,22 @@
         byte[] secureSettingsData = getSecureSettings();
         byte[] syncProviders = mSettingsHelper.getSyncProviders();
         byte[] locale = mSettingsHelper.getLocaleData();
-        
-        data.writeEntityHeader(KEY_SYSTEM, systemSettingsData.length);
-        data.writeEntityData(systemSettingsData, systemSettingsData.length);
+        byte[] wifiData = getFileData(FILE_WIFI_SUPPLICANT);
 
-        data.writeEntityHeader(KEY_SECURE, secureSettingsData.length);
-        data.writeEntityData(secureSettingsData, secureSettingsData.length);
+        long[] stateChecksums = readOldChecksums(oldState);
 
-        data.writeEntityHeader(KEY_SYNC, syncProviders.length);
-        data.writeEntityData(syncProviders, syncProviders.length);
+        stateChecksums[STATE_SYSTEM] =
+                writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data);
+        stateChecksums[STATE_SECURE] =
+                writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data);
+        stateChecksums[STATE_SYNC] =
+                writeIfChanged(stateChecksums[STATE_SYNC], KEY_SYNC, syncProviders, data);
+        stateChecksums[STATE_LOCALE] =
+                writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data);
+        stateChecksums[STATE_WIFI] =
+                writeIfChanged(stateChecksums[STATE_WIFI], FILE_WIFI_SUPPLICANT, wifiData, data);
 
-        data.writeEntityHeader(KEY_LOCALE, locale.length);
-        data.writeEntityData(locale, locale.length);
-
-        backupFile(FILE_WIFI_SUPPLICANT, data);
+        writeNewChecksums(stateChecksums, newState);
     }
 
     @Override
@@ -115,11 +130,15 @@
             final int size = data.getDataSize();
             if (KEY_SYSTEM.equals(key)) {
                 restoreSettings(data, Settings.System.CONTENT_URI);
+                mSettingsHelper.applyAudioSettings();
             } else if (KEY_SECURE.equals(key)) {
                 restoreSettings(data, Settings.Secure.CONTENT_URI);
-// TODO: Re-enable WIFI restore when we figure out a solution for the permissions
-//            } else if (FILE_WIFI_SUPPLICANT.equals(key)) {
-//                restoreFile(FILE_WIFI_SUPPLICANT, data);
+            } else if (FILE_WIFI_SUPPLICANT.equals(key)) {
+                restoreFile(FILE_WIFI_SUPPLICANT, data);
+                FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
+                        FileUtils.S_IRUSR | FileUtils.S_IWUSR |
+                        FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+                        Process.myUid(), Process.WIFI_UID);
             } else if (KEY_SYNC.equals(key)) {
                 mSettingsHelper.setSyncProviders(data);
             } else if (KEY_LOCALE.equals(key)) {
@@ -132,6 +151,49 @@
         }
     }
 
+    private long[] readOldChecksums(ParcelFileDescriptor oldState) throws IOException {
+        long[] stateChecksums = new long[STATE_SIZE];
+
+        DataInputStream dataInput = new DataInputStream(
+                new FileInputStream(oldState.getFileDescriptor()));
+        for (int i = 0; i < STATE_SIZE; i++) {
+            try {
+                stateChecksums[i] = dataInput.readLong();
+            } catch (EOFException eof) {
+                break;
+            }
+        }
+        dataInput.close();
+        return stateChecksums;
+    }
+
+    private void writeNewChecksums(long[] checksums, ParcelFileDescriptor newState)
+            throws IOException {
+        DataOutputStream dataOutput = new DataOutputStream(
+                new FileOutputStream(newState.getFileDescriptor()));
+        for (int i = 0; i < STATE_SIZE; i++) {
+            dataOutput.writeLong(checksums[i]);
+        }
+        dataOutput.close();
+    }
+
+    private long writeIfChanged(long oldChecksum, String key, byte[] data,
+            BackupDataOutput output) {
+        CRC32 checkSummer = new CRC32();
+        checkSummer.update(data);
+        long newChecksum = checkSummer.getValue();
+        if (oldChecksum == newChecksum) {
+            return oldChecksum;
+        }
+        try {
+            output.writeEntityHeader(key, data.length);
+            output.writeEntityData(data, data.length);
+        } catch (IOException ioe) {
+            // Bail
+        }
+        return newChecksum;
+    }
+
     private byte[] getSystemSettings() {
         Cursor sortedCursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION,
                 null, null, Settings.NameValueTable.NAME);
@@ -248,7 +310,7 @@
         return result;
     }
 
-    private void backupFile(String filename, BackupDataOutput data) {
+    private byte[] getFileData(String filename) {
         try {
             File file = new File(filename);
             if (file.exists()) {
@@ -260,14 +322,13 @@
                     got = fis.read(bytes, offset, bytes.length - offset);
                     if (got > 0) offset += got;
                 } while (offset < bytes.length && got > 0);
-                data.writeEntityHeader(filename, bytes.length);
-                data.writeEntityData(bytes, bytes.length);
+                return bytes;
             } else {
-                data.writeEntityHeader(filename, 0);
-                data.writeEntityData(EMPTY_DATA, 0);
+                return EMPTY_DATA;
             }
         } catch (IOException ioe) {
             Log.w(TAG, "Couldn't backup " + filename);
+            return EMPTY_DATA;
         }
     }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 2c5775a..ca739e6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -167,6 +167,9 @@
         // Check if locale was set by the user:
         Configuration conf = mContext.getResources().getConfiguration();
         Locale loc = conf.locale;
+        // TODO: The following is not working as intended because the network is forcing a locale
+        // change after registering. Need to find some other way to detect if the user manually
+        // changed the locale
         if (conf.userSetLocale) return; // Don't change if user set it in the SetupWizard
 
         final String[] availableLocales = mContext.getAssets().getLocales();
@@ -193,6 +196,14 @@
         } catch (RemoteException e) {
             // Intentionally left blank
         }
+    }
 
+    /**
+     * Informs the audio service of changes to the settings so that
+     * they can be re-read and applied.
+     */
+    void applyAudioSettings() {
+        AudioManager am = new AudioManager(mContext);
+        am.reloadAudioSettings();
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 2abf8b3..c0de9a5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -126,11 +126,14 @@
         // a notification and then using the contract class to get their data,
         // the system property will be updated and they'll get the new data.
 
+        boolean backedUpDataChanged = false;
         String property = null, table = uri.getPathSegments().get(0);
         if (table.equals("system")) {
             property = Settings.System.SYS_PROP_SETTING_VERSION;
+            backedUpDataChanged = true;
         } else if (table.equals("secure")) {
             property = Settings.Secure.SYS_PROP_SETTING_VERSION;
+            backedUpDataChanged = true;
         } else if (table.equals("gservices")) {
             property = Settings.Gservices.SYS_PROP_SETTING_VERSION;
         }
@@ -142,7 +145,9 @@
         }
 
         // Inform the backup manager about a data change
-        mBackupManager.dataChanged();
+        if (backedUpDataChanged) {
+            mBackupManager.dataChanged();
+        }
         // Now send the notification through the content framework.
 
         String notify = uri.getQueryParameter("notify");