summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--api/current.xml44
-rw-r--r--core/java/android/app/ActivityManagerNative.java65
-rw-r--r--core/java/android/app/ActivityThread.java135
-rw-r--r--core/java/android/app/ApplicationThreadNative.java35
-rw-r--r--core/java/android/app/BackupAgent.java (renamed from core/java/android/backup/BackupService.java)75
-rw-r--r--core/java/android/app/IActivityManager.java9
-rw-r--r--core/java/android/app/IApplicationThread.java11
-rw-r--r--core/java/android/app/IBackupAgent.aidl (renamed from core/java/android/backup/IBackupService.aidl)6
-rw-r--r--core/java/android/backup/IBackupManager.aidl16
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java25
-rw-r--r--core/java/android/content/pm/PackageParser.java13
-rw-r--r--core/res/AndroidManifest.xml7
-rw-r--r--core/res/res/values/attrs_manifest.xml11
-rw-r--r--core/res/res/values/public.xml4
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--services/java/com/android/server/BackupManagerService.java246
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java148
-rw-r--r--services/java/com/android/server/am/BackupRecord.java57
-rw-r--r--tests/backup/AndroidManifest.xml7
-rw-r--r--tests/backup/src/com/android/backuptest/BackupTestAgent.java (renamed from tests/backup/src/com/android/backuptest/BackupTestService.java)6
21 files changed, 740 insertions, 187 deletions
diff --git a/Android.mk b/Android.mk
index ca41e2f..1f4a375 100644
--- a/Android.mk
+++ b/Android.mk
@@ -73,6 +73,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 \
@@ -85,7 +86,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 860cce4..fe14aae 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2143,6 +2143,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"
@@ -2374,6 +2385,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"
@@ -3584,28 +3606,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">
- * &lt;!-- Use the class "MyBackupService" to perform backups for my app --&gt;
- * &lt;service android:name=".MyBackupService"&gt;
- * &lt;intent-filter&gt;
- * &lt;action android:name="android.backup.BackupService.SERVICE" /&gt;
- * &lt;/intent-filter&gt;
- * &lt;/service&gt;</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 0a24bb7..392bc60 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 3c3b2d9..ca32cdd 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 39485e2..959ef6c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -547,6 +547,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,