diff options
| author | 2009-04-29 15:16:57 -0700 | |
|---|---|---|
| committer | 2009-04-29 15:16:57 -0700 | |
| commit | f8997690da3790a1ff87f1f96a6e68fbb79b5ddc (patch) | |
| tree | 620dc745ffd15f960b350675f44fe85886f96ef4 | |
| parent | 55a664f87bc073c775e3d1b36beaf4783697b277 (diff) | |
| parent | 487529a70cd1479ae8d6bbfb356be7e72542c185 (diff) | |
am 487529a: First baby steps towards settings backup
Merge commit '487529a70cd1479ae8d6bbfb356be7e72542c185'
* commit '487529a70cd1479ae8d6bbfb356be7e72542c185':
First baby steps towards settings backup
| -rw-r--r-- | Android.mk | 3 | ||||
| -rw-r--r-- | api/current.xml | 87 | ||||
| -rw-r--r-- | core/java/android/backup/BackupService.java | 106 | ||||
| -rw-r--r-- | core/java/android/backup/IBackupManager.aidl | 32 | ||||
| -rw-r--r-- | core/java/android/backup/IBackupService.aidl | 53 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 9 | ||||
| -rw-r--r-- | core/java/com/android/internal/backup/IBackupTransport.aidl | 21 | ||||
| -rw-r--r-- | services/java/com/android/server/BackupManagerService.java | 191 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 7 |
9 files changed, 509 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk index 88f023feccde..20c93e6a7ed5 100644 --- a/Android.mk +++ b/Android.mk @@ -70,6 +70,8 @@ LOCAL_SRC_FILES += \ 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 @@ LOCAL_SRC_FILES += \ 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 1f73152a911e..9ad4bf473481 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=""android.service.action.BACKUP"" + 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=""backup"" + 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 000000000000..5cfa4f273620 --- /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 000000000000..40cebddedc79 --- /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 000000000000..24544bd26510 --- /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 e8daa6e9a8b6..2aa3695bbaa5 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1279,6 +1279,15 @@ public abstract class Context { 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 000000000000..d64a3032f075 --- /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 000000000000..de14c337d989 --- /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 efca2cb95adb..b19e2eeb71ea 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -310,6 +310,13 @@ class ServerThread extends Thread { } 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); |