Automatically restore app data at install time

When an application being installed defines a backupAgent in its manifest, we
now automatically perform a restore of the latest-known-good data for that app.
This is defined as "data backed up by this app from this handset, if available;
otherwise data for this app as it existed when the device was initially
provisioned."  If neither option exists for the app, no restore action is
taken.

The CL involves major changes in the Backup and Package Managers...

* The Package Manager's act of installing an application has now been split
into two separate phases, with a data-restore phase optionally occurring
between these two PM actions.  First, the details of the install are performed
as usual.  Instead of immediately notifying install observers and issuing the
install-related broadcasts, the in-process install state is snapshotted and
the backup manager notified that a restore operation should be attempted.  It
does this by calling a new API on IBackupManager, passing a token by which it
identifies its in-progress install state.

The backup manager then downloads [if possible] the data for the newly-installed
application and invokes the app's backupAgent to do the restore.  After this
step, regardless of failure, it then calls back into the Package Manager to
indicate that the restore phase has been completed, supplying the token that
was passed in the original notification from the Package Manager.

The Package Manager then runs the final post-install actions: notifying install
observers and sending out all the appropriate broadcasts.  It's only at this
point that the app becomes visible to the Launcher and the rest of the OS.

... and a few other bits and pieces...

* The ApplicationInfo.backupAgentName field has been exposed to the SDK.  This
can be reverted if there's a reason to do so, but it wasn't clear that this
info needs to be hidden from 3rd party apps.

* Debug logging of restore set IDs and operation timeout tokens [used during
any asynchronous Backup Manager operation] are now consistently in hex for
readability.

* We now properly reset our binder identity before calling into the transport
during restore-set operations.  This fixes a permissions failure when a
single-app restore was attempted.

* The 'BackupTest' test app is no longer lumped onto the system partition
by default.

Change-Id: If3addefb846791f327e2a221de97c8d5d20ee7b3
diff --git a/api/current.xml b/api/current.xml
index 86043df..e123602 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -42639,6 +42639,16 @@
  visibility="public"
 >
 </field>
+<field name="backupAgentName"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="className"
  type="java.lang.String"
  transient="false"
diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl
index bf6c79f..d94b066 100644
--- a/core/java/android/backup/IBackupManager.aidl
+++ b/core/java/android/backup/IBackupManager.aidl
@@ -62,6 +62,12 @@
     void agentDisconnected(String packageName);
 
     /**
+     * Notify the Backup Manager Service that an application being installed will
+     * need a data-restore pass.  This method is only invoked by the Package Manager.
+     */
+    void restoreAtInstall(String packageName, int token);
+
+    /**
      * Enable/disable the backup service entirely.  When disabled, no backup
      * or restore operations will take place.  Data-changed notifications will
      * still be observed and collected, however, so that changes made while the
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 123d9b7..2e405c1 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -68,8 +68,6 @@
      * 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;
     
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 7a02a98..f793a00 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -154,6 +154,8 @@
     void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags,
             in String installerPackageName);
 
+    void finishPackageInstall(int token);
+
     /**
      * Delete a package.
      *
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index ea0fc65..f79a02a 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.IApplicationThread;
@@ -34,6 +35,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
@@ -113,6 +115,7 @@
 
     private Context mContext;
     private PackageManager mPackageManager;
+    IPackageManager mPackageManagerBinder;
     private IActivityManager mActivityManager;
     private PowerManager mPowerManager;
     private AlarmManager mAlarmManager;
@@ -179,13 +182,15 @@
         public IRestoreObserver observer;
         public long token;
         public PackageInfo pkgInfo;
+        public int pmToken; // in post-install restore, the PM's token for this transaction
 
         RestoreParams(IBackupTransport _transport, IRestoreObserver _obs,
-                long _token, PackageInfo _pkg) {
+                long _token, PackageInfo _pkg, int _pmToken) {
             transport = _transport;
             observer = _obs;
             token = _token;
             pkgInfo = _pkg;
+            pmToken = _pmToken;
         }
 
         RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token) {
@@ -193,6 +198,7 @@
             observer = _obs;
             token = _token;
             pkgInfo = null;
+            pmToken = 0;
         }
     }
 
@@ -302,7 +308,7 @@
                 RestoreParams params = (RestoreParams)msg.obj;
                 Log.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
                 (new PerformRestoreTask(params.transport, params.observer,
-                        params.token, params.pkgInfo)).run();
+                        params.token, params.pkgInfo, params.pmToken)).run();
                 break;
             }
 
@@ -349,6 +355,7 @@
     public BackupManagerService(Context context) {
         mContext = context;
         mPackageManager = context.getPackageManager();
+        mPackageManagerBinder = ActivityThread.getPackageManager();
         mActivityManager = ActivityManagerNative.getDefault();
 
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -1115,6 +1122,18 @@
         }
     }
 
+    // Get the restore-set token for the best-available restore set for this package:
+    // the active set if possible, else the ancestral one.  Returns zero if none available.
+    long getAvailableRestoreToken(String packageName) {
+        long token = mAncestralToken;
+        synchronized (mQueueLock) {
+            if (mEverStoredApps.contains(packageName)) {
+                token = mCurrentToken;
+            }
+        }
+        return token;
+    }
+
     // -----
     // Utility methods used by the asynchronous-with-timeout backup/restore operations
     boolean waitUntilOperationComplete(int token) {
@@ -1132,12 +1151,14 @@
             }
         }
         mBackupHandler.removeMessages(MSG_TIMEOUT);
-        if (DEBUG) Log.v(TAG, "operation " + token + " complete: finalState=" + finalState);
+        if (DEBUG) Log.v(TAG, "operation " + Integer.toHexString(token)
+                + " complete: finalState=" + finalState);
         return finalState == OP_ACKNOWLEDGED;
     }
 
     void prepareOperationTimeout(int token, long interval) {
-        if (DEBUG) Log.v(TAG, "starting timeout: token=" + token + " interval=" + interval);
+        if (DEBUG) Log.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
+                + " interval=" + interval);
         mCurrentOperations.put(token, OP_PENDING);
         Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0);
         mBackupHandler.sendMessageDelayed(msg, interval);
@@ -1476,6 +1497,7 @@
         private long mToken;
         private PackageInfo mTargetPackage;
         private File mStateDir;
+        private int mPmToken;
 
         class RestoreRequest {
             public PackageInfo app;
@@ -1488,11 +1510,12 @@
         }
 
         PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
-                long restoreSetToken, PackageInfo targetPackage) {
+                long restoreSetToken, PackageInfo targetPackage, int pmToken) {
             mTransport = transport;
             mObserver = observer;
             mToken = restoreSetToken;
             mTargetPackage = targetPackage;
+            mPmToken = pmToken;
 
             try {
                 mStateDir = new File(mBaseStateDir, transport.transportDirName());
@@ -1505,25 +1528,7 @@
             long startRealtime = SystemClock.elapsedRealtime();
             if (DEBUG) Log.v(TAG, "Beginning restore process mTransport=" + mTransport
                     + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken)
-                    + " mTargetPackage=" + mTargetPackage);
-            /**
-             * Restore sequence:
-             *
-             * 1. get the restore set description for our identity
-             * 2. for each app in the restore set:
-             *    2.a. if it's restorable on this device, add it to the restore queue
-             * 3. for each app in the restore queue:
-             *    3.a. clear the app data
-             *    3.b. get the restore data for the app from the transport
-             *    3.c. launch the backup agent for the app
-             *    3.d. agent.doRestore() with the data from the server
-             *    3.e. unbind the agent [and kill the app?]
-             * 4. shut down the transport
-             *
-             * On errors, we try our best to recover and move on to the next
-             * application, but if necessary we abort the whole operation --
-             * the user is waiting, after all.
-             */
+                    + " mTargetPackage=" + mTargetPackage + " mPmToken=" + mPmToken);
 
             PackageManagerBackupAgent pmAgent = null;
             int error = -1; // assume error
@@ -1609,6 +1614,7 @@
                         EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
                         return;
                     } else if (packageName.equals("")) {
+                        if (DEBUG) Log.v(TAG, "No next package, finishing restore");
                         break;
                     }
 
@@ -1733,6 +1739,15 @@
                     writeRestoreTokens();
                 }
 
