merge from eclair
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9585043..7eab984 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -18,7 +18,13 @@
         package="com.android.providers.userdictionary"
         android:sharedUserId="android.uid.shared">
 
-    <application android:process="android.process.acore">
+    <uses-permission android:name="android.permission.BACKUP_DATA" />
+
+    <application android:process="android.process.acore"
+        android:allowClearUserData="false"
+        android:backupAgent="DictionaryBackupAgent"
+        android:killAfterRestore="false"
+        >
         <provider android:name="UserDictionaryProvider" android:authorities="user_dictionary"
                 android:syncable="false" android:multiprocess="false"
                 android:readPermission="android.permission.READ_USER_DICTIONARY" 
diff --git a/src/com/android/providers/userdictionary/DictionaryBackupAgent.java b/src/com/android/providers/userdictionary/DictionaryBackupAgent.java
new file mode 100644
index 0000000..fc3783a
--- /dev/null
+++ b/src/com/android/providers/userdictionary/DictionaryBackupAgent.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.userdictionary;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+import java.util.zip.CRC32;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.backup.BackupHelperAgent;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.UserDictionary.Words;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Performs backup and restore of the User Dictionary.
+ */
+public class DictionaryBackupAgent extends BackupHelperAgent {
+
+    private static final String KEY_DICTIONARY = "userdictionary";
+
+    private static final int STATE_DICTIONARY = 0;
+    private static final int STATE_SIZE = 1;
+
+    private static final String SEPARATOR = "|";
+
+    private static final byte[] EMPTY_DATA = new byte[0];
+
+    private static final String TAG = "DictionaryBackupAgent";
+
+    private static final int COLUMN_WORD = 1;
+    private static final int COLUMN_FREQUENCY = 2;
+    private static final int COLUMN_LOCALE = 3;
+    private static final int COLUMN_APPID = 4;
+
+    private static final String[] PROJECTION = {
+        Words._ID,
+        Words.WORD,
+        Words.FREQUENCY,
+        Words.LOCALE,
+        Words.APP_ID
+    };
+
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState) throws IOException {
+
+        byte[] userDictionaryData = getDictionary();
+
+        long[] stateChecksums = readOldChecksums(oldState);
+
+        stateChecksums[STATE_DICTIONARY] =
+                writeIfChanged(stateChecksums[STATE_DICTIONARY], KEY_DICTIONARY,
+                        userDictionaryData, data);
+
+        writeNewChecksums(stateChecksums, newState);
+    }
+
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode,
+            ParcelFileDescriptor newState) throws IOException {
+
+        while (data.readNextHeader()) {
+            final String key = data.getKey();
+            final int size = data.getDataSize();
+            if (KEY_DICTIONARY.equals(key)) {
+                restoreDictionary(data, Words.CONTENT_URI);
+            } else {
+                data.skipEntityData();
+            }
+        }
+    }
+
+    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[] getDictionary() {
+        Cursor cursor = getContentResolver().query(Words.CONTENT_URI, PROJECTION,
+                null, null, Words.WORD);
+        if (cursor == null) return EMPTY_DATA;
+        if (!cursor.moveToFirst()) {
+            Log.e(TAG, "Couldn't read from the cursor");
+            cursor.close();
+            return EMPTY_DATA;
+        }
+        byte[] sizeBytes = new byte[4];
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(cursor.getCount() * 10);
+        try {
+            GZIPOutputStream gzip = new GZIPOutputStream(baos);
+            while (!cursor.isAfterLast()) {
+                String name = cursor.getString(COLUMN_WORD);
+                int frequency = cursor.getInt(COLUMN_FREQUENCY);
+                String locale = cursor.getString(COLUMN_LOCALE);
+                int appId = cursor.getInt(COLUMN_APPID);
+                String out = name + "|" + frequency + "|" + locale + "|" + appId;
+                byte[] line = out.getBytes();
+                writeInt(sizeBytes, 0, line.length);
+                gzip.write(sizeBytes);
+                gzip.write(line);
+                cursor.moveToNext();
+            }
+            gzip.finish();
+        } catch (IOException ioe) {
+            Log.e(TAG, "Couldn't compress the dictionary:\n" + ioe);
+            return EMPTY_DATA;
+        } finally {
+            cursor.close();
+        }
+        return baos.toByteArray();
+    }
+
+    private void restoreDictionary(BackupDataInput data, Uri contentUri) {
+        ContentValues cv = new ContentValues(2);
+        byte[] dictCompressed = new byte[data.getDataSize()];
+        byte[] dictionary = null;
+        try {
+            data.readEntityData(dictCompressed, 0, dictCompressed.length);
+            GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(dictCompressed));
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            byte[] tempData = new byte[1024];
+            int got;
+            while ((got = gzip.read(tempData)) > 0) {
+                baos.write(tempData, 0, got);
+            }
+            gzip.close();
+            dictionary = baos.toByteArray();
+        } catch (IOException ioe) {
+            Log.e(TAG, "Couldn't read and uncompress entity data:\n" + ioe);
+            return;
+        }
+        int pos = 0;
+        while (pos + 4 < dictionary.length) {
+            int length = readInt(dictionary, pos);
+            pos += 4;
+            if (pos + length > dictionary.length) {
+                Log.e(TAG, "Insufficient data");
+            }
+            String line = new String(dictionary, pos, length);
+            pos += length;
+            StringTokenizer st = new StringTokenizer(line, SEPARATOR);
+            String word;
+            String frequency;
+            try {
+                word = st.nextToken();
+                frequency = st.nextToken();
+                String locale = null;
+                String appid = null;
+                if (st.hasMoreTokens()) locale = st.nextToken();
+                if ("null".equalsIgnoreCase(locale)) locale = null;
+                if (st.hasMoreTokens()) appid = st.nextToken();
+                int frequencyInt = Integer.parseInt(frequency);
+                int appidInt = appid != null? Integer.parseInt(appid) : 0;
+
+                if (!TextUtils.isEmpty(frequency)) {
+                    cv.clear();
+                    cv.put(Words.WORD, word);
+                    cv.put(Words.FREQUENCY, frequencyInt);
+                    cv.put(Words.LOCALE, locale);
+                    cv.put(Words.APP_ID, appidInt);
+                    // Remove duplicate first
+                    getContentResolver().delete(contentUri, Words.WORD + "=?", new String[] {word});
+                    getContentResolver().insert(contentUri, cv);
+                }
+            } catch (NoSuchElementException nsee) {
+                Log.e(TAG, "Token format error\n" + nsee);
+            } catch (NumberFormatException nfe) {
+                Log.e(TAG, "Number format error\n" + nfe);
+            }
+        }
+    }
+
+    /**
+     * Write an int in BigEndian into the byte array.
+     * @param out byte array
+     * @param pos current pos in array
+     * @param value integer to write
+     * @return the index after adding the size of an int (4)
+     */
+    private int writeInt(byte[] out, int pos, int value) {
+        out[pos + 0] = (byte) ((value >> 24) & 0xFF);
+        out[pos + 1] = (byte) ((value >> 16) & 0xFF);
+        out[pos + 2] = (byte) ((value >>  8) & 0xFF);
+        out[pos + 3] = (byte) ((value >>  0) & 0xFF);
+        return pos + 4;
+    }
+
+    private int readInt(byte[] in, int pos) {
+        int result =
+                ((in[pos    ] & 0xFF) << 24) |
+                ((in[pos + 1] & 0xFF) << 16) |
+                ((in[pos + 2] & 0xFF) <<  8) |
+                ((in[pos + 3] & 0xFF) <<  0);
+        return result;
+    }
+}
diff --git a/src/com/android/providers/userdictionary/UserDictionaryProvider.java b/src/com/android/providers/userdictionary/UserDictionaryProvider.java
index c1928c2..59a0c24 100644
--- a/src/com/android/providers/userdictionary/UserDictionaryProvider.java
+++ b/src/com/android/providers/userdictionary/UserDictionaryProvider.java
@@ -17,6 +17,9 @@
 package com.android.providers.userdictionary;
 
 
