First baby steps towards settings backup

This change adds a sketched outline of the backup system architecture, with
all of the major pieces represented other than client-side helpers for
specific types of data.  IBackupManager and BackupService are public so that
we can write test apps against SDK-domain symbols from the outset.

What code exists in this change hasn't been tested and may crash.  It's the
beginnings of the real implementation but of course is barely begun.
diff --git a/Android.mk b/Android.mk
index de5f5f8..82a8735 100644
--- a/Android.mk
+++ b/Android.mk
@@ -67,6 +67,8 @@
 	core/java/android/app/ITransientNotification.aidl \
 	core/java/android/app/IWallpaperService.aidl \
 	core/java/android/app/IWallpaperServiceCallback.aidl \
+	core/java/android/backup/IBackupManager.aidl \
+	core/java/android/backup/IBackupService.aidl \
 	core/java/android/bluetooth/IBluetoothA2dp.aidl \
 	core/java/android/bluetooth/IBluetoothDevice.aidl \
 	core/java/android/bluetooth/IBluetoothDeviceCallback.aidl \
@@ -102,6 +104,7 @@
 	core/java/com/android/internal/app/IUsageStats.aidl \
 	core/java/com/android/internal/appwidget/IAppWidgetService.aidl \
 	core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
+	core/java/com/android/internal/backup/IBackupTransport.aidl \
 	core/java/com/android/internal/os/IResultReceiver.aidl \
 	core/java/com/android/internal/view/IInputContext.aidl \
 	core/java/com/android/internal/view/IInputContextCallback.aidl \
diff --git a/api/current.xml b/api/current.xml
index 18e8c57..75eec10 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -21740,6 +21740,82 @@
 </field>
 </class>
 </package>
+<package name="android.backup"
+>
+<class name="BackupService"
+ extends="android.app.Service"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="BackupService"
+ type="android.backup.BackupService"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onBackup"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="oldStateFd" type="int">
+</parameter>
+<parameter name="dataFd" type="int">
+</parameter>
+<parameter name="newStateFd" type="int">
+</parameter>
+</method>
+<method name="onBind"
+ return="android.os.IBinder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="onRestore"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dataFd" type="int">
+</parameter>
+<parameter name="newStateFd" type="int">
+</parameter>
+</method>
+<field name="SERVICE_ACTION"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.service.action.BACKUP&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+</package>
 <package name="android.content"
 >
 <class name="ActivityNotFoundException"
@@ -24893,6 +24969,17 @@
  visibility="public"
 >
 </field>
+<field name="BACKUP_SERVICE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;backup&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="BIND_AUTO_CREATE"
  type="int"
  transient="false"