+                // We must under all circumstances tell the Package Manager to
+                // proceed with install notifications if it's waiting for us.
+                if (mPmToken > 0) {
+                    if (DEBUG) Log.v(TAG, "finishing PM token " + mPmToken);
+                    try {
+                        mPackageManagerBinder.finishPackageInstall(mPmToken);
+                    } catch (RemoteException e) { /* can't happen */ }
+                }
+
                 // done; we can finally release the wakelock
                 mWakelock.release();
             }
@@ -2169,7 +2184,7 @@
     public String getCurrentTransport() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getCurrentTransport");
-        Log.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport);
+        if (DEBUG) Log.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport);
         return mCurrentTransport;
     }
 
@@ -2248,6 +2263,45 @@
         }
     }
 
+    // An application being installed will need a restore pass, then the Package Manager
+    // will need to be told when the restore is finished.
+    public void restoreAtInstall(String packageName, int token) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            Log.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
+                    + " attemping install-time restore");
+            return;
+        }
+
+        long restoreSet = getAvailableRestoreToken(packageName);
+        if (DEBUG) Log.v(TAG, "restoreAtInstall pkg=" + packageName
+                + " token=" + Integer.toHexString(token));
+
+        if (restoreSet != 0) {
+            // okay, we're going to attempt a restore of this package from this restore set.
+            // The eventual message back into the Package Manager to run the post-install
+            // steps for 'token' will be issued from the restore handling code.
+
+            // We can use a synthetic PackageInfo here because:
+            //   1. We know it's valid, since the Package Manager supplied the name
+            //   2. Only the packageName field will be used by the restore code
+            PackageInfo pkg = new PackageInfo();
+            pkg.packageName = packageName;
+
+            mWakelock.acquire();
+            Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
+            msg.obj = new RestoreParams(getTransport(mCurrentTransport), null,
+                    restoreSet, pkg, token);
+            mBackupHandler.sendMessage(msg);
+        } else {
+            // No way to attempt a restore; just tell the Package Manager to proceed
+            // with the post-install handling for this package.
+            if (DEBUG) Log.v(TAG, "No restore set -- skipping restore");
+            try {
+                mPackageManagerBinder.finishPackageInstall(token);
+            } catch (RemoteException e) { /* can't happen */ }
+        }
+    }
+
     // Hand off a restore session
     public IRestoreSession beginRestoreSession(String transport) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "beginRestoreSession");
@@ -2266,7 +2320,7 @@
     // completed the given outstanding asynchronous backup/restore operation.
     public void opComplete(int token) {
         synchronized (mCurrentOpLock) {
-            if (DEBUG) Log.v(TAG, "opComplete: " + token);
+            if (DEBUG) Log.v(TAG, "opComplete: " + Integer.toHexString(token));
             mCurrentOperations.put(token, OP_ACKNOWLEDGED);
             mCurrentOpLock.notifyAll();
         }
@@ -2289,6 +2343,7 @@
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                     "getAvailableRestoreSets");
 
+            long oldId = Binder.clearCallingIdentity();
             try {
                 if (mRestoreTransport == null) {
                     Log.w(TAG, "Null transport getting restore sets");
@@ -2302,6 +2357,8 @@
             } catch (Exception e) {
                 Log.e(TAG, "Error in getAvailableRestoreSets", e);
                 return null;
+            } finally {
+                Binder.restoreCallingIdentity(oldId);
             }
         }
 
@@ -2360,12 +2417,7 @@
             // So far so good; we're allowed to try to restore this package.  Now
             // check whether there is data for it in the current dataset, falling back
             // to the ancestral dataset if not.
-            long token = mAncestralToken;
-            synchronized (mQueueLock) {
-                if (mEverStoredApps.contains(packageName)) {
-                    token = mCurrentToken;
-                }
-            }
+            long token = getAvailableRestoreToken(packageName);
 
             // If we didn't come up with a place to look -- no ancestral dataset and
             // the app has never been backed up from this device -- there's nothing
@@ -2378,7 +2430,7 @@
             long oldId = Binder.clearCallingIdentity();
             mWakelock.acquire();
             Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