+import java.util.HashMap;
+
+import android.backup.BackupManager;
 import android.content.ContentProvider;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -33,8 +36,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.util.HashMap;
-
 /**
  * Provides access to a database of user defined words. Each item has a word and a frequency.
  */
@@ -58,7 +59,9 @@
     private static final int WORDS = 1;
     
     private static final int WORD_ID = 2;
-    
+
+    private BackupManager mBackupManager;
+
     /**
      * This class helps open, create, and upgrade the database file.
      */
@@ -93,6 +96,7 @@
     @Override
     public boolean onCreate() {
         mOpenHelper = new DatabaseHelper(getContext());
+        mBackupManager = new BackupManager(getContext());
         return true;
     }
 
@@ -181,6 +185,7 @@
         if (rowId > 0) {
             Uri wordUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, rowId);
             getContext().getContentResolver().notifyChange(wordUri, null);
+            mBackupManager.dataChanged();
             return wordUri;
         }
 
@@ -207,6 +212,7 @@
         }
 
         getContext().getContentResolver().notifyChange(uri, null);
+        mBackupManager.dataChanged();
         return count;
     }
 
@@ -230,6 +236,7 @@
         }
 
         getContext().getContentResolver().notifyChange(uri, null);
+        mBackupManager.dataChanged();
         return count;
     }