diff --git a/core/java/android/backup/BackupService.java b/core/java/android/backup/BackupService.java
new file mode 100644
index 0000000..5cfa4f2
--- /dev/null
+++ b/core/java/android/backup/BackupService.java
@@ -0,0 +1,106 @@
+/*
+ * 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 android.backup;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.backup.IBackupService;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * This is the central interface between an application and Android's
+ * settings backup mechanism.
+ * 
+ * <p><em>Not hidden but API subject to change and should not be published</em>
+ */
+
+public abstract class BackupService extends Service {
+    /**
+     * Service Action: Participate in the backup infrastructure.  Applications
+     * that wish to use the Android backup mechanism must provide an exported
+     * subclass of BackupService and give it an {@link android.content.IntentFilter
+     * IntentFilter} that accepts this action. 
+     */
+    @SdkConstant(SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_ACTION = "android.service.action.BACKUP";
+
+    /**
+     * The application is being asked to write any data changed since the
+     * last time it performed a backup operation.  The state data recorded
+     * during the last backup pass is provided in the oldStateFd file descriptor.
+     * If oldStateFd is negative, no old state is available and the application
+     * should perform a full backup.  In both cases, a representation of the
+     * final backup state after this pass should be written to the file pointed
+     * to by the newStateFd file descriptor.
+     * 
+     * @param oldStateFd An open, read-only file descriptor pointing to the last
+     *                   backup state provided by the application.  May be negative,
+     *                   in which case no prior state is being provided and the
+     *                   application should perform a full backup.
+     * @param dataFd An open, read/write file descriptor pointing to the backup data
+     *               destination.  Typically the application will use backup helper
+     *               classes to write to this file.
+     * @param newStateFd An open, read/write file descriptor pointing to an empty
+     *                   file.  The application should record the final backup state
+     *                   here after writing the requested data to dataFd.
+     */
+    public abstract void onBackup(int oldStateFd, int dataFd, int newStateFd);
+    
+    /**
+     * The application is being restored from backup, and should replace any
+     * existing data with the contents of the backup.  The backup data is
+     * provided in the file pointed to by the dataFd file descriptor.  Once
+     * the restore is finished, the application should write a representation
+     * of the final state to the newStateFd file descriptor, 
+     * 
+     * @param dataFd An open, read-only file descriptor pointing to a full snapshot
+     *               of the application's data.
+     * @param newStateFd An open, read/write file descriptor pointing to an empty
+     *                   file.  The application should record the final backup state
+     *                   here after restoring its data from dataFd.
+     */
+    public abstract void onRestore(int dataFd, int newStateFd);
+
+
+    // ----- Core implementation -----
+    
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    private final IBinder mBinder = new BackupServiceBinder().asBinder();
+
+    // ----- IBackupService binder interface -----
+    private class BackupServiceBinder extends IBackupService.Stub {
+        public void doBackup(int oldStateFd, int dataFd, int newStateFd)
+                throws RemoteException {
+            // !!! TODO - real implementation; for now just invoke the callbacks directly
+            Log.v("BackupServiceBinder", "doBackup() invoked");
+            BackupService.this.onBackup(oldStateFd, dataFd, newStateFd);
+        }
+
+        public void doRestore(int dataFd, int newStateFd) throws RemoteException {
+            // !!! TODO - real implementation; for now just invoke the callbacks directly
+            Log.v("BackupServiceBinder", "doRestore() invoked");
+            BackupService.this.onRestore(dataFd, newStateFd);
+        }
+    }
+}
diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl
new file mode 100644
index 0000000..40cebdd
--- /dev/null
+++ b/core/java/android/backup/IBackupManager.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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 android.backup;
+
+/**
+ * Direct interface to the Backup Manager Service that applications invoke on.  The only
+ * operation currently needed is a simple notification that the app has made changes to
+ * data it wishes to back up, so the system should run a backup pass.
+ *
+ * {@hide pending API solidification}
+ */
+interface IBackupManager {
+    /**
+     * Tell the system service that the caller has made changes to its
+     * data, and therefore needs to undergo a backup pass.
+     */
+    oneway void dataChanged();
+}
diff --git a/core/java/android/backup/IBackupService.aidl b/core/java/android/backup/IBackupService.aidl
new file mode 100644
index 0000000..24544bd
--- /dev/null
+++ b/core/java/android/backup/IBackupService.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright 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 android.backup;
+
+/**
+ * Interface presented by applications being asked to participate in the
+ * backup & restore mechanism.  End user code does not typically implement
+ * this interface; they subclass BackupService instead.
+ *
+ * {@hide}
+ */
+interface IBackupService {
+    /**
+     * Request that the app perform an incremental backup.
+     *
+     * @param oldStateFd Read-only file containing the description blob of the
+     *        app's data state as of the last backup operation's completion.
+     *
+     * @param dataFd Read-write file, empty when onBackup() is called, that
+     *        is the data destination for this backup pass's incrementals.
+     *
+     * @param newStateFd Read-write file, empty when onBackup() is called,
+     *        where the new state blob is to be recorded.
+     */
+    void doBackup(int oldStateFd, int dataFd, int newStateFd);
+
+    /**
+     * Restore an entire data snapshot to the application.
+     *
+     * @param dataFd Read-only file containing the full data snapshot of the
+     *        app's backup.  This is to be a <i>replacement</i> of the app's
+     *        current data, not to be merged into it.
+     *
+     * @param newStateFd Read-write file, empty when onRestore() is called,
+     *        that is to be written with the state description that holds after
+     *        the restore has been completed.
+     */
+    void doRestore(int dataFd, int newStateFd);
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a301449..f2ad248 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1269,6 +1269,15 @@
     public static final String APPWIDGET_SERVICE = "appwidget";
     
     /**
+     * Use with {@link #getSystemService} to retrieve an
+     * {@blink android.backup.IBackupManager IBackupManager} for communicating
+     * with the backup mechanism.
+     *
+     * @see #getSystemService
+     */
+    public static final String BACKUP_SERVICE = "backup";
+    
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
new file mode 100644
index 0000000..d64a303
--- /dev/null
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.internal.backup;
+
+/** {@hide} */
+interface IBackupTransport {
+}
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
new file mode 100644
index 0000000..de14c33
--- /dev/null
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -0,0 +1,191 @@
+/*
+ * 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.server;
+
+import android.backup.BackupService;
+import android.backup.IBackupService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import android.backup.IBackupManager;
+
+import java.lang.String;
+import java.util.HashSet;
+import java.util.List;
+
+class BackupManagerService extends IBackupManager.Stub {
+    private static final String TAG = "BackupManagerService";
+    private static final boolean DEBUG = true;
+    
+    private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
+
+    private static final int MSG_RUN_BACKUP = 1;
+    
+    private Context mContext;
+    private PackageManager mPackageManager;
+    private final BackupHandler mBackupHandler = new BackupHandler();
+    // map UIDs to the set of backup client services within that UID's app set
+    private SparseArray<HashSet<ServiceInfo>> mBackupParticipants
+        = new SparseArray<HashSet<ServiceInfo>>();
+    // set of backup services that have pending changes
+    private HashSet<ServiceInfo> mPendingBackups = new HashSet<ServiceInfo>();
+    private final Object mQueueLock = new Object();
+
+    
+    // ----- Handler that runs the actual backup process asynchronously -----
+
+    private class BackupHandler extends Handler implements ServiceConnection {
+        private volatile Object mBindSignaller = new Object();
+        private volatile boolean mBinding = false;
+        private IBackupService mTargetService = null;
+
+        public void handleMessage(Message msg) {
+
+            switch (msg.what) {
+            case MSG_RUN_BACKUP:
+            {
+                // snapshot the pending-backup set and work on that
+                HashSet<ServiceInfo> queue;
+                synchronized (mQueueLock) {
+                    queue = mPendingBackups;
+                    mPendingBackups = new HashSet<ServiceInfo>();
+                    // !!! TODO: start a new backup-queue journal file too
+                }
+                
+                // Walk the set of pending backups, setting up the relevant files and
+                // invoking the backup service in each participant
+                Intent backupIntent = new Intent(BackupService.SERVICE_ACTION);
+                for (ServiceInfo service : queue) {
+                    mBinding = true;
+                    mTargetService = null;
+
+                    backupIntent.setClassName(service.packageName, service.name);
+                    Log.d(TAG, "binding to " + backupIntent);
+                    if (mContext.bindService(backupIntent, this, 0)) {
+                        synchronized (mBindSignaller) {
+                            while (mTargetService == null && mBinding == true) {
+                                try {
+                                    mBindSignaller.wait();
+                                } catch (InterruptedException e) {
+                                }
+                            }
+                        }
+                        if (mTargetService != null) {
+                            try {
+                                Log.d(TAG, "invoking doBackup() on " + backupIntent);
+                                // !!! TODO: set up files
+                                mTargetService.doBackup(-1, -1, -1);
+                            } catch (RemoteException e) {
+                                Log.d(TAG, "Remote target " + backupIntent
+                                        + " threw during backup:");
+                                e.printStackTrace();
+                            }
+                            mContext.unbindService(this);
+                        }
+                    } else {
+                        Log.d(TAG, "Unable to bind to " + backupIntent);
+                    }
+                }
+            }
+            break;
+            }
+        }
+        
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (mBindSignaller) {
+                mTargetService = IBackupService.Stub.asInterface(service);
+                mBinding = false;
+                mBindSignaller.notifyAll();
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (mBindSignaller) {
+                mTargetService = null;
+                mBinding = false;
+                mBindSignaller.notifyAll();
+            }
+        }
+    }
+    
+    public BackupManagerService(Context context) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+
+        // Identify the backup participants
+        // !!! TODO: also watch package-install to keep this up to date
+        List<ResolveInfo> services = mPackageManager.queryIntentServices(
+                new Intent(BackupService.SERVICE_ACTION), 0);
+        if (DEBUG) {
+            Log.v(TAG, "Backup participants: " + services.size());
+            for (ResolveInfo ri : services) {
+                Log.v(TAG, "    " + ri + " : " + ri.filter);
+            }
+        }
+
+        // Build our mapping of uid to backup client services
+        for (ResolveInfo ri : services) {
+            int uid = ri.serviceInfo.applicationInfo.uid;
+            HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
+            if (set == null) {
+                set = new HashSet<ServiceInfo>();
+                mBackupParticipants.put(uid, set);
+            }
+            set.add(ri.serviceInfo);
+        }
+    }
+
+    
+    // ----- IBackupManager binder interface -----
+    
+    public void dataChanged() throws RemoteException {
+        // Record that we need a backup pass for the caller.  Since multiple callers
+        // may share a uid, we need to note all candidates within that uid and schedule
+        // a backup pass for each of them.
+        
+        HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
+        if (targets != null) {
+            synchronized (mQueueLock) {
+                // Note that this client has made data changes that need to be backed up
+                // !!! add them to the set of pending packages
+                for (ServiceInfo service : targets) {
+                    if (mPendingBackups.add(service)) {
+                        // !!! TODO: write to the pending-backup journal file in case of crash
+                    }
+                }
+
+                // Schedule a backup pass in a few minutes.  As backup-eligible data
+                // keeps changing, continue to defer the backup pass until things
+                // settle down, to avoid extra overhead.
+                mBackupHandler.removeMessages(MSG_RUN_BACKUP);
+                mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
+            }
+        }
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1f508a6..8b7260b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -306,6 +306,13 @@
             }
 
             try {
+                Log.i(TAG, "Starting Backup Service");
+                ServiceManager.addService(Context.BACKUP_SERVICE, new BackupManagerService(context));
+            } catch (Throwable e) {
+                Log.e(TAG, "Failure starting Backup Service", e);
+            }
+
+            try {
                 Log.i(TAG, "Starting AppWidget Service");
                 appWidget = new AppWidgetService(context);
                 ServiceManager.addService(Context.APPWIDGET_SERVICE, appWidget);