-            msg.obj = new RestoreParams(mRestoreTransport, observer, token, app);
+            msg.obj = new RestoreParams(mRestoreTransport, observer, token, app, 0);
             mBackupHandler.sendMessage(msg);
             Binder.restoreCallingIdentity(oldId);
             return 0;
@@ -2391,12 +2443,14 @@
             if (DEBUG) Log.d(TAG, "endRestoreSession");
 
             synchronized (this) {
+                long oldId = Binder.clearCallingIdentity();
                 try {
                     if (mRestoreTransport != null) mRestoreTransport.finishRestore();
                 } catch (Exception e) {
                     Log.e(TAG, "Error in finishRestore", e);
                 } finally {
                     mRestoreTransport = null;
+                    Binder.restoreCallingIdentity(oldId);
                 }
             }
 
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 252f2a6..90a606d 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -29,6 +29,7 @@
 
 import android.app.ActivityManagerNative;
 import android.app.IActivityManager;
+import android.backup.IBackupManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -123,6 +124,7 @@
     private static final boolean DEBUG_SETTINGS = false;
     private static final boolean DEBUG_PREFERRED = false;
     private static final boolean DEBUG_UPGRADE = false;
+    private static final boolean DEBUG_INSTALL = false;
 
     private static final boolean MULTIPLE_APPLICATION_UIDS = true;
     private static final int RADIO_UID = Process.PHONE_UID;
@@ -310,6 +312,7 @@
     static final int MCS_UNBIND = 6;
     static final int START_CLEANING_PACKAGE = 7;
     static final int FIND_INSTALL_LOC = 8;
+    static final int POST_INSTALL = 9;
     // Delay time in millisecs
     static final int BROADCAST_DELAY = 10 * 1000;
     private ServiceConnection mDefContainerConn = new ServiceConnection() {
@@ -324,6 +327,20 @@
         }
     };
 
+    // Recordkeeping of restore-after-install operations that are currently in flight
+    // between the Package Manager and the Backup Manager
+    class PostInstallData {
+        public InstallArgs args;
+        public PackageInstalledInfo res;
+
+        PostInstallData(InstallArgs _a, PackageInstalledInfo _r) {
+            args = _a;
+            res = _r;
+        }
+    };
+    final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<PostInstallData>();
+    int mNextInstallToken = 1;  // nonzero; will be wrapped back to 1 when ++ overflows
+
     class PackageHandler extends Handler {
         final ArrayList<HandlerParams> mPendingInstalls =
             new ArrayList<HandlerParams>();
@@ -422,6 +439,51 @@
                     }
                     startCleaningPackages();
                 } break;
+                case POST_INSTALL: {
+                    if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1);
+                    PostInstallData data = mRunningInstalls.get(msg.arg1);
+                    mRunningInstalls.delete(msg.arg1);
+
+                    if (data != null) {
+                        InstallArgs args = data.args;
+                        PackageInstalledInfo res = data.res;
+
+                        if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
+                            res.removedInfo.sendBroadcast(false, true);
+                            Bundle extras = new Bundle(1);
+                            extras.putInt(Intent.EXTRA_UID, res.uid);
+                            final boolean update = res.removedInfo.removedPackage != null;
+                            if (update) {
+                                extras.putBoolean(Intent.EXTRA_REPLACING, true);
+                            }
+                            sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
+                                    res.pkg.applicationInfo.packageName,
+                                    extras);
+                            if (update) {
+                                sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
+                                        res.pkg.applicationInfo.packageName,
+                                        extras);
+                            }
+                            if (res.removedInfo.args != null) {
+                                // Remove the replaced package's older resources safely now
+                                synchronized (mInstallLock) {
+                                    res.removedInfo.args.doPostDeleteLI(true);
+                                }
+                            }
+                        }
+                        Runtime.getRuntime().gc();
+
+                        if (args.observer != null) {
+                            try {
+                                args.observer.packageInstalled(res.name, res.returnCode);
+                            } catch (RemoteException e) {
+                                Log.i(TAG, "Observer no longer exists.");
+                            }
+                        }
+                    } else {
+                        Log.e(TAG, "Bogus post-install token " + msg.arg1);
+                    }
+                } break;
             }
         }
     }
