am 487529a: First baby steps towards settings backup

Merge commit '487529a70cd1479ae8d6bbfb356be7e72542c185'

* commit '487529a70cd1479ae8d6bbfb356be7e72542c185':
  First baby steps towards settings backup
diff --git a/Android.mk b/Android.mk
index 88f023f..20c93e6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -70,6 +70,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 \
@@ -105,6 +107,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 1f73152a..9ad4bf4 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -23953,6 +23953,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"
@@ -27117,6 +27193,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 e8daa6e..2aa3695 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1279,6 +1279,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 efca2cb..b19e2ee 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -310,6 +310,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);