diff options
author | Christopher Tate <ctate@google.com> | 2009-05-14 11:12:14 -0700 |
---|---|---|
committer | Christopher Tate <ctate@google.com> | 2009-05-31 13:10:03 -0700 |
commit | 181fafaf48208978b8ba2022683ffa78aaeddde1 (patch) | |
tree | 7c062847d418415e28813e70aac53c8c47e4ff69 | |
parent | c01159bb00f7273f9b051dfbbe6bc10d54d3a846 (diff) | |
download | frameworks_base-181fafaf48208978b8ba2022683ffa78aaeddde1.zip frameworks_base-181fafaf48208978b8ba2022683ffa78aaeddde1.tar.gz frameworks_base-181fafaf48208978b8ba2022683ffa78aaeddde1.tar.bz2 |
Retool the backup process to use a new 'BackupAgent' class
Backups will be handled by launching the application in a special
mode under which no activities or services will be started, only
the BackupAgent subclass named in the app's android:backupAgent
manifest property. This takes the place of the BackupService class
used earlier during development.
In the cases of *full* backup or restore, an application that does
not supply its own BackupAgent will be launched in a restricted
manner; in particular, it will be using the default Application
class rather than any manifest-declared one. This ensures that the
app is not running any code that may try to manipulate its data
while the backup system reads/writes its data set.
21 files changed, 740 insertions, 187 deletions
@@ -70,6 +70,7 @@ LOCAL_SRC_FILES += \ core/java/android/app/IActivityPendingResult.aidl \ core/java/android/app/IActivityWatcher.aidl \ core/java/android/app/IAlarmManager.aidl \ + core/java/android/app/IBackupAgent.aidl \ core/java/android/app/IInstrumentationWatcher.aidl \ core/java/android/app/IIntentReceiver.aidl \ core/java/android/app/IIntentSender.aidl \ @@ -82,7 +83,6 @@ LOCAL_SRC_FILES += \ 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 \ diff --git a/api/current.xml b/api/current.xml index f572a4d..3e22a13 100644 --- a/api/current.xml +++ b/api/current.xml @@ -2077,6 +2077,17 @@ visibility="public" > </field> +<field name="allowBackup" + type="int" + transient="false" + volatile="false" + value="16843393" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="allowClearUserData" type="int" transient="false" @@ -2308,6 +2319,17 @@ visibility="public" > </field> +<field name="backupAgent" + type="int" + transient="false" + volatile="false" + value="16843392" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="baselineAlignBottom" type="int" transient="false" @@ -3507,28 +3529,6 @@ visibility="public" > </field> -<field name="donut_resource_pad31" - type="int" - transient="false" - volatile="false" - value="16843393" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="donut_resource_pad32" - type="int" - transient="false" - volatile="false" - value="16843392" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="donut_resource_pad4" type="int" transient="false" diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 16f0a30..3d3d7d5 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; import android.content.res.Configuration; @@ -1021,6 +1022,33 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeStrongBinder(binder); return true; } + + case START_BACKUP_AGENT_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + ApplicationInfo info = ApplicationInfo.CREATOR.createFromParcel(data); + int backupRestoreMode = data.readInt(); + boolean success = bindBackupAgent(info, backupRestoreMode); + reply.writeNoException(); + reply.writeInt(success ? 1 : 0); + return true; + } + + case BACKUP_AGENT_CREATED_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String packageName = data.readString(); + IBinder agent = data.readStrongBinder(); + backupAgentCreated(packageName, agent); + reply.writeNoException(); + return true; + } + + case UNBIND_BACKUP_AGENT_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + ApplicationInfo info = ApplicationInfo.CREATOR.createFromParcel(data); + unbindBackupAgent(info); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -1681,6 +1709,43 @@ class ActivityManagerProxy implements IActivityManager return binder; } + public boolean bindBackupAgent(ApplicationInfo app, int backupRestoreMode) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + app.writeToParcel(data, 0); + data.writeInt(backupRestoreMode); + mRemote.transact(START_BACKUP_AGENT_TRANSACTION, data, reply, 0); + reply.readException(); + boolean success = reply.readInt() != 0; + reply.recycle(); + data.recycle(); + return success; + } + + public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(packageName); + data.writeStrongBinder(agent); + mRemote.transact(BACKUP_AGENT_CREATED_TRANSACTION, data, reply, 0); + reply.recycle(); + data.recycle(); + } + + public void unbindBackupAgent(ApplicationInfo app) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + app.writeToParcel(data, 0); + mRemote.transact(UNBIND_BACKUP_AGENT_TRANSACTION, data, reply, 0); + reply.readException(); + reply.recycle(); + data.recycle(); + } + public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher) throws RemoteException { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 06e0a45..29e57cd 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -115,6 +115,7 @@ public final class ActivityThread { private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; private static final boolean DEBUG_BROADCAST = false; private static final boolean DEBUG_RESULTS = false; + private static final boolean DEBUG_BACKUP = true; private static final long MIN_TIME_BETWEEN_GCS = 5*1000; private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";"); private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003; @@ -499,7 +500,7 @@ public final class ActivityThread { return mResources; } - public Application makeApplication() { + public Application makeApplication(boolean forceDefaultAppClass) { if (mApplication != null) { return mApplication; } @@ -507,7 +508,7 @@ public final class ActivityThread { Application app = null; String appClass = mApplicationInfo.className; - if (appClass == null) { + if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application"; } @@ -1199,6 +1200,16 @@ public final class ActivityThread { } } + private static final class CreateBackupAgentData { + ApplicationInfo appInfo; + int backupMode; + public String toString() { + return "CreateBackupAgentData{appInfo=" + appInfo + + " backupAgent=" + appInfo.backupAgentName + + " mode=" + backupMode + "}"; + } + } + private static final class CreateServiceData { IBinder token; ServiceInfo info; @@ -1239,6 +1250,7 @@ public final class ActivityThread { Bundle instrumentationArgs; IInstrumentationWatcher instrumentationWatcher; int debugMode; + boolean restrictedBackupMode; Configuration config; boolean handlingProfiling; public String toString() { @@ -1374,6 +1386,21 @@ public final class ActivityThread { queueOrSendMessage(H.RECEIVER, r); } + public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) { + CreateBackupAgentData d = new CreateBackupAgentData(); + d.appInfo = app; + d.backupMode = backupMode; + + queueOrSendMessage(H.CREATE_BACKUP_AGENT, d); + } + + public final void scheduleDestroyBackupAgent(ApplicationInfo app) { + CreateBackupAgentData d = new CreateBackupAgentData(); + d.appInfo = app; + + queueOrSendMessage(H.DESTROY_BACKUP_AGENT, d); + } + public final void scheduleCreateService(IBinder token, ServiceInfo info) { CreateServiceData s = new CreateServiceData(); @@ -1419,7 +1446,7 @@ public final class ActivityThread { ApplicationInfo appInfo, List<ProviderInfo> providers, ComponentName instrumentationName, String profileFile, Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, - int debugMode, Configuration config, + int debugMode, boolean isRestrictedBackupMode, Configuration config, Map<String, IBinder> services) { Process.setArgV0(processName); @@ -1437,6 +1464,7 @@ public final class ActivityThread { data.instrumentationArgs = instrumentationArgs; data.instrumentationWatcher = instrumentationWatcher; data.debugMode = debugMode; + data.restrictedBackupMode = isRestrictedBackupMode; data.config = config; queueOrSendMessage(H.BIND_APPLICATION, data); } @@ -1718,6 +1746,8 @@ public final class ActivityThread { public static final int ACTIVITY_CONFIGURATION_CHANGED = 125; public static final int RELAUNCH_ACTIVITY = 126; public static final int PROFILER_CONTROL = 127; + public static final int CREATE_BACKUP_AGENT = 128; + public static final int DESTROY_BACKUP_AGENT = 129; String codeToString(int code) { if (localLOGV) { switch (code) { @@ -1749,6 +1779,8 @@ public final class ActivityThread { case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED"; case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY"; case PROFILER_CONTROL: return "PROFILER_CONTROL"; + case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT"; + case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT"; } } return "(unknown)"; @@ -1851,6 +1883,12 @@ public final class ActivityThread { case PROFILER_CONTROL: handleProfilerControl(msg.arg1 != 0, (String)msg.obj); break; + case CREATE_BACKUP_AGENT: + handleCreateBackupAgent((CreateBackupAgentData)msg.obj); + break; + case DESTROY_BACKUP_AGENT: + handleDestroyBackupAgent((CreateBackupAgentData)msg.obj); + break; } } } @@ -1908,6 +1946,8 @@ public final class ActivityThread { Application mInitialApplication; final ArrayList<Application> mAllApplications = new ArrayList<Application>(); + // set of instantiated backup agents, keyed by package name + final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>(); static final ThreadLocal sThreadLocal = new ThreadLocal(); Instrumentation mInstrumentation; String mInstrumentationAppDir = null; @@ -2269,7 +2309,7 @@ public final class ActivityThread { } try { - Application app = r.packageInfo.makeApplication(); + Application app = r.packageInfo.makeApplication(false); if (localLOGV) Log.v(TAG, "Performing launch of " + r); if (localLOGV) Log.v( @@ -2464,7 +2504,7 @@ public final class ActivityThread { } try { - Application app = packageInfo.makeApplication(); + Application app = packageInfo.makeApplication(false); if (localLOGV) Log.v( TAG, "Performing receive of " + data.intent @@ -2507,6 +2547,85 @@ public final class ActivityThread { } } + // Instantiate a BackupAgent and tell it that it's alive + private final void handleCreateBackupAgent(CreateBackupAgentData data) { + if (DEBUG_BACKUP) Log.v(TAG, "handleCreateBackupAgent: " + data); + + // no longer idle; we have backup work to do + unscheduleGcIdler(); + + // instantiate the BackupAgent class named in the manifest + PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo); + String packageName = packageInfo.mPackageName; + if (mBackupAgents.get(packageName) != null) { + Log.d(TAG, "BackupAgent " + " for " + packageName + + " already exists"); + return; + } + + BackupAgent agent = null; + String classname = data.appInfo.backupAgentName; + if (classname == null) { + if (data.backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) { + Log.e(TAG, "Attempted incremental backup but no defined agent for " + + packageName); + return; + } + classname = "android.app.FullBackupAgent"; + } + try { + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance(); + } catch (Exception e) { + throw new RuntimeException("Unable to instantiate backup agent " + + data.appInfo.backupAgentName + ": " + e.toString(), e); + } + + // set up the agent's context + try { + if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent " + + data.appInfo.backupAgentName); + + ApplicationContext context = new ApplicationContext(); + context.init(packageInfo, null, this); + context.setOuterContext(agent); + agent.attach(context); + agent.onCreate(); + + // tell the OS that we're live now + IBinder binder = agent.onBind(); + try { + ActivityManagerNative.getDefault().backupAgentCreated(packageName, binder); + } catch (RemoteException e) { + // nothing to do. + } + mBackupAgents.put(packageName, agent); + } catch (Exception e) { + throw new RuntimeException("Unable to create BackupAgent " + + data.appInfo.backupAgentName + ": " + e.toString(), e); + } + } + + // Tear down a BackupAgent + private final void handleDestroyBackupAgent(CreateBackupAgentData data) { + if (DEBUG_BACKUP) Log.v(TAG, "handleDestroyBackupAgent: " + data); + + PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo); + String packageName = packageInfo.mPackageName; + BackupAgent agent = mBackupAgents.get(packageName); + if (agent != null) { + try { + agent.onDestroy(); + } catch (Exception e) { + Log.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo); + e.printStackTrace(); + } + mBackupAgents.remove(packageName); + } else { + Log.w(TAG, "Attempt to destroy unknown backup agent " + data); + } + } + private final void handleCreateService(CreateServiceData data) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. @@ -2532,7 +2651,7 @@ public final class ActivityThread { ApplicationContext context = new ApplicationContext(); context.init(packageInfo, null, this); - Application app = packageInfo.makeApplication(); + Application app = packageInfo.makeApplication(false); context.setOuterContext(service); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); @@ -3694,7 +3813,9 @@ public final class ActivityThread { mInstrumentation = new Instrumentation(); } - Application app = data.info.makeApplication(); + // If the app is being launched for full backup or restore, bring it up in + // a restricted environment with the base application class. + Application app = data.info.makeApplication(data.restrictedBackupMode); mInitialApplication = app; List<ProviderInfo> providers = data.providers; diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index f243185..e28fd0f 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -230,11 +230,13 @@ public abstract class ApplicationThreadNative extends Binder IBinder binder = data.readStrongBinder(); IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder); int testMode = data.readInt(); + boolean restrictedBackupMode = (data.readInt() != 0); Configuration config = Configuration.CREATOR.createFromParcel(data); HashMap<String, IBinder> services = data.readHashMap(null); bindApplication(packageName, info, providers, testName, profileName, - testArgs, testWatcher, testMode, config, services); + testArgs, testWatcher, testMode, restrictedBackupMode, + config, services); return true; } @@ -339,6 +341,15 @@ public abstract class ApplicationThreadNative extends Binder setSchedulingGroup(group); return true; } + + case SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(data); + int backupMode = data.readInt(); + scheduleCreateBackupAgent(appInfo, backupMode); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -492,6 +503,24 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } + public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + app.writeToParcel(data, 0); + data.writeInt(backupMode); + mRemote.transact(SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION, data, null, 0); + data.recycle(); + } + + public final void scheduleDestroyBackupAgent(ApplicationInfo app) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + app.writeToParcel(data, 0); + mRemote.transact(SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION, data, null, 0); + data.recycle(); + } + public final void scheduleCreateService(IBinder token, ServiceInfo info) throws RemoteException { Parcel data = Parcel.obtain(); @@ -551,7 +580,8 @@ class ApplicationThreadProxy implements IApplicationThread { public final void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers, ComponentName testName, String profileName, Bundle testArgs, IInstrumentationWatcher testWatcher, int debugMode, - Configuration config, Map<String, IBinder> services) throws RemoteException { + boolean restrictedBackupMode, Configuration config, + Map<String, IBinder> services) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeString(packageName); @@ -567,6 +597,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeBundle(testArgs); data.writeStrongInterface(testWatcher); data.writeInt(debugMode); + data.writeInt(restrictedBackupMode ? 1 : 0); config.writeToParcel(data, 0); data.writeMap(services); mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null, diff --git a/core/java/android/backup/BackupService.java b/core/java/android/app/BackupAgent.java index 50a5921..997bfdc 100644 --- a/core/java/android/backup/BackupService.java +++ b/core/java/android/app/BackupAgent.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package android.backup; +package android.app; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.app.Service; -import android.backup.IBackupService; -import android.content.Intent; +import android.app.IBackupAgent; +import android.backup.BackupDataOutput; +import android.content.Context; +import android.content.ContextWrapper; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -30,31 +29,18 @@ import android.util.Log; * This is the central interface between an application and Android's * settings backup mechanism. * - * In order to use the backup service, your application must implement a - * subclass of BackupService, and declare an intent filter - * in the application manifest specifying that your BackupService subclass - * handles the {@link BackupService#SERVICE_ACTION} intent action. For example: - * - * <pre class="prettyprint"> - * <!-- Use the class "MyBackupService" to perform backups for my app --> - * <service android:name=".MyBackupService"> - * <intent-filter> - * <action android:name="android.backup.BackupService.SERVICE" /> - * </intent-filter> - * </service></pre> - * * @hide pending API solidification */ +public abstract class BackupAgent extends ContextWrapper { + public BackupAgent() { + super(null); + } -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.backup.BackupService.SERVICE"; + public void onCreate() { + } + + public void onDestroy() { + } /** * The application is being asked to write any data changed since the @@ -91,7 +77,8 @@ public abstract class BackupService extends Service { * file. The application should record the final backup state * here after restoring its data from dataFd. */ - public abstract void onRestore(ParcelFileDescriptor /* TODO: BackupDataInput */ data, ParcelFileDescriptor newState); + public abstract void onRestore(ParcelFileDescriptor /* TODO: BackupDataInput */ data, + ParcelFileDescriptor newState); // ----- Core implementation ----- @@ -100,29 +87,33 @@ public abstract class BackupService extends Service { * Returns the private interface called by the backup system. Applications will * not typically override this. */ - public IBinder onBind(Intent intent) { - if (intent.getAction().equals(SERVICE_ACTION)) { - return mBinder; - } - return null; + public IBinder onBind() { + return mBinder; } private final IBinder mBinder = new BackupServiceBinder().asBinder(); + /** @hide */ + public void attach(Context context) { + attachBaseContext(context); + } + // ----- IBackupService binder interface ----- - private class BackupServiceBinder extends IBackupService.Stub { + private class BackupServiceBinder extends IBackupAgent.Stub { + private static final String TAG = "BackupServiceBinder"; + public void doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState) throws RemoteException { // !!! TODO - real implementation; for now just invoke the callbacks directly - Log.v("BackupServiceBinder", "doBackup() invoked"); - BackupDataOutput output = new BackupDataOutput(BackupService.this, + Log.v(TAG, "doBackup() invoked"); + BackupDataOutput output = new BackupDataOutput(BackupAgent.this, data.getFileDescriptor()); try { - BackupService.this.onBackup(oldState, output, newState); + BackupAgent.this.onBackup(oldState, output, newState); } catch (RuntimeException ex) { - Log.d("BackupService", "onBackup (" - + BackupService.this.getClass().getName() + ") threw", ex); + Log.d("BackupAgent", "onBackup (" + + BackupAgent.this.getClass().getName() + ") threw", ex); throw ex; } } @@ -130,8 +121,8 @@ public abstract class BackupService extends Service { public void doRestore(ParcelFileDescriptor data, ParcelFileDescriptor newState) throws RemoteException { // !!! TODO - real implementation; for now just invoke the callbacks directly - Log.v("BackupServiceBinder", "doRestore() invoked"); - BackupService.this.onRestore(data, newState); + Log.v(TAG, "doRestore() invoked"); + BackupAgent.this.onRestore(data, newState); } } } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index d15a154..c948aec 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -21,6 +21,7 @@ import android.content.ContentProviderNative; import android.content.IContentProvider; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.ProviderInfo; @@ -149,6 +150,11 @@ public interface IActivityManager extends IInterface { public void serviceDoneExecuting(IBinder token) throws RemoteException; public IBinder peekService(Intent service, String resolvedType) throws RemoteException; + public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode) + throws RemoteException; + public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException; + public void unbindBackupAgent(ApplicationInfo appInfo) throws RemoteException; + public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher) throws RemoteException; @@ -397,4 +403,7 @@ public interface IActivityManager extends IInterface { int SHUTDOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+86; int STOP_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+87; int RESUME_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+88; + int START_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+89; + int BACKUP_AGENT_CREATED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+90; + int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index ec03d3a..bca1fea 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -59,6 +59,11 @@ public interface IApplicationThread extends IInterface { int configChanges) throws RemoteException; void scheduleReceiver(Intent intent, ActivityInfo info, int resultCode, String data, Bundle extras, boolean sync) throws RemoteException; + static final int BACKUP_MODE_INCREMENTAL = 0; + static final int BACKUP_MODE_FULL = 1; + static final int BACKUP_MODE_RESTORE = 2; + void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) throws RemoteException; + void scheduleDestroyBackupAgent(ApplicationInfo app) throws RemoteException; void scheduleCreateService(IBinder token, ServiceInfo info) throws RemoteException; void scheduleBindService(IBinder token, Intent intent, boolean rebind) throws RemoteException; @@ -71,8 +76,8 @@ public interface IApplicationThread extends IInterface { static final int DEBUG_WAIT = 2; void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers, ComponentName testName, String profileName, Bundle testArguments, - IInstrumentationWatcher testWatcher, int debugMode, Configuration config, Map<String, - IBinder> services) throws RemoteException; + IInstrumentationWatcher testWatcher, int debugMode, boolean restrictedBackupMode, + Configuration config, Map<String, IBinder> services) throws RemoteException; void scheduleExit() throws RemoteException; void requestThumbnail(IBinder token) throws RemoteException; void scheduleConfigurationChanged(Configuration config) throws RemoteException; @@ -119,4 +124,6 @@ public interface IApplicationThread extends IInterface { int REQUEST_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26; int PROFILER_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27; int SET_SCHEDULING_GROUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28; + int SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29; + int SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30; } diff --git a/core/java/android/backup/IBackupService.aidl b/core/java/android/app/IBackupAgent.aidl index 1bde8ea..bb9f008 100644 --- a/core/java/android/backup/IBackupService.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -14,18 +14,18 @@ * limitations under the License. */ -package android.backup; +package android.app; import android.os.ParcelFileDescriptor; /** * 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. + * this interface; they subclass BackupAgent instead. * * {@hide} */ -interface IBackupService { +interface IBackupAgent { /** * Request that the app perform an incremental backup. * diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl index cf22798..3468d70 100644 --- a/core/java/android/backup/IBackupManager.aidl +++ b/core/java/android/backup/IBackupManager.aidl @@ -34,8 +34,22 @@ interface IBackupManager { oneway void dataChanged(String packageName); /** + * Notifies the Backup Manager Service that an agent has become available. This + * method is only invoked by the Activity Manager. + * !!! TODO: permission + */ + oneway void agentConnected(String packageName, IBinder agent); + + /** + * Notify the Backup Manager Service that an agent has unexpectedly gone away. + * This method is only invoked by the Activity Manager. + * !!! TODO: permission + */ + oneway void agentDisconnected(String packageName); + + /** * Schedule a full backup of the given package. - * !!! TODO: protect with a signature-or-system permission? + * !!! TODO: permission */ oneway void scheduleFullBackup(String packageName); } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index ad022e7..f16eb74 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -58,11 +58,22 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Class implementing the Application's manage space * functionality. From the "manageSpaceActivity" * attribute. This is an optional attribute and will be null if - * application's dont specify it in their manifest + * applications don't specify it in their manifest */ public String manageSpaceActivityName; /** + * Class implementing the Application's backup functionality. From + * the "backupAgent" attribute. This is an optional attribute and + * will be null if the application does not specify it in its manifest. + * + * <p>If android:allowBackup is set to false, this attribute is ignored. + * + * {@hide} + */ + public String backupAgentName; + + /** * Value for {@link #flags}: if set, this application is installed in the * device's system image. */ @@ -93,7 +104,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_PERSISTENT = 1<<3; /** - * Value for {@link #flags}: set to true iif this application holds the + * Value for {@link #flags}: set to true if this application holds the * {@link android.Manifest.permission#FACTORY_TEST} permission and the * device is running in factory test mode. */ @@ -126,6 +137,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_TEST_ONLY = 1<<8; /** + * Value for {@link #flags}: this is false if the application has set + * its android:allowBackup to false, true otherwise. + * + * {@hide} + */ + public static final int FLAG_ALLOW_BACKUP = 1<<10; + + /** * Flags associated with the application. Any combination of * {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE}, * {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and @@ -285,6 +304,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(targetSdkVersion); dest.writeInt(enabled ? 1 : 0); dest.writeString(manageSpaceActivityName); + dest.writeString(backupAgentName); dest.writeInt(descriptionRes); dest.writeIntArray(supportsDensities); } @@ -315,6 +335,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { targetSdkVersion = source.readInt(); enabled = source.readInt() != 0; manageSpaceActivityName = source.readString(); + backupAgentName = source.readString(); descriptionRes = source.readInt(); supportsDensities = source.createIntArray(); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index ee20aee..36d154e 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1176,6 +1176,19 @@ public class PackageParser { outError); } + boolean allowBackup = sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_allowBackup, true); + if (allowBackup) { + ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP; + String backupAgent = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestApplication_backupAgent); + if (backupAgent != null) { + ai.backupAgentName = buildClassName(pkgName, backupAgent, outError); + Log.v(TAG, "android:backupAgent = " + ai.backupAgentName + + " from " + pkgName + "+" + backupAgent); + } + } + TypedValue v = sa.peekValue( com.android.internal.R.styleable.AndroidManifestApplication_label); if (v != null && (ai.labelRes=v.resourceId) == 0) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 36f7dfb..b105aaa 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -972,6 +972,13 @@ android:description="@string/permdesc_batteryStats" android:protectionLevel="normal" /> + <!-- Allows an application to control the backup and restore process + @hide pending API council --> + <permission android:name="android.permission.BACKUP" + android:label="@string/permlab_backup" + android:description="@string/permdesc_backup" + android:protectionLevel="signature" /> + <!-- Allows an application to tell the AppWidget service which application can access AppWidget's data. The normal user flow is that a user picks an AppWidget to go into a particular host, thereby giving that diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 7b48267..817a566 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -570,6 +570,15 @@ <!-- Application's requirement for five way navigation --> <attr name="reqFiveWayNav" format="boolean" /> + <!-- The name of the class implementing <code>BackupAgent</code> to manage + backup and restore of the application's settings to external storage. --> + <attr name="backupAgent" format="string" /> + + <!-- Whether the application allows its data to be backed up at all. This + attribute defaults to 'true': unless the application opts out, the + user will be able to back up its data to desktop storage. --> + <attr name="allowBackup" format="boolean" /> + <!-- The <code>manifest</code> tag is the root of an <code>AndroidManifest.xml</code> file, describing the contents of an Android package (.apk) file. One @@ -643,6 +652,8 @@ <attr name="manageSpaceActivity" /> <attr name="allowClearUserData" /> <attr name="testOnly" /> + <attr name="backupAgent" /> + <attr name="allowBackup" /> </declare-styleable> <!-- The <code>permission</code> tag declares a security permission that can be diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 621270e..c2c84d6 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1113,7 +1113,9 @@ <public type="attr" name="gestureStrokeAngleThreshold" /> <public type="attr" name="eventsInterceptionEnabled" /> <public type="attr" name="fadeEnabled" /> - + <public type="attr" name="backupAgent" /> + <public type="attr" name="allowBackup" /> + <public-padding type="attr" name="donut_resource_pad" end="0x0101029f" /> <public-padding type="id" name="donut_resource_pad" end="0x01020040" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e311abd..7078bbf 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -540,6 +540,11 @@ collected battery statistics. Not for use by normal applications.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_backup">control system backup and restore</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_backup">Allows the application to control the system's backup and restore mechanism. Not for use by normal applications.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_internalSystemWindow">display unauthorized windows</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_internalSystemWindow">Allows the creation of diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index e3fff81..82ed1e3 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -16,17 +16,16 @@ package com.android.server; -import android.backup.BackupService; -import android.backup.IBackupService; +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.app.IApplicationThread; +import android.app.IBackupAgent; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -49,6 +48,7 @@ import java.lang.String; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; class BackupManagerService extends IBackupManager.Stub { @@ -62,22 +62,28 @@ class BackupManagerService extends IBackupManager.Stub { private Context mContext; private PackageManager mPackageManager; + private final IActivityManager mActivityManager; 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>>(); + private SparseArray<HashSet<ApplicationInfo>> mBackupParticipants + = new SparseArray<HashSet<ApplicationInfo>>(); // set of backup services that have pending changes private class BackupRequest { - public ServiceInfo service; + public ApplicationInfo appInfo; public boolean fullBackup; - BackupRequest(ServiceInfo svc, boolean isFull) { - service = svc; + BackupRequest(ApplicationInfo app, boolean isFull) { + appInfo = app; fullBackup = isFull; } + + public String toString() { + return "BackupRequest{app=" + appInfo + " full=" + fullBackup + "}"; + } } // Backups that we haven't started yet. - private HashMap<ComponentName,BackupRequest> mPendingBackups = new HashMap(); + private HashMap<ApplicationInfo,BackupRequest> mPendingBackups + = new HashMap<ApplicationInfo,BackupRequest>(); // Backups that we have started. These are separate to prevent starvation // if an app keeps re-enqueuing itself. private ArrayList<BackupRequest> mBackupQueue; @@ -89,6 +95,7 @@ class BackupManagerService extends IBackupManager.Stub { public BackupManagerService(Context context) { mContext = context; mPackageManager = context.getPackageManager(); + mActivityManager = ActivityManagerNative.getDefault(); // Set up our bookkeeping mStateDir = new File(Environment.getDataDirectory(), "backup"); @@ -151,7 +158,7 @@ class BackupManagerService extends IBackupManager.Stub { // ----- Run the actual backup process asynchronously ----- - private class BackupHandler extends Handler implements ServiceConnection { + private class BackupHandler extends Handler { public void handleMessage(Message msg) { switch (msg.what) { @@ -163,31 +170,20 @@ class BackupManagerService extends IBackupManager.Stub { for (BackupRequest b: mPendingBackups.values()) { mBackupQueue.add(b); } - mPendingBackups = new HashMap<ComponentName,BackupRequest>(); + mPendingBackups = new HashMap<ApplicationInfo,BackupRequest>(); } // !!! TODO: start a new backup-queue journal file too // WARNING: If we crash after this line, anything in mPendingBackups will // be lost. FIX THIS. } - startOneService(); + startOneAgent(); break; } } - - public void onServiceConnected(ComponentName name, IBinder service) { - Log.d(TAG, "onServiceConnected name=" + name + " service=" + service); - IBackupService bs = IBackupService.Stub.asInterface(service); - processOneBackup(name, bs); - } - - public void onServiceDisconnected(ComponentName name) { - // TODO: handle backup being interrupted - } } - void startOneService() { + void startOneAgent() { // Loop until we find someone to start or the queue empties out. - Intent intent = new Intent(BackupService.SERVICE_ACTION); while (true) { BackupRequest request; synchronized (mQueueLock) { @@ -205,14 +201,19 @@ class BackupManagerService extends IBackupManager.Stub { // Take it off the queue when we're done. } - intent.setClassName(request.service.packageName, request.service.name); - Log.d(TAG, "binding to " + intent); + Log.d(TAG, "starting agent for " + request); + // !!! TODO: need to handle the restore case? + int mode = (request.fullBackup) + ? IApplicationThread.BACKUP_MODE_FULL + : IApplicationThread.BACKUP_MODE_INCREMENTAL; try { - if (mContext.bindService(intent, mBackupHandler, Context.BIND_AUTO_CREATE)) { - Log.d(TAG, "awaiting service object for " + intent); + if (mActivityManager.bindBackupAgent(request.appInfo, mode)) { + Log.d(TAG, "awaiting agent for " + request); // success return; } + } catch (RemoteException e) { + // can't happen; activity manager is local } catch (SecurityException ex) { // Try for the next one. Log.d(TAG, "error in bind", ex); @@ -220,23 +221,23 @@ class BackupManagerService extends IBackupManager.Stub { } } - void processOneBackup(ComponentName name, IBackupService bs) { - try { - Log.d(TAG, "processOneBackup doBackup() on " + name); + void processOneBackup(String packageName, IBackupAgent bs) { + Log.d(TAG, "processOneBackup doBackup() on " + packageName); - BackupRequest request; - synchronized (mQueueLock) { - if (mBackupQueue == null) { - Log.d(TAG, "mBackupQueue is null. WHY?"); - } - request = mBackupQueue.get(0); + BackupRequest request; + synchronized (mQueueLock) { + if (mBackupQueue == null) { + Log.d(TAG, "mBackupQueue is null. WHY?"); } + request = mBackupQueue.get(0); + } + try { // !!! TODO right now these naming schemes limit applications to // one backup service per package - File savedStateName = new File(mStateDir, request.service.packageName); - File backupDataName = new File(mDataDir, request.service.packageName + ".data"); - File newStateName = new File(mStateDir, request.service.packageName + ".new"); + File savedStateName = new File(mStateDir, request.appInfo.packageName); + File backupDataName = new File(mDataDir, request.appInfo.packageName + ".data"); + File newStateName = new File(mStateDir, request.appInfo.packageName + ".new"); // In a full backup, we pass a null ParcelFileDescriptor as // the saved-state "file" @@ -280,7 +281,7 @@ class BackupManagerService extends IBackupManager.Stub { Log.d(TAG, "File not found on backup: "); fnf.printStackTrace(); } catch (RemoteException e) { - Log.d(TAG, "Remote target " + name + " threw during backup:"); + Log.d(TAG, "Remote target " + packageName + " threw during backup:"); e.printStackTrace(); } catch (Exception e) { Log.w(TAG, "Final exception guard in backup: "); @@ -289,37 +290,45 @@ class BackupManagerService extends IBackupManager.Stub { synchronized (mQueueLock) { mBackupQueue.remove(0); } - mContext.unbindService(mBackupHandler); + + if (request != null) { + try { + mActivityManager.unbindBackupAgent(request.appInfo); + } catch (RemoteException e) { + // can't happen + } + } // start the next one - startOneService(); + startOneAgent(); } - // Add the backup services in the given package to our set of known backup participants. - // If 'packageName' is null, adds all backup services in the system. + // Add the backup agents in the given package to our set of known backup participants. + // If 'packageName' is null, adds all backup agents in the whole system. void addPackageParticipantsLocked(String packageName) { - List<ResolveInfo> services = mPackageManager.queryIntentServices( - new Intent(BackupService.SERVICE_ACTION), 0); - addPackageParticipantsLockedInner(packageName, services); + // Look for apps that define the android:backupAgent attribute + List<ApplicationInfo> targetApps = allAgentApps(); + addPackageParticipantsLockedInner(packageName, targetApps); } - private void addPackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) { - for (ResolveInfo ri : services) { - if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) { - int uid = ri.serviceInfo.applicationInfo.uid; - HashSet<ServiceInfo> set = mBackupParticipants.get(uid); + private void addPackageParticipantsLockedInner(String packageName, + List<ApplicationInfo> targetApps) { + if (DEBUG) { + Log.v(TAG, "Adding " + targetApps.size() + " backup participants:"); + for (ApplicationInfo a : targetApps) { + Log.v(TAG, " " + a + " agent=" + a.backupAgentName); + } + } + + for (ApplicationInfo app : targetApps) { + if (packageName == null || app.packageName.equals(packageName)) { + int uid = app.uid; + HashSet<ApplicationInfo> set = mBackupParticipants.get(uid); if (set == null) { - set = new HashSet<ServiceInfo>(); + set = new HashSet<ApplicationInfo>(); mBackupParticipants.put(uid, set); } - if (DEBUG) { - Log.v(TAG, "Adding " + services.size() + " backup participants:"); - for (ResolveInfo svc : services) { - Log.v(TAG, " " + svc + " : " + svc.filter); - } - } - - set.add(ri.serviceInfo); + set.add(app); } } } @@ -327,19 +336,30 @@ class BackupManagerService extends IBackupManager.Stub { // Remove the given package's backup services from our known active set. If // 'packageName' is null, *all* backup services will be removed. void removePackageParticipantsLocked(String packageName) { - List<ResolveInfo> services = mPackageManager.queryIntentServices( - new Intent(BackupService.SERVICE_ACTION), 0); - removePackageParticipantsLockedInner(packageName, services); + List<ApplicationInfo> allApps = null; + if (packageName != null) { + allApps = new ArrayList<ApplicationInfo>(); + try { + ApplicationInfo app = mPackageManager.getApplicationInfo(packageName, 0); + allApps.add(app); + } catch (Exception e) { + // just skip it + } + } else { + // all apps with agents + allApps = allAgentApps(); + } + removePackageParticipantsLockedInner(packageName, allApps); } private void removePackageParticipantsLockedInner(String packageName, - List<ResolveInfo> services) { - for (ResolveInfo ri : services) { - if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) { - int uid = ri.serviceInfo.applicationInfo.uid; - HashSet<ServiceInfo> set = mBackupParticipants.get(uid); + List<ApplicationInfo> agents) { + for (ApplicationInfo app : agents) { + if (packageName == null || app.packageName.equals(packageName)) { + int uid = app.uid; + HashSet<ApplicationInfo> set = mBackupParticipants.get(uid); if (set != null) { - set.remove(ri.serviceInfo); + set.remove(app); if (set.size() == 0) { mBackupParticipants.put(uid, null); } @@ -348,6 +368,21 @@ class BackupManagerService extends IBackupManager.Stub { } } + // Returns the set of all applications that define an android:backupAgent attribute + private List<ApplicationInfo> allAgentApps() { + List<ApplicationInfo> allApps = mPackageManager.getInstalledApplications(0); + int N = allApps.size(); + if (N > 0) { + for (int a = N-1; a >= 0; a--) { + ApplicationInfo app = allApps.get(a); + if (app.backupAgentName == null) { + allApps.remove(a); + } + } + } + return allApps; + } + // Reset the given package's known backup participants. Unlike add/remove, the update // action cannot be passed a null package name. void updatePackageParticipantsLocked(String packageName) { @@ -357,10 +392,9 @@ class BackupManagerService extends IBackupManager.Stub { } // brute force but small code size - List<ResolveInfo> services = mPackageManager.queryIntentServices( - new Intent(BackupService.SERVICE_ACTION), 0); - removePackageParticipantsLockedInner(packageName, services); - addPackageParticipantsLockedInner(packageName, services); + List<ApplicationInfo> allApps = allAgentApps(); + removePackageParticipantsLockedInner(packageName, allApps); + addPackageParticipantsLockedInner(packageName, allApps); } // ----- IBackupManager binder interface ----- @@ -372,24 +406,29 @@ class BackupManagerService extends IBackupManager.Stub { Log.d(TAG, "dataChanged packageName=" + packageName); - HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid()); - Log.d(TAG, "targets=" + targets); + HashSet<ApplicationInfo> targets = mBackupParticipants.get(Binder.getCallingUid()); if (targets != null) { synchronized (mQueueLock) { // Note that this client has made data changes that need to be backed up - for (ServiceInfo service : targets) { + for (ApplicationInfo app : targets) { // validate the caller-supplied package name against the known set of // packages associated with this uid - if (service.packageName.equals(packageName)) { + if (app.packageName.equals(packageName)) { // Add the caller to the set of pending backups. If there is // one already there, then overwrite it, but no harm done. - mPendingBackups.put(new ComponentName(service.packageName, service.name), - new BackupRequest(service, true)); + BackupRequest req = new BackupRequest(app, false); + mPendingBackups.put(app, req); // !!! TODO: write to the pending-backup journal file in case of crash } } - Log.d(TAG, "Scheduling backup for " + mPendingBackups.size() + " participants"); + if (DEBUG) { + int numKeys = mPendingBackups.size(); + Log.d(TAG, "Scheduling backup for " + numKeys + " participants:"); + for (BackupRequest b : mPendingBackups.values()) { + Log.d(TAG, " + " + b + " agent=" + b.appInfo.backupAgentName); + } + } // 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. @@ -402,22 +441,35 @@ class BackupManagerService extends IBackupManager.Stub { // that uid or package itself. public void scheduleFullBackup(String packageName) throws RemoteException { // !!! TODO: protect with a signature-or-system permission? - HashSet<ServiceInfo> targets = new HashSet<ServiceInfo>(); synchronized (mQueueLock) { int numKeys = mBackupParticipants.size(); for (int index = 0; index < numKeys; index++) { int uid = mBackupParticipants.keyAt(index); - HashSet<ServiceInfo> servicesAtUid = mBackupParticipants.get(uid); - for (ServiceInfo service: servicesAtUid) { - if (service.packageName.equals(packageName)) { - mPendingBackups.put(new ComponentName(service.packageName, service.name), - new BackupRequest(service, true)); + HashSet<ApplicationInfo> servicesAtUid = mBackupParticipants.get(uid); + for (ApplicationInfo app: servicesAtUid) { + if (app.packageName.equals(packageName)) { + mPendingBackups.put(app, new BackupRequest(app, true)); } } } } } + // Callback: a requested backup agent has been instantiated + public void agentConnected(String packageName, IBinder agentBinder) { + Log.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder); + IBackupAgent bs = IBackupAgent.Stub.asInterface(agentBinder); + processOneBackup(packageName, bs); + } + + // Callback: a backup agent has failed to come up, or has unexpectedly quit. + // If the agent failed to come up in the first place, the agentBinder argument + // will be null. + public void agentDisconnected(String packageName) { + // TODO: handle backup being interrupted + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -428,12 +480,18 @@ class BackupManagerService extends IBackupManager.Stub { int uid = mBackupParticipants.keyAt(i); pw.print(" uid: "); pw.println(uid); - HashSet<ServiceInfo> services = mBackupParticipants.valueAt(i); - for (ServiceInfo s: services) { + HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i); + for (ApplicationInfo app: participants) { pw.print(" "); - pw.println(s.toString()); + pw.println(app.toString()); } } + pw.println("Pending:"); + Iterator<BackupRequest> br = mPendingBackups.values().iterator(); + while (br.hasNext()) { + pw.print(" "); + pw.println(br); + } } } } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index d1c40b4..3b26cb7 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -42,6 +42,7 @@ import android.app.IThumbnailReceiver; import android.app.Instrumentation; import android.app.PendingIntent; import android.app.ResultInfo; +import android.backup.IBackupManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.ContentResolver; @@ -131,6 +132,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final boolean DEBUG_PROCESSES = localLOGV || false; static final boolean DEBUG_USER_LEAVING = localLOGV || false; static final boolean DEBUG_RESULTS = localLOGV || false; + static final boolean DEBUG_BACKUP = localLOGV || true; static final boolean VALIDATE_TOKENS = false; static final boolean SHOW_ACTIVITY_START_TIME = true; @@ -633,6 +635,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new ArrayList<ServiceRecord>(); /** + * Backup/restore process management + */ + String mBackupAppName = null; + BackupRecord mBackupTarget = null; + + /** * List of PendingThumbnailsRecord objects of clients who are still * waiting to receive all of the thumbnails for a task. */ @@ -4669,6 +4677,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mPendingBroadcast = null; scheduleBroadcastsLocked(); } + if (mBackupTarget != null && mBackupTarget.app.pid == pid) { + Log.w(TAG, "Unattached app died before backup, skipping"); + try { + IBackupManager bm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + bm.agentDisconnected(app.info.packageName); + } catch (RemoteException e) { + // Can't happen; the backup manager is local + } + } } else { Log.w(TAG, "Spurious process start timeout - pid not known for " + app); } @@ -4757,11 +4775,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mWaitForDebugger = mOrigWaitForDebugger; } } + // If the app is being launched for restore or full backup, set it up specially + boolean isRestrictedBackupMode = false; + if (mBackupTarget != null && mBackupAppName.equals(processName)) { + isRestrictedBackupMode = (mBackupTarget.backupMode == BackupRecord.RESTORE) + || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL); + } thread.bindApplication(processName, app.instrumentationInfo != null ? app.instrumentationInfo : app.info, providers, app.instrumentationClass, app.instrumentationProfileFile, app.instrumentationArguments, app.instrumentationWatcher, testMode, - mConfiguration, getCommonServicesLocked()); + isRestrictedBackupMode, mConfiguration, getCommonServicesLocked()); updateLRUListLocked(app, false); app.lastRequestedGc = SystemClock.uptimeMillis(); } catch (Exception e) { @@ -4842,6 +4866,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + // Check whether the next backup agent is in this process... + if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.info.uid) { + if (DEBUG_BACKUP) Log.v(TAG, "New app is backup target, launching agent for " + app); + try { + thread.scheduleCreateBackupAgent(mBackupTarget.appInfo, mBackupTarget.backupMode); + } catch (Exception e) { + Log.w(TAG, "Exception scheduling backup agent creation: "); + e.printStackTrace(); + } + } + if (badApp) { // todo: Also need to kill application to deal with all // kinds of exceptions. @@ -9118,6 +9153,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.receivers.clear(); } + // If the app is undergoing backup, tell the backup manager about it + if (mBackupTarget != null && app.pid == mBackupTarget.app.pid) { + if (DEBUG_BACKUP) Log.d(TAG, "App " + mBackupTarget.appInfo + " died during backup"); + try { + IBackupManager bm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + bm.agentDisconnected(app.info.packageName); + } catch (RemoteException e) { + // can't happen; backup manager is local + } + } + // If the caller is restarting this app, then leave it in its // current lists and let the caller take care of it. if (restarting) { @@ -10234,6 +10281,105 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // ========================================================= + // BACKUP AND RESTORE + // ========================================================= + + // Cause the target app to be launched if necessary and its backup agent + // instantiated. The backup agent will invoke backupAgentCreated() on the + // activity manager to announce its creation. + public boolean bindBackupAgent(ApplicationInfo app, int backupMode) { + if (DEBUG_BACKUP) Log.v(TAG, "startBackupAgent: app=" + app + " mode=" + backupMode); + enforceCallingPermission("android.permission.BACKUP", "startBackupAgent"); + + synchronized(this) { + // !!! TODO: currently no check here that we're already bound + BatteryStatsImpl.Uid.Pkg.Serv ss = null; + BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + ss = stats.getServiceStatsLocked(app.uid, app.packageName, app.name); + } + + BackupRecord r = new BackupRecord(ss, app, backupMode); + ComponentName hostingName = new ComponentName(app.packageName, app.backupAgentName); + // startProcessLocked() returns existing proc's record if it's already running + ProcessRecord proc = startProcessLocked(app.processName, app, + false, 0, "backup", hostingName); + if (proc == null) { + Log.e(TAG, "Unable to start backup agent process " + r); + return false; + } + + r.app = proc; + mBackupTarget = r; + mBackupAppName = app.packageName; + + // If the process is already attached, schedule the creation of the backup agent now. + // If it is not yet live, this will be done when it attaches to the framework. + if (proc.thread != null) { + if (DEBUG_BACKUP) Log.v(TAG, "Agent proc already running: " + proc); + try { + proc.thread.scheduleCreateBackupAgent(app, backupMode); + } catch (RemoteException e) { + // !!! TODO: notify the backup manager that we crashed, or rely on + // death notices, or...? + } + } else { + if (DEBUG_BACKUP) Log.v(TAG, "Agent proc not running, waiting for attach"); + } + // Invariants: at this point, the target app process exists and the application + // is either already running or in the process of coming up. mBackupTarget and + // mBackupAppName describe the app, so that when it binds back to the AM we + // know that it's scheduled for a backup-agent operation. + } + + return true; + } + + // A backup agent has just come up + public void backupAgentCreated(String agentPackageName, IBinder agent) { + if (DEBUG_BACKUP) Log.v(TAG, "backupAgentCreated: " + agentPackageName + + " = " + agent); + + synchronized(this) { + if (!agentPackageName.equals(mBackupAppName)) { + Log.e(TAG, "Backup agent created for " + agentPackageName + " but not requested!"); + return; + } + + try { + IBackupManager bm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + bm.agentConnected(agentPackageName, agent); + } catch (RemoteException e) { + // can't happen; the backup manager service is local + } catch (Exception e) { + Log.w(TAG, "Exception trying to deliver BackupAgent binding: "); + e.printStackTrace(); + } + } + } + + // done with this agent + public void unbindBackupAgent(ApplicationInfo appInfo) { + if (DEBUG_BACKUP) Log.v(TAG, "unbindBackupAgent: " + appInfo); + + synchronized(this) { + if (!mBackupAppName.equals(appInfo.packageName)) { + Log.e(TAG, "Unbind of " + appInfo + " but is not the current backup target"); + return; + } + + try { + mBackupTarget.app.thread.scheduleDestroyBackupAgent(appInfo); + } catch (Exception e) { + Log.e(TAG, "Exception when unbinding backup agent:"); + e.printStackTrace(); + } + mBackupTarget = null; + mBackupAppName = null; + } + } + // ========================================================= // BROADCASTS // ========================================================= diff --git a/services/java/com/android/server/am/BackupRecord.java b/services/java/com/android/server/am/BackupRecord.java new file mode 100644 index 0000000..5ac8e0d --- /dev/null +++ b/services/java/com/android/server/am/BackupRecord.java @@ -0,0 +1,57 @@ +/* + * 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.am; + +import com.android.internal.os.BatteryStatsImpl; + +import android.content.pm.ApplicationInfo; + +/** @hide */ +class BackupRecord { + // backup/restore modes + public static final int BACKUP_NORMAL = 0; + public static final int BACKUP_FULL = 1; + public static final int RESTORE = 2; + + final BatteryStatsImpl.Uid.Pkg.Serv stats; + String stringName; // cached toString() output + final ApplicationInfo appInfo; // information about BackupAgent's app + final int backupMode; // full backup / incremental / restore + ProcessRecord app; // where this agent is running or null + + // ----- Implementation ----- + + BackupRecord(BatteryStatsImpl.Uid.Pkg.Serv _agentStats, ApplicationInfo _appInfo, + int _backupMode) { + stats = _agentStats; + appInfo = _appInfo; + backupMode = _backupMode; + } + + public String toString() { + if (stringName != null) { + return stringName; + } + StringBuilder sb = new StringBuilder(128); + sb.append("BackupRecord{") + .append(Integer.toHexString(System.identityHashCode(this))) + .append(' ').append(appInfo.packageName) + .append(' ').append(appInfo.name) + .append(' ').append(appInfo.backupAgentName).append('}'); + return stringName = sb.toString(); + } +}
\ No newline at end of file diff --git a/tests/backup/AndroidManifest.xml b/tests/backup/AndroidManifest.xml index eaeb5b7..3778742 100644 --- a/tests/backup/AndroidManifest.xml +++ b/tests/backup/AndroidManifest.xml @@ -1,6 +1,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.backuptest"> - <application> + <application android:backupAgent="BackupTestAgent"> <activity android:name="BackupTestActivity" android:label="_BackupTest"> <intent-filter> <action android:name="android.intent.action.MAIN" /> @@ -8,10 +8,5 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> - <service android:name="BackupTestService"> - <intent-filter> - <action android:name="android.backup.BackupService.SERVICE" /> - </intent-filter> - </service> </application> </manifest> diff --git a/tests/backup/src/com/android/backuptest/BackupTestService.java b/tests/backup/src/com/android/backuptest/BackupTestAgent.java index 00eb86e..11e520e 100644 --- a/tests/backup/src/com/android/backuptest/BackupTestService.java +++ b/tests/backup/src/com/android/backuptest/BackupTestAgent.java @@ -16,15 +16,15 @@ package com.android.backuptest; -import android.backup.BackupService; +import android.app.BackupAgent; import android.backup.BackupDataOutput; import android.backup.FileBackupHelper; import android.os.ParcelFileDescriptor; import android.util.Log; -public class BackupTestService extends BackupService +public class BackupTestAgent extends BackupAgent { - static final String TAG = "BackupTestService"; + static final String TAG = "BackupTestAgent"; @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, |