@@ -4253,6 +4315,12 @@
         mHandler.sendMessage(msg);
     }
 
+    public void finishPackageInstall(int token) {
+        if (DEBUG_INSTALL) Log.v(TAG, "BM finishing package install for " + token);
+        Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
+        mHandler.sendMessage(msg);
+    }
+
     private void processPendingInstall(final InstallArgs args, final int currentStatus) {
         // Queue up an async operation since the package installation may take a little while.
         mHandler.post(new Runnable() {
@@ -4271,38 +4339,54 @@
                     }
                     args.doPostInstall(res.returnCode);
                 }
-                // There appears to be a subtle deadlock condition if the sendPackageBroadcast
-                // call appears in the synchronized block above.
-                if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
-                    res.removedInfo.sendBroadcast(false, true);
-                    Bundle extras = new Bundle(1);
-                    extras.putInt(Intent.EXTRA_UID, res.uid);
-                    final boolean update = res.removedInfo.removedPackage != null;
-                    if (update) {
-                        extras.putBoolean(Intent.EXTRA_REPLACING, true);
-                    }
-                    sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                                         res.pkg.applicationInfo.packageName,
-                                         extras);
-                    if (update) {
-                        sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                                res.pkg.applicationInfo.packageName,
-                                extras);
-                    }
-                    if (res.removedInfo.args != null) {
-                        // Remove the replaced package's older resources safely now
-                        synchronized (mInstallLock) {
-                            res.removedInfo.args.doPostDeleteLI(true);
+
+                // A restore should be performed at this point if (a) the install
+                // succeeded, (b) the operation is not an update, and (c) the new
+                // package has a backupAgent defined.
+                final boolean update = res.removedInfo.removedPackage != null;
+                boolean doRestore = (!update && res.pkg.applicationInfo.backupAgentName != null);
+
+                // Set up the post-install work request bookkeeping.  This will be used
+                // and cleaned up by the post-install event handling regardless of whether
+                // there's a restore pass performed.  Token values are >= 1.
+                int token;
+                if (mNextInstallToken < 0) mNextInstallToken = 1;
+                token = mNextInstallToken++;
+
+                PostInstallData data = new PostInstallData(args, res);
+                mRunningInstalls.put(token, data);
+                if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
+
+                if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
+                    // Pass responsibility to the Backup Manager.  It will perform a
+                    // restore if appropriate, then pass responsibility back to the
+                    // Package Manager to run the post-install observer callbacks
+                    // and broadcasts.
+                    IBackupManager bm = IBackupManager.Stub.asInterface(
+                            ServiceManager.getService(Context.BACKUP_SERVICE));
+                    if (bm != null) {
+                        if (DEBUG_INSTALL) Log.v(TAG, "token " + token
+                                + " to BM for possible restore");
+                        try {
+                            bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token);
+                        } catch (RemoteException e) {
+                            // can't happen; the backup manager is local
+                        } catch (Exception e) {
+                            Log.e(TAG, "Exception trying to enqueue restore", e);
+                            doRestore = false;
                         }
+                    } else {
+                        Log.e(TAG, "Backup Manager not found!");
+                        doRestore = false;
                     }
                 }
-                Runtime.getRuntime().gc();
-                if (args.observer != null) {
-                    try {
-                        args.observer.packageInstalled(res.name, res.returnCode);
-                    } catch (RemoteException e) {
-                        Log.i(TAG, "Observer no longer exists.");
-                    }
+
+                if (!doRestore) {
+                    // No restore possible, or the Backup Manager was mysteriously not
+                    // available -- just fire the post-install work request directly.
+                    if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
+                    Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
+                    mHandler.sendMessage(msg);
                 }
             }
         });
diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk
index 0813c35..498ec40 100644
--- a/tests/backup/Android.mk
+++ b/tests/backup/Android.mk
@@ -23,6 +23,7 @@
  
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := backup_helper_test
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 LOCAL_SHARED_LIBRARIES := libutils
 
 include $(BUILD_EXECUTABLE)
@@ -33,6 +34,8 @@
 
 LOCAL_MODULE_TAGS := optional
 
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
 LOCAL_PACKAGE_NAME := BackupTest